Một câu đố khiến người mới bối rối: mở res.users ra, bạn đọc được user.name, user.email, user.phone. Nhưng nhìn vào database, bảng res_users không có cột name. Vậy user.name lấy ở đâu ra? Câu trả lời là một cơ chế kế thừa đặc biệt của Odoo: _inherits (chú ý chữ "s" ở cuối). Nó khác hẳn _inherit ở bài trước, và hiểu nhầm hai cái là nguồn của rất nhiều lú lẫn.

_inherits: nối hai model qua một Many2one

_inherits khai báo dưới dạng {model_cha: 'field_liên_kết'}. Ví dụ kinh điển là chính res.users:

class Users(models.Model):
    _name = 'res.users'
    _inherits = {'res.partner': 'partner_id'}   # uỷ quyền sang partner
    partner_id = fields.Many2one('res.partner', required=True, ondelete='restrict')

Ý nghĩa: mỗi res.users giữ một liên kết partner_id tới một res.partner, và Odoo cho phép user "mượn" (delegate) mọi field của partner qua liên kết đó. user.name thật ra là user.partner_id.name — Odoo tự chuyển hướng.

Ảnh chụp đoạn mã Python nền tối, lớp Users kế thừa models chấm Model có _name res chấm users và _inherits là từ điển ánh xạ res chấm partner sang partner_id với chú thích uỷ quyền sang partner, field partner_id là Many2one tới res chấm partner required True ondelete restrict, tiếp theo các dòng minh hoạ u chấm name thật ra đọc u chấm partner_id chấm name, u chấm email tương tự, gán u chấm name bằng X thật ra ghi vào partner, đoạn tạo user với name và email là field của partner cùng login là field riêng của user sinh ra một dòng res_users và một dòng res_partner, và chú thích khác _inherit không s là trộn không tạo liên kết còn _inherits là hai bảng riêng nối qua Many2one

Hình 1: _inherits = {'res.partner': 'partner_id'}. Field partner_id là mắt xích. Mọi field của partner (name, email, phone...) truy cập được trực tiếp trên user, nhưng dữ liệu thật nằm ở bản ghi partner được liên kết. ondelete='restrict' bảo vệ: không cho xoá partner khi còn user trỏ vào.

Chứng minh trên hệ thống thật

Chạy odoo shell để thấy cơ chế lộ ra:

Ảnh chụp phiên odoo shell blog19, res chấm users chấm _inherits là từ điển res chấm partner sang partner_id, cột name không có trong bảng res_users trả về False vì name nằm ở res_partner còn cột name có trong bảng res_partner là True, tạo một user chỉ với name và email làm số partner tăng từ 31 lên 32 do tự động tạo partner, u chấm id bằng 9 và u chấm partner_id chấm id bằng 3549 là hai bản ghi riêng ở hai bảng, u chấm name là Test Delegation và u chấm email là td at x chấm vn, sau khi gán u chấm name bằng Da Doi Ten thì partner chấm name cũng thành Da Doi Ten do ghi xuyên qua, và xoá user thì partner vẫn còn vì partner tồn tại độc lập với user

Hình 2: Ba điều cốt lõi hiện rõ. (1) Cột name không nằm ở bảng res_users — nó ở res_partner; user chỉ mượn. (2) Tạo user tự sinh partner: số partner tăng 31→32, và user id 9 nối tới partner id 3549 — hai bản ghi, hai bảng. (3) Ghi xuyên qua: gán u.name='Da Doi Ten' thì partner.name đổi theo. Lưu ý cuối: xoá user thì partner vẫn còn — quan hệ một chiều, partner sống độc lập.

_inherits vs _inherit: đừng nhầm chữ "s"

Đây là bảng phân biệt để không bao giờ lẫn nữa:

  • _inherit = ['ten.model'] (không "s"): mở rộng hoặc trộn. Nếu kèm _name mới → trộn mixin (bài 53); nếu không kèm _name → mở rộng chính model đó. Không tạo bảng liên kết; field nằm chung một bảng.
  • _inherits = {'ten.model': 'field_id'} (có "s"): uỷ quyền (delegation). Hai model có hai bảng riêng, nối nhau qua một Many2one; model con mượn field của model cha nhưng dữ liệu ở hai nơi.

Cách nhớ: _inherit là "tôi là một dạng của nó" (kế thừa kiểu OOP); _inherits là "tôi có một cái đó và mượn đồ của nó" (kết hợp qua liên kết).

Khi nào dùng _inherits

  • Khi một thực thể là sự mở rộng của thực thể khác nhưng cần bảng riêng. User là một partner có thêm khả năng đăng nhập; sản phẩm bán (product.product) uỷ quyền sang mẫu sản phẩm (product.template). Dùng _inherits để không chép lại toàn bộ field.
  • Khi muốn tạo con là tự động có cha. create trên model con với các field của cha sẽ tự tạo bản ghi cha — tiện và nhất quán.

Vài lưu ý thực chiến:

  • Field liên kết phải required=True (mỗi user phải có một partner). Thường đặt ondelete='restrict' hoặc cascade tuỳ ngữ nghĩa.
  • Xoá con không tự xoá cha (như thấy: xoá user, partner còn) — cha có thể được chia sẻ hoặc tồn tại độc lập. Nếu muốn dọn cha, phải làm tay.
  • Chỉ uỷ quyền được field, không uỷ quyền method theo cách tự động như field — muốn gọi method của cha thì qua record.partner_id.method().

Ba ý mang về

  1. _inherits = {'model_cha': 'field_id'} nối model con vào model cha qua một Many2one; con mượn toàn bộ field của cha (đọc/ghi xuyên qua), nhưng dữ liệu nằm ở hai bảng riêng. Đã chứng minh: cột name không ở res_users mà ở res_partner.
  2. Tạo con tự sinh cha: create một user với name/email làm số partner tăng 31→32; ghi u.name đổi luôn partner.name. Xoá con không tự xoá cha.
  3. Đừng nhầm với _inherit (không "s"): _inherit trộn/mở rộng trong cùng bảng; _inherits uỷ quyền qua liên kết giữa hai bảng. "Là một" vs "có một".

Ta đã đi hết các kiểu kế thừa model. Lần sau bước vào chỗ dễ sai nhất khi tuỳ biến hành vi model: Phần sau nói về ghi đè create/write/unlink đúng cách — gọi super() ở đâu, xử lý batch ra sao, và những cái bẫy khiến dữ liệu sai âm thầm.