Yêu cầu kinh điển: "cho tôi thêm một trường vào công việc, nhưng chỉ cho dự án này thôi". Cách cũ là lập trình viên thêm cột — chậm, cứng nhắc. Odoo hiện đại có câu trả lời hay hơn nhiều: trường Properties — cho phép mỗi bản ghi cha định nghĩa schema riêng, và các bản ghi con có đúng những trường đó, không cần thêm cột, không cần code. Đây là một trong những tính năng mạnh và mới của Odoo (16+). Khép lại chương "trường quan hệ & đặc biệt", bài này bàn Properties.

Ý tưởng: schema động do người dùng định

Properties cần hai phần: bản ghi cha khai định nghĩa (schema), bản ghi con dùng trường Properties trỏ tới định nghĩa đó:

Trường Properties trong Odoo 19 mỗi bản ghi một schema riêng không cần thêm cột: người dùng tự thêm trường không cần lập trình viên Odoo 16 trở lên; một bản ghi cha khai định nghĩa schema, project.project có task_properties_definition bằng fields.PropertiesDefinition Task Properties; hai bản ghi con dùng Properties trỏ tới định nghĩa của cha, project.task có task_properties bằng fields.Properties với definition bằng project_id.task_properties_definition nghĩa mỗi project định nghĩa các trường riêng task của project đó có chúng; định nghĩa schema là list dict tên kiểu nhãn, do_uu_tien type selection string Độ ưu tiên với selection cao Cao thap Thấp, so_gio type integer string Số giờ, gap type boolean string Gấp; lưu ở đâu một cột JSONB không thêm cột cho từng property

Hình 1: PropertiesDefinition trên project khai danh sách trường (schema). Properties(definition='project_id.task_properties_definition') trên task trỏ tới định nghĩa của project cha. Kết quả: mỗi project tự định nghĩa các trường tuỳ biến, và task của project đó có đúng những trường ấy — người dùng thêm/bớt qua giao diện, không cần lập trình viên.

Chạy thật trên project.task

Đây là core project.task dùng Properties thật. Tôi tạo một project với 3 trường tuỳ biến, rồi 2 task với giá trị khác nhau. Output thật trên db blog19:

Properties chạy thật trên project.task database blog19: project DA Cafe khai 3 trường tuỳ biến schema, proj.task_properties_definition ra 3 trường do_uu_tien selection so_gio integer gap boolean; hai task cùng project cùng schema giá trị khác nhau, t1 là Task Menu với task_properties do_uu_tien cao so_gio 8 gap True, t1.task_properties trả do_uu_tien bằng cao so_gio bằng 8 gap bằng True, t2 do_uu_tien bằng thap task khác giá trị khác; lưu trong cơ sở dữ liệu một cột JSONB không thêm cột cho mỗi property, SELECT task_properties từ project_task với id t1 ra dict gap true so_gio 8 do_uu_tien cao là JSONB; đổi thêm trường tuỳ biến không cần sửa code hay ALTER TABLE

Hình 2: Output thật. Project "DA Cafe" định nghĩa 3 trường (do_uu_tien selection, so_gio integer, gap boolean). Hai task cùng project có cùng schema nhưng giá trị khác nhau (t1.do_uu_tien='cao', t2.do_uu_tien='thap'). Nhìn CSDL: tất cả lưu trong một cột JSONB {'gap': true, 'so_gio': 8, 'do_uu_tien': 'cao'} — không thêm cột cho từng property.

Vì sao đây là bước tiến lớn

  • Không ALTER TABLE: thêm/bớt trường tuỳ biến không đụng schema CSDL — không migration, không downtime.
  • Người dùng tự làm: admin/quản lý dự án tự thêm trường qua giao diện, không cần lập trình viên.
  • Linh hoạt theo ngữ cảnh: mỗi project (hay mỗi loại) có bộ trường riêng — thứ mà cột cố định không làm được.

Đây là lý do CRM (lead), Project (task), Event (registration) dùng Properties: mỗi pipeline/dự án/sự kiện cần trường khác nhau, không thể đúc sẵn hết vào model.

Các kiểu property hỗ trợ

Định nghĩa mỗi property có type: char, text, integer, float, boolean, date, datetime, selection, tags, many2one, many2many. Gần đủ như trường thật — nhưng lưu trong JSON.

{'name': 'nha_cung_cap', 'type': 'many2one', 'string': 'NCC',
 'comodel': 'res.partner'}   # property kiểu Many2one cũng được

Đánh đổi: sức mạnh đổi lấy hiệu năng

Properties lưu JSONB nên có giới hạn:

  • Tìm kiếm/lọc chậm hơn trường cột thật (dù PostgreSQL index được JSONB, vẫn không bằng cột riêng).
  • Không FK cứng cho property many2one — như Reference, quan hệ "mềm".
  • Không hợp dữ liệu nghiệp vụ cốt lõi cần truy vấn nặng — cái đó vẫn dùng cột thật.

Quy tắc: trường cốt lõi, dùng thường, cần tìm nhanh → cột thật (field bình thường); trường phụ, tuỳ biến theo ngữ cảnh, người dùng tự định → Properties.

Nhớ ba ý

  1. Properties cho mỗi bản ghi một schema động do bản ghi cha định nghĩa (PropertiesDefinition + Properties(definition='...')) — người dùng tự thêm trường, không cần code, không ALTER TABLE.
  2. Đo thật: 2 task cùng project có cùng schema, giá trị khác nhau; tất cả lưu trong một cột JSONB.
  3. Đánh đổi: linh hoạt nhưng tìm/lọc chậm hơn, quan hệ mềm — dùng cho trường tuỳ biến theo ngữ cảnh, còn dữ liệu cốt lõi vẫn dùng cột thật.

Xong toàn bộ các kiểu trường của Odoo. Chương tiếp bước vào lõi thực thi ORM — môi trường. Phần sau mổ xẻ env: cr (con trỏ CSDL), uid (người dùng), context — ba thứ mọi recordset mang theo và bạn dùng suốt.