Ở bài trước, @api.constrains kiểm dữ liệu bằng Python — mạnh cho luật phức tạp. Nhưng với luật đơn giản (mã phải duy nhất, số không được âm), có cách nhanh và chắc hơn nhiều: để chính PostgreSQL canh, qua ràng buộc cấp CSDL. Và đây là bài có một phát hiện bất ngờ khi tôi thử trên Odoo 19 thật: API cũ _sql_constraints đã bị khai tử.
Phát hiện thật: Odoo 19 đổi API
Tôi thêm ràng buộc theo cách quen thuộc _sql_constraints = [...] vào module quan_ca_phe, nâng cấp, và Odoo 19 cảnh báo thẳng:
WARNING odoo.registry: Model attribute '_sql_constraints' is no longer
supported, please define models.Constraint on the model.
Ràng buộc không được tạo. Cách viết đúng của Odoo 19 là models.Constraint — một khai báo trên model, không còn là list. (Bạn đã thấy nó trong code core sale_order.py: _date_order_conditional_required = models.Constraint(...).)

Hình 1: Odoo 19 thay _sql_constraints (list) bằng models.Constraint(biểu_thức_sql, thông_báo) khai trên model. Biểu thức là SQL thuần: unique(name), check(diem >= 0). Đây là ràng buộc cấp CSDL — nhanh, và đặc biệt an toàn với chạy song song (race condition) mà kiểm bằng Python khó đảm bảo.
Bằng chứng: nó tạo ràng buộc SQL thật
Sau khi dùng đúng models.Constraint và nâng cấp module, Odoo tạo ràng buộc thật trong PostgreSQL. Đây là output thật trên db blog19:

Hình 2: Bằng chứng thật. Sau -u, hai ràng buộc xuất hiện trong pg_constraint: UNIQUE (name) và CHECK ((diem >= 0)) — cột SQL thật. Kích thử: tạo thẻ trùng mã → UniqueViolation; tạo thẻ điểm âm → CheckViolation. Chính PostgreSQL chặn, không phải Python.
SQL constraint vs @api.constrains: chọn cái nào?
| Tiêu chí | models.Constraint (SQL) |
@api.constrains (Python) |
|---|---|---|
| Loại luật | Đơn giản: unique, check, not null | Phức tạp: nhiều trường, tính toán, xuyên quan hệ |
| Ai kiểm | PostgreSQL | Odoo (Python) |
| Tốc độ | Rất nhanh | Chậm hơn (chạy code) |
| An toàn race | Có (khóa ở CSDL) | Khó đảm bảo |
| Biểu đạt | Hạn chế (cú pháp SQL) | Tự do (mọi logic Python) |
Quy tắc: luật đơn giản → SQL constraint (nhanh, chắc); luật phức tạp → @api.constrains.
Vì sao unique NÊN là SQL constraint
Kiểm "mã đã tồn tại chưa" bằng Python (search_count > 0) có lỗ hổng race condition: hai request đồng thời cùng thấy "chưa có", cùng ghi → trùng. Ràng buộc unique cấp CSDL không bị vậy — PostgreSQL khóa và đảm bảo duy nhất kể cả khi chạy song song. Đây là lý do luật duy nhất luôn nên dùng SQL constraint, không dùng @api.constrains.
Vài loại ràng buộc SQL hay dùng
_ma_unique = models.Constraint('unique(name)', 'Mã phải duy nhất!')
_gia_duong = models.Constraint('check(gia > 0)', 'Giá phải dương!')
_ngay_hople = models.Constraint('check(date_end >= date_start)', 'Ngày kết thúc phải sau ngày bắt đầu!')
_unique_cap = models.Constraint('unique(company_id, code)', 'Mã phải duy nhất trong mỗi công ty!')
unique(a, b) là duy nhất tổ hợp — cặp (a, b) không trùng, nhưng từng cái vẫn lặp được.
Lưu ý khi triển khai
- Ràng buộc tạo lúc
-u module: sửa/thêmmodels.Constraintphải nâng cấp module để Odoo tạo/cập nhật trong CSDL. - Dữ liệu cũ vi phạm: nếu bảng đã có dữ liệu trùng/sai, Odoo không tạo được ràng buộc (PostgreSQL từ chối) — phải dọn dữ liệu trước.
- Thông báo: chuỗi thứ hai là thông báo hiện cho người dùng khi vi phạm — viết rõ ràng tiếng Việt.
Nhớ ba ý
- Odoo 19 bỏ
_sql_constraints, thay bằngmodels.Constraint(biểu_thức_sql, thông_báo)— tôi đã thấy cảnh báo deprecate thật và ràng buộcUNIQUE/CHECKsinh trongpg_constraint. - Ràng buộc cấp CSDL do PostgreSQL canh — nhanh, và an toàn với race condition; kích thật cho
UniqueViolation/CheckViolation. - Luật đơn giản (unique/check) → SQL constraint; phức tạp →
@api.constrains; luật duy nhất luôn nên dùng SQL để tránh race.
Xong ràng buộc "chặn". Có một loại "phản ứng" khác không chặn mà hỗ trợ người dùng lúc nhập. Phần sau đi vào @api.onchange — tự điền/cảnh báo ngay khi người dùng gõ trên form, và vì sao nó khác hẳn constraint.