Một field Many2one như "Khách hàng" của thẻ thành viên, mặc định cho chọn mọi bản ghi res.partner — cả cá nhân lẫn công ty, cả nhà cung cấp lẫn khách. Nhưng một tấm thẻ thành viên thì phải thuộc về một con người, không phải một công ty. domain là thứ nói với Odoo điều đó: chỉ cho chọn những bản ghi khớp điều kiện. Bài này gắn một domain thật vào field partner_id và kiểm chứng bằng cả giao diện lẫn odoo shell.

Ba nơi đặt domain

Ảnh chụp đoạn mã nền tối ba phần: domain tĩnh trong view đặt trên field partner_id với điều kiện is_company bằng False; domain động là chuỗi Python tham chiếu company_id và customer_rank lớn hơn 0; domain trong định nghĩa field Python Many2one res.partner kèm domain is_company False; cuối ảnh ghi domain tĩnh là literal danh sách còn domain động là chuỗi Odoo tính lại khi field phụ thuộc đổi, domain ở view đè domain ở định nghĩa field

Hình 1: Ba cách. (1) Domain tĩnh trong view — một literal danh sách, luôn cố định. (2) Domain động — một chuỗi Python tham chiếu field khác trên cùng bản ghi (company_id), Odoo tính lại mỗi khi field đó đổi. (3) Domain đặt ngay trong định nghĩa field (Python) — áp cho mọi view dùng field. Domain ở view đè domain ở định nghĩa field.

Phân biệt quan trọng của Odoo 19: domain tĩnh là một danh sách literal ([('is_company','=',False)]), còn domain động là một chuỗi mà Odoo đánh giá lại theo giá trị các field khác. Viết nhầm domain động thành danh sách thì nó không "sống" theo field.

Domain tĩnh đang chạy

Tôi thêm vào form đúng một dòng: domain="[('is_company', '=', False)]". Giờ mở ô "Khách hàng" ra:

Ảnh chụp giao diện Odoo thật, ô Khách hàng trên form thẻ đang mở danh sách gợi ý gồm Administrator, Emily Davis, các liên hệ cá nhân thuộc Global Tech Corp như Kho hàng Long Beach và Phòng Kế toán Trụ sở và Sarah Nguyen và Trần Quốc Việt, rồi Hoàng Văn Em, John Smith, cuối cùng là Tìm kiếm thêm; không có dòng nào là một công ty độc lập

Hình 2: Ô "Khách hàng" mở ra chỉ toàn cá nhân — không một công ty độc lập nào lọt vào danh sách. Domain đã lọc ngay tại nguồn: những gì không khớp is_company = False thì Odoo không cho hiện để chọn.

Nhìn dropdown thì khó thấy "cái gì bị loại". Cho chắc, tôi kiểm bằng odoo shell để đếm chính xác:

Ảnh chụp phiên odoo shell trên blog19, gán P bằng env res.partner, search_count rỗng trả về 31 là tổng liên hệ, search_count với is_company bằng False trả về 25 là số cá nhân chọn được, search_count với is_company bằng True trả về 6 là số công ty bị domain loại, và search hai công ty đầu trả về Cà Phê Việt Chi nhánh Hà Nội và Công ty Cà Phê Tây Nguyên là những cái không hiện trong ô chọn

Hình 3: Con số thật. Tổng 31 liên hệ; domain [('is_company','=',False)] cho 25 cá nhân chọn được; 6 công ty bị loại (ví dụ "Cà Phê Việt - Chi nhánh Hà Nội", "Công ty Cà Phê Tây Nguyên"). Chính 6 cái này là những dòng không xuất hiện ở Hình 2.

Domain động: lọc theo một field khác

Cái hay thật sự của domain là động theo bản ghi đang sửa. Ví dụ kinh điển: chọn liên hệ nhưng chỉ trong công ty đã chọn ở field company_id:

<field name="partner_id"
       domain="[('company_id', '=', company_id), ('customer_rank', '>', 0)]"/>

Ở đây company_id (không có ngoặc) là giá trị field trên form, không phải chuỗi. Đổi công ty ở trên, danh sách khách chọn được bên dưới tự thu hẹp theo. Đây là cách Odoo làm những form kiểu "chọn tỉnh rồi mới chọn quận", "chọn danh mục rồi mới chọn sản phẩm".

domain ở view hay ở field?

  • Đặt ở định nghĩa field (Python) khi giới hạn là bản chất của quan hệ — đúng ở mọi nơi field xuất hiện. Ví dụ "thẻ luôn thuộc cá nhân".
  • Đặt ở view khi giới hạn chỉ đúng trong ngữ cảnh một màn hình, hoặc cần động theo các field khác của form đó. Domain view đè domain field.
  • Domain chỉ chặn ở tầng chọn (UI) — nó lọc danh sách gợi ý. Muốn cấm lưu một giá trị sai thật sự thì cần thêm ràng buộc ở tầng model (constraint), vì API vẫn ghi được bản ghi ngoài domain.

Ba ý mang về

  1. domain giới hạn bản ghi được phép chọn trong Many2one/Many2many; đặt được ở view (đè) hoặc trong định nghĩa field (áp mọi nơi).
  2. Tĩnh là danh sách literal; động là chuỗi tham chiếu field khác của cùng bản ghi (company_id không ngoặc) — Odoo tính lại mỗi khi field phụ thuộc đổi, cho các form "chọn A rồi mới chọn B".
  3. Domain chỉ lọc ở tầng chọn, không phải ràng buộc lưu — cần chặn cứng thì thêm constraint ở model; đã đo thật: 31 liên hệ → 25 chọn được, 6 công ty bị loại.

Domain quyết định chọn được gì. Bài sau là người anh em của nó, quyết định mặc định là gì khi tạo mới: Phần sau về context trên field — cách truyền giá trị mặc định để khi bấm "Mới" ngay trong một quan hệ, bản ghi con tự điền sẵn đúng thứ bạn muốn.