Bạn muốn thêm một tab "Thẻ thành viên" vào form Liên hệ của Odoo. Form đó thuộc app Contacts của Odoo — bạn không sở hữu nó, và không bao giờ được sửa code lõi (mất hết khi nâng cấp). Vậy làm sao? Câu trả lời là view inheritance: module của bạn khai một view "kế thừa" trỏ vào view gốc, rồi dùng xpath để tìm một phần tử và position để chèn/sửa/xoá quanh nó. Đây là nền tảng của mọi tuỳ biến giao diện trong Odoo. Bài này chỉ ra cơ chế bằng một tab thật được thêm vào form Liên hệ.

Cấu trúc một view kế thừa

Một inherited view là một record ir.ui.view có inherit_id trỏ vào view gốc, và arch chứa các phép sửa:

<record id="view_partner_form_the" model="ir.ui.view">
  <field name="model">res.partner</field>
  <field name="inherit_id" ref="base.view_partner_form"/>  <!-- view GỐC cần sửa -->
  <field name="arch" type="xml">
    <!-- xpath: TÌM phần tử; position: chèn VÀO ĐÂU -->
    <xpath expr="//notebook" position="inside">
      <page string="Thẻ thành viên">
        <group><field name="so_the_thanh_vien"/></group>
      </page>
    </xpath>
  </field>
</record>

Ảnh chụp mã XML nền tối định nghĩa một view kế thừa, record ir chấm ui chấm view có field model res chấm partner, field inherit_id ref base chấm view_partner_form là view gốc cần sửa, field arch chứa một xpath với expr trỏ tới notebook và position inside chèn một page tên Thẻ thành viên chứa group với field so_the_thanh_vien, phần dưới liệt kê các giá trị position gồm inside chèn vào trong, after chèn ngay sau, before chèn ngay trước, replace thay thế, attributes đổi thuộc tính bằng thẻ attribute, và một đường tắt trỏ thẳng field phone position after để chèn field mới

Hình 1: Cơ chế. inherit_id trỏ view gốc (external id của nó). <xpath expr="..."> là biểu thức XPath tìm phần tử trong view gốc (ở đây //notebook = thẻ notebook bất kỳ). position quyết định chèn vào đâu. Có một đường tắt: khỏi xpath, trỏ thẳng <field name="phone" position="after"> để chèn quanh một field cụ thể.

Xem thật: tab được thêm vào form Liên hệ core

View trên nằm trong module quan_ca_phe; nó sửa form Liên hệ của app Contacts (lõi Odoo). Mở một liên hệ, tab "Thẻ thành viên" xuất hiện:

Ảnh chụp form Liên hệ Odoo của Nguyễn Văn An trong app Liên hệ, thanh tab cuối form gồm Liên hệ, Bán hàng và Mua hàng, Hóa đơn, Phân công đối tác, Ghi chú, và tab Thẻ thành viên đang được chọn, nội dung tab hiển thị Số thẻ thành viên 1, phía trên là thông tin liên hệ email điện thoại vị trí công việc Giám đốc và các thẻ Khách miền Bắc VIP, bên phải là khung chatter

Hình 2: Kết quả thật. Form Liên hệ (của app Contacts lõi) giờ có thêm tab "Thẻ thành viên" ở cuối dãy tab — do <xpath expr="//notebook" position="inside"> chèn một <page> vào cuối notebook. Bên trong hiện "Số thẻ thành viên: 1". Module quan_ca_phe không chép lại form Liên hệ; nó chỉ bổ sung — nâng cấp Odoo không mất gì.

Điểm cốt lõi: một module bổ sung vào view của module khác mà không sửa file gốc. Đây là cách hàng nghìn module cộng đồng thêm field, tab, nút vào các app Odoo.

Các giá trị position

  • inside: chèn vào trong (cuối) phần tử tìm được — như thêm page vào notebook.
  • after: chèn ngay sau phần tử — thêm field sau một field có sẵn.
  • before: chèn ngay trước phần tử.
  • replace: thay thế phần tử bằng nội dung mới. Mạnh nhưng dễ vỡ khi view gốc đổi — dùng thận trọng.
  • attributes: đổi thuộc tính của phần tử (không đổi nội dung):
<xpath expr="//field[@name='diem']" position="attributes">
  <attribute name="string">Điểm thưởng</attribute>
  <attribute name="readonly">1</attribute>
</xpath>

Viết xpath cho đúng

XPath là cách "chỉ đường" tới phần tử trong view gốc:

  • //field[@name='phone']: field tên phone ở bất kỳ đâu.
  • //notebook: thẻ notebook đầu tiên.
  • //page[@string='Ghi chú']: page có tiêu đề "Ghi chú".
  • //group[1]: group thứ nhất.

Nguyên tắc: chọn xpath ổn định — trỏ theo name của field (ít đổi) hơn là vị trí số ([3], dễ lệch khi view gốc thêm bớt phần tử). Đường tắt <field name="x" position="..."> thực chất là xpath //field[@name='x'] viết gọn.

Vài lưu ý Odoo 19

  • inherit_id dùng external id của view gốc — tìm qua developer tools hoặc code module đó (base.view_partner_form, sale.view_order_form...).
  • Nhiều inherited view chồng lên nhau được — mỗi module thêm một lớp; chúng áp theo thứ tự cài và priority.
  • replace cả một field mà module khác cũng đụng tới dễ gây xung đột — ưu tiên attributes hoặc after/before.
  • Kế thừa view khác kế thừa model: inherit model thêm field/logic; inherit view thêm/sửa giao diện. Ở đây so_the_thanh_vien là field thêm vào res.partner bằng inherit model, rồi hiện lên form bằng inherit view — hai lớp phối hợp.

Ba ý mang về

  1. View inheritance sửa view module khác mà không chép lại: record ir.ui.view có inherit_id trỏ view gốc, arch chứa <xpath> (tìm) + position (chèn/sửa). Đã chứng minh: thêm tab "Thẻ thành viên" vào form Liên hệ lõi Odoo.
  2. position: inside (vào trong/cuối), after/before (quanh phần tử), replace (thay thế — thận trọng), attributes (đổi thuộc tính qua <attribute>).
  3. Viết xpath ổn định: trỏ theo name field hơn vị trí số; đường tắt <field name="x" position="..."> = //field[@name='x']. Nhiều lớp inherit chồng nhau được.

Còn một sắc thái quan trọng của inheritance: khi nào bạn mở rộng một view, khi nào tạo bản sao mới? Phần sau phân biệt view primary vs extension — hai chế độ kế thừa view cho hai mục đích khác nhau.