Bạn có ba model cần cùng một thứ: một field "ghi chú nội bộ", một cờ "đã có ghi chú", và một hàm dán nhãn. Chép cùng một đoạn code vào ba nơi là công thức của bug và mệt mỏi. Odoo có cách gọn hơn: viết logic một lần trong một AbstractModel rồi trộn (mix) nó vào từng model bằng _inherit. Đây chính là cơ chế đứng sau những mixin trứ danh như mail.thread (theo dõi thay đổi) hay mail.activity.mixin.

Bài này tự dựng một mixin thật vào module quan_ca_phe và gắn nó vào model thẻ thành viên — để thấy tận mắt AbstractModel là gì và trộn vào hoạt động ra sao.

AbstractModel: model không có bảng

models.AbstractModel khai báo giống model thường (có field, method, computed) nhưng có một điểm khác căn bản: nó không sinh bảng trong database. Nó tồn tại chỉ để cho đi field và method cho các model khác kế thừa.

class DauVetMixin(models.AbstractModel):     # AbstractModel, không phải Model
    _name = 'quan.mixin.dau.vet'
    _description = 'Mixin dấu vết ghi chú'

    ghi_chu_noi_bo = fields.Text('Ghi chú nội bộ')
    co_ghi_chu = fields.Boolean('Đã có ghi chú',
        compute='_compute_co_ghi_chu', store=True)

    @api.depends('ghi_chu_noi_bo')
    def _compute_co_ghi_chu(self):
        for rec in self:
            rec.co_ghi_chu = bool(rec.ghi_chu_noi_bo and rec.ghi_chu_noi_bo.strip())

    def dan_nhan(self, nhan):                # method dùng lại cho mọi model mix vào
        for rec in self:
            rec.ghi_chu_noi_bo = ((rec.ghi_chu_noi_bo or '') + '\n') + '[%s]' % nhan

Ảnh chụp đoạn mã Python nền tối gồm hai phần, phần trên là lớp DauVetMixin kế thừa models chấm AbstractModel với chú thích AbstractModel không phải Model, có field ghi_chu_noi_bo kiểu Text và co_ghi_chu kiểu Boolean compute store, phương thức _compute_co_ghi_chu gắn api chấm depends, và phương thức dan_nhan dùng lại cho mọi model, phần dưới là lớp TheThanhVien kế thừa models chấm Model có _name quan chấm the chấm thanh chấm vien và dòng _inherit chứa quan chấm mixin chấm dau chấm vet với chú thích trộn mixin nhận field và method

Hình 1: Trái tim của mixin. AbstractModel quan.mixin.dau.vet gói một field, một computed field và một method. Model thật quan.the.thanh.vien chỉ cần thêm một dòng _inherit = ['quan.mixin.dau.vet'] là nhận hết — không chép lại dòng code nào.

Trộn vào: chỉ một dòng _inherit

Để một model dùng mixin, thêm tên mixin vào _inherit:

class TheThanhVien(models.Model):
    _name = 'quan.the.thanh.vien'
    _inherit = ['quan.mixin.dau.vet']    # trộn mixin vào
    # ... từ đây model có ghi_chu_noi_bo, co_ghi_chu, dan_nhan()

Sau khi nâng cấp module, mình chạy thử trên hệ thống thật để xác nhận model đã "hút" được mọi thứ từ mixin:

Ảnh chụp phiên odoo shell blog19 kiểm tra mixin, quan chấm mixin chấm dau chấm vet có _abstract bằng True, không có bảng quan_mixin_dau_vet trả về False vì AbstractModel không sinh bảng, model the_thanh_vien có field ghi_chu_noi_bo và co_ghi_chu đều True và có method dan_nhan True, co_ghi_chu khi rỗng là False, sau khi gọi dan_nhan hai lần với VIP và Uu tien thì ghi_chu_noi_bo thành chuỗi có VIP xuống dòng Uu tien, co_ghi_chu compute store thành True, và cột ghi_chu_noi_bo nằm trong bảng quan_the_thanh_vien là True

Hình 2: Chạy thật. Mixin có _abstract=True và không có bảng quan_mixin_dau_vet. Model quan.the.thanh.vien sau khi _inherit thì có đủ field ghi_chu_noi_bo, co_ghi_chu và method dan_nhan — gọi dan_nhan hai lần cho ra [VIP]\n[Uu tien], computed field co_ghi_chu chuyển False→True. Quan trọng: cột của mixin nằm trong bảng của chính model thật (quan_the_thanh_vien), không phải ở đâu khác.

Câu cuối là điểm cốt lõi: mixin không có bảng, nhưng khi trộn vào một model có bảng, các field của nó thành cột của bảng đó. Nhiều model cùng trộn mixin thì mỗi model có cột riêng trong bảng riêng — code chung, dữ liệu riêng.

AbstractModel vs Model vs TransientModel

Ba loại model trong Odoo, phân biệt bằng chuyện bảng:

  • models.Model — model thường, có bảng, lưu dữ liệu nghiệp vụ lâu dài.
  • models.TransientModel — có bảng nhưng bản ghi tự dọn (xem bài wizard).
  • models.AbstractModel — không có bảng, chỉ để cho đi code qua kế thừa.

Thực ra Model và TransientModel đều kế thừa từ AbstractModel bên trong Odoo — abstract là gốc chung, còn hai cái kia thêm phần "có bảng".

Khi nào nên viết mixin

  • Cùng một tập field/logic lặp ở nhiều model: trạng thái duyệt, dấu vết ghi chú, tính toán chung. Gói thành mixin, _inherit vào từng model.
  • Muốn dùng tính năng có sẵn của Odoo: _inherit = ['mail.thread'] cho model của bạn khả năng theo dõi thay đổi và có khung trao đổi; mail.activity.mixin cho lịch hoạt động. Đây chính là mixin của Odoo core.
  • Không nên khi logic chỉ dùng cho đúng một model — lúc đó viết thẳng vào model gọn hơn.

Một lưu ý về _inherit: khi _inherit kèm _name mới (như ví dụ trên), bạn đang tạo model mới và trộn mixin vào. Khi _inherit không kèm _name, bạn đang mở rộng model đã có — đó là chuyện của một bài khác.

Ba ý mang về

  1. AbstractModel là model không sinh bảng (_abstract=True, không có bảng trong DB) — dùng để gói field, computed field và method dùng lại. Đã kiểm chứng: quan_mixin_dau_vet không tồn tại trong information_schema.tables.
  2. Trộn mixin bằng một dòng _inherit = ['ten.mixin']: model thật nhận đủ field + method của mixin, và field của mixin thành cột trong bảng của chính model đó (đã thấy cột ghi_chu_noi_bo trong bảng quan_the_thanh_vien).
  3. Ba loại model phân biệt bằng bảng: Model (có bảng, lưu lâu), TransientModel (có bảng, tự dọn), AbstractModel (không bảng, chỉ cho code). Mixin nổi tiếng của Odoo: mail.thread, mail.activity.mixin.

Ngoài kế thừa để trộn code, Odoo còn một kiểu kế thừa nữa gắn hẳn model này vào model khác: Phần sau nói về _inherits (có chữ "s") — kế thừa ủy quyền (delegation), cách một model "mượn" toàn bộ field của model khác qua một liên kết.