Khi cần chèn logic vào lúc bản ghi được tạo, sửa, hay xoá — sinh mã tự động, kiểm tra điều kiện, ghi nhật ký — bạn ghi đè create, write, hoặc unlink. Nghe đơn giản, nhưng đây là một trong những chỗ dễ viết sai nhất trong Odoo, và cái sai thường không báo lỗi mà chỉ làm dữ liệu lệch âm thầm. Bài này dựng một model thật với cả ba override rồi chạy thử để chỉ ra cách làm đúng và những cái bẫy.

create: nhận DANH SÁCH vals, không phải một dict

Bẫy lớn nhất: trong Odoo hiện đại, create nhận một danh sách các dict vals (tạo hàng loạt), không phải một dict đơn. Bạn phải trang trí bằng @api.model_create_multi và lặp qua vals_list:

@api.model_create_multi                     # BẮT BUỘC
def create(self, vals_list):
    for vals in vals_list:                  # sửa vals TRƯỚC khi tạo
        if not vals.get('ma_tu_sinh'):
            vals['ma_tu_sinh'] = sinh_ma()
    records = super().create(vals_list)     # gọi super để THẬT SỰ tạo
    for rec in records:                     # hậu xử lý trên bản ghi ĐÃ tạo
        rec.ten = (rec.ten or '') + ' ✓'
    return records                          # PHẢI trả về recordset

Ảnh chụp đoạn mã Python nền tối gồm ba phương thức, create gắn api chấm model_create_multi nhận vals_list lặp qua từng vals đặt ma_tu_sinh trước khi gọi super chấm create vals_list rồi hậu xử lý thêm dấu tick vào ten của từng record và trả về records, write gọi super chấm write vals lưu vào res rồi trả về res, unlink kiểm tra nếu bất kỳ record nào da_khoa thì raise UserError không thể xoá bản ghi đã khoá còn không thì trả về super chấm unlink

Hình 1: Ba override đúng chuẩn. Điểm cần thuộc lòng: (1) create có @api.model_create_multi và lặp vals_list; (2) super() được gọi để thao tác thật sự xảy ra — quên là bản ghi không được tạo/sửa/xoá; (3) sửa vals trước super (để giá trị vào đúng lúc ghi), nhưng hậu xử lý field tính từ bản ghi thì sau super (khi đã có id); (4) create trả về recordset, unlink chặn bằng cách raise trước super.

Chạy thật: ba override cùng bắn

Cài model demo quan.nhat.ky.thao.tac với ba override trên vào module rồi chạy odoo shell:

Ảnh chụp phiên odoo shell blog19 chạy ba override CRUD, create một danh sách ba vals ten A B C trả về 3 bản ghi theo lô, ma_tu_sinh đặt trước super ra NK-00001 NK-00002 NK-00003, ten sau hậu xử lý thêm dấu tick thành A tick B tick C tick, sau write đặt ten A-doi thì ten phần tử 0 là A-doi, khoá một bản ghi rồi gọi unlink thì ném UserError không thể xoá bản ghi đã khoá, unlink bản ghi thường thành công làm số bản ghi giảm từ 3 xuống 2

Hình 2: Cả ba override chạy đúng. create nhận danh sách 3 vals, tạo 3 bản ghi trong một lệnh; ma_tu_sinh được đặt trước super (NK-00001/2/3), còn dấu ✓ được thêm sau super. write gọi super rồi trả về. unlink raise UserError khi gặp bản ghi đã khoá, và xoá bình thường bản ghi thường (3→2).

Ba cái bẫy làm dữ liệu sai âm thầm

  • Quên @api.model_create_multi: code cũ dùng def create(self, vals) với một dict. Trong Odoo 19, khung gọi create với danh sách, nên vals của bạn thành một list — vals.get(...) nổ hoặc xử lý sai. Luôn dùng model_create_multi + lặp.
  • Quên gọi super(): nếu override create/write/unlink mà không gọi super(), thao tác không thật sự xảy ra — bản ghi không được ghi, nhưng cũng không có lỗi. Đây là loại bug tệ nhất vì im lặng.
  • Đặt hậu xử lý sai phía super: field tính từ bản ghi (cần id, cần giá trị đã lưu) phải xử lý sau super; còn giá trị muốn đưa vào lúc ghi phải đặt vào vals trước super. Đảo ngược là giá trị vào sai thời điểm.

Vài lưu ý thêm

  • write thường gọi super trước rồi hậu xử lý, vì bạn muốn dữ liệu đã lưu rồi mới phản ứng. Nhưng nếu cần chặn một thay đổi, kiểm tra trước super rồi raise.
  • unlink chặn bằng cách raise trước super (như ví dụ). Cẩn thận: unlink chạy trên recordset nhiều bản ghi, nên kiểm any(...)/for rec in self chứ đừng giả định một bản ghi.
  • Đừng ghi đè để làm việc mà có cơ chế sẵn: ràng buộc dữ liệu nên dùng @api.constrains hay models.Constraint; giá trị mặc định nên dùng default/default_get. Ghi đè create/write chỉ khi thật cần chèn logic vào vòng đời.
  • create/write/unlink bỏ qua nếu recordset rỗng vẫn an toàn, nhưng hãy nhớ chúng luôn có thể nhận nhiều bản ghi — viết code theo hướng batch từ đầu.

Ba ý mang về

  1. create phải có @api.model_create_multi và nhận vals_list (danh sách dict) — đã chứng minh: một lệnh create([...]) tạo 3 bản ghi cùng lúc. Lặp qua vals_list, sửa vals trước super(), hậu xử lý sau.
  2. Luôn gọi super() trong cả ba override — quên là thao tác không xảy ra mà không báo lỗi. create trả về recordset; write trả về True; unlink trả về kết quả super.
  3. Đặt logic đúng phía super: giá trị đưa vào lúc ghi → trước super; phản ứng/field từ bản ghi đã tạo → sau super; chặn thao tác (unlink bản ghi khoá) → raise trước super. Nhớ mọi override chạy theo lô nhiều bản ghi.

Xong phần model, ta chuyển sang phần giao diện. Lần sau bắt đầu nhóm bài về view: Phần sau mổ xẻ bố cục form view — sheet, group, và cách sắp xếp field cho một biểu mẫu gọn gàng, chuẩn Odoo.