Ở bài trước ta thêm một tab vào form Liên hệ bằng view inheritance. Nhưng có một sắc thái quan trọng chưa nói: khi bạn kế thừa một view, bạn đang sửa nó tại chỗ hay tạo một bản mới dựa trên nó? Hai ý định đó tương ứng với hai chế độ: extension và primary. Chọn sai là hoặc vô tình đổi form của cả hệ thống, hoặc không đổi được gì. Bài này phân biệt hai chế độ bằng một form biến thể thật.

Ba trường hợp kế thừa view

<!-- 1) EXTENSION (mặc định khi có inherit_id): SỬA view gốc TẠI CHỖ -->
<field name="inherit_id" ref="base.view_partner_form"/>   <!-- mode ngầm = extension -->

<!-- 2) PRIMARY có inherit_id: TẠO view MỚI dựa trên gốc; gốc KHÔNG đổi -->
<record id="view_the_form_gon" model="ir.ui.view">
  <field name="inherit_id" ref="view_the_form"/>
  <field name="mode">primary</field>                       <!-- ← điểm mấu chốt -->
  <field name="arch" type="xml">
    <xpath expr="//notebook" position="replace"/>          <!-- bỏ tab -->
    <xpath expr="//chatter" position="replace"/>           <!-- bỏ chatter -->
  </field>
</record>

<record id="action_the_gon" model="ir.actions.act_window">
  <field name="view_id" ref="view_the_form_gon"/>          <!-- dùng biến thể -->
</record>

Ảnh chụp mã XML nền tối trình bày ba trường hợp kế thừa view, trường hợp một extension mặc định khi có inherit_id sửa view gốc tại chỗ với chú thích mọi action dùng view gốc đều thấy thay đổi, trường hợp hai primary có inherit_id tạo view mới dựa trên gốc mà gốc không đổi gồm record view_the_form_gon có inherit_id trỏ view_the_form field mode primary là điểm mấu chốt và arch dùng xpath replace bỏ notebook và chatter, kế đó record action_the_gon có view_id trỏ view_the_form_gon để dùng biến thể, trường hợp ba không có inherit_id luôn là primary view độc lập

Hình 1: Ba trường hợp. (1) extension (mặc định khi có inherit_id): các phép sửa áp thẳng vào view gốc — mọi action dùng view gốc đều thấy. (2) primary + inherit_id: tạo một view mới kế thừa từ gốc; view gốc không đổi, biến thể chỉ dùng ở nơi bạn chỉ định. (3) không inherit_id: view độc lập, luôn là primary (đây là view "gốc rễ" như form chính của ta).

Xem thật: form biến thể "gọn", form gốc nguyên vẹn

Mình tạo một view primary kế thừa form chính nhưng bỏ notebook và chatter — làm một màn "nhập nhanh", rồi gắn nó vào một action riêng. Mở cùng thẻ VIP-0001 qua action đó:

Ảnh chụp form Odoo biến thể gọn của thẻ VIP-0001, có header với nút Cộng điểm và statusbar Mới cấp Đang dùng Hết hạn, tiêu đề Mã thẻ VIP-0001, hai cột Thông tin thẻ với Khách hàng Nguyễn Văn An Hạng thẻ Bạc Ngày cấp 3 tháng 9 và cột Điểm và số dư với Điểm tích luỹ 150 phần trăm Số dư 0 đô la Tiền tệ USD Ưu đãi áp dụng, nhưng KHÔNG có các tab Lịch sử điểm Ghi chú và KHÔNG có khung chatter bên phải

Hình 2: Form biến thể (mode primary). Cùng thẻ VIP-0001, nhưng gọn hơn: không còn notebook (tab Lịch sử điểm/Ghi chú) và không có chatter — vì view primary đã replace chúng. Quan trọng nhất: form gốc (mở qua menu chính) vẫn đầy đủ notebook + chatter — biến thể primary không hề đụng tới nó. Hai form, một model, hai action khác nhau.

Nếu mình dùng extension thay vì primary ở đây, việc replace notebook/chatter sẽ xoá chúng khỏi form chính luôn — mọi người mở thẻ đều mất tab và chatter. primary khoanh vùng thay đổi vào đúng biến thể.

Khi nào dùng cái nào

  • Dùng extension (mặc định) khi bạn muốn bổ sung/sửa view có sẵn cho mọi người dùng nó: thêm một field vào form đơn hàng, thêm cột vào list sản phẩm. Đây là 90% trường hợp.
  • Dùng primary khi bạn muốn một biến thể riêng dùng ở một action/ngữ cảnh cụ thể, không ảnh hưởng bản gốc: một form "nhập nhanh", một list rút gọn cho một menu phụ, một form chỉ-đọc cho vai trò khác.
  • primary cũng cần thiết khi kế thừa dây chuyền: nếu bạn muốn kế thừa tiếp từ một view đã là primary (biến thể của biến thể), lớp giữa phải là primary để làm "gốc" cho lớp sau.

Cơ chế bên dưới

  • extension: Odoo gộp arch của view kế thừa vào view gốc khi dựng view cuối cùng. Không có view record "mới" nào được dùng riêng — chỉ có view gốc đã được vá.
  • primary: view kế thừa trở thành một view độc lập có arch riêng (đã gộp từ gốc + phép sửa của nó). Bạn tham chiếu nó bằng view_id trong action, hoặc để nó làm gốc cho các extension khác.
  • Kiểm tra thật: view chính view_the_form có mode=primary (không inherit_id → gốc rễ); view thêm tab partner có mode=extension; biến thể gọn có mode=primary + inherit_id → view mới, gốc không đổi.

Vài lưu ý Odoo 19

  • mode mặc định là extension khi có inherit_id, primary khi không có — nên chỉ khai mode=primary tường minh cho trường hợp (2).
  • Biến thể primary phải được trỏ tới (qua view_id của action) mới dùng được — khác extension tự động áp.
  • Đừng lạm dụng primary: mỗi biến thể là một view phải bảo trì. Nếu chỉ cần ẩn/hiện theo điều kiện, dùng invisible/groups trên cùng một form gọn hơn.
  • replace trong primary an toàn hơn trong extension vì phạm vi bị khoanh vào biến thể — nhưng vẫn phải cẩn thận với các extension khác cùng nhắm view gốc.

Ba ý mang về

  1. extension (mặc định) sửa view gốc tại chỗ — mọi nơi dùng view đó đều thấy; primary tạo một view mới dựa trên gốc mà gốc không đổi, chỉ dùng ở nơi được trỏ tới.
  2. Đã chứng minh: view primary kế thừa form chính, replace notebook + chatter → một form "gọn" cho action riêng; form gốc vẫn đầy đủ. Kiểm chứng mode: gốc=primary (không inherit), tab partner=extension, biến thể=primary+inherit.
  3. Chọn: extension để bổ sung cho mọi người (90% ca); primary cho biến thể riêng theo action, hoặc làm gốc cho kế thừa dây chuyền. mode mặc định theo có/không inherit_id.

Nói tới sửa view, có một thao tác tinh tế hay cần: di chuyển hay thay một field đã có sang chỗ khác. Phần sau đi sâu thay thế và di chuyển field trong view — dùng position="move", replace, và các mẹo tránh vỡ view khi làm vậy.