Bạn đã gặp: mở form đơn hàng, chọn khách hàng, và bảng giá/điều khoản/địa chỉ tự điền theo. Hay chọn một tùy chọn thì hiện cảnh báo ngay. Đó là @api.onchange — cơ chế phản ứng tức thì trên form đang nhập. Nó cực tiện cho trải nghiệm người dùng, nhưng cũng là thứ hay bị dùng sai thay cho ràng buộc. Nối tiếp @api.constrains và SQL constraint, bài này làm rõ vai trò thật của onchange.
onchange: tự điền và cảnh báo trên form
@api.onchange('field') chạy method mỗi khi người dùng đổi trường đó trên form (chưa lưu). Bên trong, bạn gán các trường khác để tự điền, hoặc return {'warning': {...}} để cảnh báo:

Hình 1: Một @api.onchange('partner_id') thật. Khi người dùng chọn khách hàng: (1) tự điền name = 'TV-<id>' nếu ô còn trống; (2) nếu khách chưa mua (customer_rank == 0) thì return {'warning': {...}} để hiện cảnh báo. Chú ý khác biệt cốt lõi với @api.constrains ở phần dưới.
Cho nó chạy thật
Tôi thêm onchange trên vào module quan_ca_phe, nâng cấp, rồi giả lập form đang nhập (T.new({}) tạo bản ghi ảo chưa lưu). Đây là output thật:

Hình 2: Output thật. form = T.new({}) là bản ghi ảo (name = False). Gán partner_id = khách đã mua → onchange chạy, name tự điền thành 'TV-7'. Với khách chưa mua (customer_rank = 0), onchange trả về một dict warning với "Khách hàng này chưa từng mua hàng." — form vẫn tự điền name, kèm hộp cảnh báo. onchange chỉ gợi ý, không lưu gì.
onchange KHÁC constrains — đừng nhầm
Đây là nhầm lẫn nguy hiểm nhất của người mới. onchange không phải hàng rào bảo vệ dữ liệu:
@api.onchange |
@api.constrains |
|
|---|---|---|
| Chạy khi | Đổi trường trên form (chưa lưu) | Lưu (create/write) |
| Nguồn | Chỉ giao diện form | Mọi nguồn (form, API, import, shell) |
| Mục đích | Tự điền, gợi ý, cảnh báo | Chặn dữ liệu sai |
| Chặn được không | Không (chỉ cảnh báo) | Có (raise → rollback) |
Điểm chí mạng: onchange không chạy khi dữ liệu đến từ API, import, hay create trong code. Nên nếu bạn dựa vào onchange để "kiểm tra hợp lệ", dữ liệu xấu vẫn lọt qua API. Muốn bảo vệ dữ liệu chắc chắn, luôn dùng @api.constrains/SQL constraint — onchange chỉ để hỗ trợ trải nghiệm nhập liệu.
Ba việc onchange làm tốt
- Tự điền phụ thuộc: chọn khách → điền địa chỉ giao/bảng giá; chọn sản phẩm → điền đơn giá/đơn vị.
- Cảnh báo mềm: nhắc nhở nhưng vẫn cho làm (như "khách chưa mua", "tồn kho thấp").
- Lọc lựa chọn động: đổi
domaincủa một trường Many2one khác theo giá trị vừa chọn.
Vài lưu ý
- Chạy trên bản ghi ảo: trong onchange,
selflà bản ghi chưa lưu (NewId), nên đừng gọi những thứ cần id thật (như tạo bản ghi liên kết). - Đừng viết logic nặng/ghi CSDL trong onchange — nó chạy mỗi lần gõ, và không nên có tác dụng phụ lên CSDL.
- Trùng lặp với computed: nhiều trường hợp "tự điền" nên dùng trường computed (chạy cả khi tới từ API) thay vì onchange (chỉ form). Cân nhắc: cần đúng ở mọi nguồn → computed; chỉ hỗ trợ nhập tay → onchange.
Nhớ ba ý
@api.onchange('field')chạy khi người dùng đổi trường trên form: gánself.x = ...để tự điền,return {'warning': {...}}để cảnh báo — đo thật:nametự điền'TV-7', khách chưa mua ra warning.- onchange KHÁC constrains: chỉ chạy trên form, không chạy từ API/import, không chặn được — dựa vào nó để kiểm hợp lệ là lỗ hổng.
- Bảo vệ dữ liệu →
@api.constrains/SQL constraint; "tự điền" cần đúng ở mọi nguồn → computed; onchange chỉ để hỗ trợ nhập tay.
Nói về "tự điền", có một cơ chế điền giá trị ban đầu khi tạo bản ghi mới. Phần sau đi vào default và default_get — cách đặt giá trị mặc định tĩnh và động cho trường khi tạo mới.