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 đó:

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:

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 ý
- 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. - Đ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.
- Đá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.