Bấm một nút trong Odoo và một hộp thoại hiện lên: "Cộng điểm cho các thẻ đã chọn", bạn nhập số, bấm OK, hộp thoại đóng lại và dữ liệu được áp. Cái hộp thoại đó — trong Odoo gọi là wizard — không phải một widget giao diện đặc biệt. Nó là một model đầy đủ, có field, có method, có form view. Điểm khác biệt duy nhất: nó kế thừa models.TransientModel thay vì models.Model, và vì thế dữ liệu của nó tự dọn sau một thời gian ngắn.

Để hiểu tường tận, ở bài này mình tự dựng một wizard thật vào module quan_ca_phe rồi chạy thử — không mô tả suông.

TransientModel: giống Model, nhưng bản ghi có hạn sử dụng

Khai báo một wizard giống hệt khai báo một model thường, chỉ đổi lớp cha:

class ThemDiemWizard(models.TransientModel):   # KHÔNG phải models.Model
    _name = 'quan.them.diem.wizard'
    _description = 'Wizard cộng điểm hàng loạt'

    diem_cong = fields.Integer(string='Số điểm cộng thêm', default=10)
    the_ids = fields.Many2many('quan.the.thanh.vien', string='Thẻ áp dụng')

Ảnh chụp đoạn mã Python nền tối định nghĩa lớp ThemDiemWizard kế thừa models chấm TransientModel với chú thích không phải models chấm Model, có tên quan chấm them chấm diem chấm wizard, hai field diem_cong kiểu Integer mặc định 10 và the_ids kiểu Many2many tới quan chấm the chấm thanh chấm vien, một phương thức default_get gắn api chấm model nạp sẵn the_ids từ active_ids trong context nếu active_model đúng, và phương thức action_cong_diem lặp qua the_ids cộng diem_cong lên từng thẻ rồi trả về ir chấm actions chấm act_window_close để đóng hộp thoại

Hình 1: Wizard cộng điểm. default_get nạp sẵn the_ids từ active_ids — chính là các bản ghi user đang chọn ở màn hình danh sách khi mở wizard. action_cong_diem là hàm nút bấm gọi tới: nó ghi lên model thật (quan.the.thanh.vien) rồi trả về ir.actions.act_window_close để đóng hộp thoại.

Điểm mấu chốt về ý nghĩa: wizard là công cụ tạm để thu thập input rồi tác động lên dữ liệu thật. Bản thân bản ghi wizard không phải dữ liệu nghiệp vụ cần giữ — nó chỉ sống đủ lâu để user điền form và bấm nút.

Chạy thật: wizard vừa cài, cộng điểm thật

Sau khi cài wizard vào module và nâng cấp, mình chạy thử qua odoo shell để thấy nó hoạt động end-to-end:

Ảnh chụp phiên odoo shell blog19, wizard quan chấm them chấm diem chấm wizard có _transient bằng True, _transient_max_hours bằng 1.0 nghĩa bản ghi cũ hơn 1 giờ sẽ bị dọn, _transient_max_count bằng 0 nghĩa không giới hạn theo số lượng, có bảng quan_them_diem_wizard trong DB là True vì transient vẫn có bảng, mô phỏng user chọn thẻ rồi mở wizard nhập cộng 15, thẻ VIP-0001 diem trước là 120, wizard tạo id 1 với the_ids nạp sẵn từ active_ids là danh sách chứa 1, action trả về ir chấm actions chấm act_window_close, thẻ VIP-0001 diem sau khi bấm wizard là 135 đã ghi lên model thật cộng 15

Hình 2: Wizard chạy thật. _transient=True. Thẻ VIP-0001 có 120 điểm; mở wizard (nó tự nạp the_ids từ active_ids), nhập +15, gọi action_cong_diem → thẻ thật thành 135. Bản ghi wizard chỉ là phương tiện; kết quả nằm ở model thật.

Vòng đời: bảng thật, nhưng tự dọn

Đây là chỗ khác biệt cốt lõi với model thường, và cũng hay bị hiểu lầm:

  • TransientModel VẪN có bảng thật trong database (quan_them_diem_wizard — kiểm tra information_schema.tables thấy True). Nó không phải "chỉ trong bộ nhớ".
  • Nhưng bản ghi tự bị dọn định kỳ bởi cơ chế vacuum của Odoo, dựa trên hai thuộc tính:
    • _transient_max_hours (mặc định 1.0): bản ghi cũ hơn ngần này giờ bị xoá.
    • _transient_max_count (mặc định 0 = không giới hạn theo số lượng): nếu đặt >0, chỉ giữ tối đa ngần đó bản ghi mới nhất.

Nhờ vacuum, bảng wizard không phình to vô hạn dù người dùng mở hàng nghìn hộp thoại mỗi ngày. Bạn không cần tự dọn — Odoo lo.

Hệ quả thực chiến

  • Đừng lưu dữ liệu cần giữ lâu vào wizard. Bản ghi wizard biến mất sau ~1 giờ. Kết quả phải được ghi sang model thật (như action_cong_diem làm với the_ids).
  • default_get + active_ids là cách chuẩn nạp ngữ cảnh. Khi user chọn nhiều dòng ở danh sách rồi mở wizard qua một action, Odoo truyền active_ids/active_model trong context. Đọc chúng trong default_get để wizard biết đang thao tác trên bản ghi nào.
  • Field Many2one/Many2many từ wizard tới model thật là bình thường, nhưng model thật không nên tham chiếu ngược tới wizard — quan hệ chỉ một chiều, vì bản ghi wizard sẽ biến mất.
  • Quyền truy cập vẫn cần khai trong ir.model.access.csv cho model transient, y như model thường (mình đã thêm dòng access_them_diem_wizard cho base.group_user).

Ba ý mang về

  1. Mọi wizard là một TransientModel — khai báo giống models.Model (field, method, view) nhưng kế thừa TransientModel. Nó dùng để thu thập input rồi tác động lên dữ liệu thật, chứ bản thân không lưu dữ liệu nghiệp vụ.
  2. TransientModel có bảng thật nhưng bản ghi tự dọn: vacuum xoá theo _transient_max_hours (mặc định 1 giờ) và _transient_max_count (mặc định 0 = không giới hạn số lượng) — đã kiểm chứng cả hai giá trị trên wizard vừa dựng.
  3. Nạp ngữ cảnh qua default_get đọc active_ids/active_model; hành động (action_...) ghi kết quả lên model thật rồi trả ir.actions.act_window_close để đóng hộp thoại (đã chạy thật: VIP-0001 từ 120 lên 135).

Còn một họ model đặc biệt nữa, ngược hoàn toàn với transient — model không có bảng riêng mà dùng để chia sẻ code: Phần sau nói về AbstractModel và mixin — cách gói logic dùng lại cho nhiều model khác nhau.