Ở 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(...).)

Ràng buộc ở tầng cơ sở dữ liệu Odoo 19 đổi API: Odoo 18 trở về trước dùng _sql_constraints là list các bộ ba tên biểu thức thông báo nhưng nay đã bỏ ở Odoo 19, Odoo 19 cảnh báo _sql_constraints is no longer supported please define models.Constraint on the model; Odoo 19 dùng models.Constraint, class TheThanhVien model quan.the.thanh.vien khai _ma_the_unique bằng models.Constraint với biểu thức SQL unique name và thông báo Mã thẻ phải là duy nhất, và _diem_khong_am bằng models.Constraint với check diem lớn hơn hoặc bằng 0 và thông báo Điểm tích luỹ không được âm; so sánh SQL constraint nhanh do PostgreSQL kiểm hợp luật đơn giản unique check not null, api.constrains dùng Python hợp luật phức tạp nhiều trường tính toán, unique nên dùng SQL luôn vì nhanh và an toàn với chạy song song race

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:

models.Constraint chạy thật trên module quan_ca_phe database blog19: sau nâng cấp module bằng -u, Odoo tạo ràng buộc thật trong PostgreSQL pg_constraint, psql SELECT conname pg_get_constraintdef cho quan_the_thanh_vien_ma_the_unique là UNIQUE name và quan_the_thanh_vien_diem_khong_am là CHECK diem lớn hơn bằng 0; tạo TV-SQL-001 diem 50 OK; một trùng mã tạo lại TV-SQL-001 diem 10 bị PostgreSQL chặn UniqueViolation duplicate key value violates unique constraint quan_the_thanh_vien_ma_the_unique; hai điểm âm tạo TV-SQL-002 diem -5 bị PostgreSQL chặn CheckViolation new row violates check constraint quan_the_thanh_vien_diem_khong_am; ràng buộc do chính PostgreSQL canh nhanh và chắc kể cả khi chạy song song

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êm models.Constraint phả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 ý

  1. Odoo 19 bỏ _sql_constraints, thay bằng models.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ộc UNIQUE/CHECK sinh trong pg_constraint.
  2. 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.
  3. 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.