Một tuỳ biến cực kỳ hay gặp trong thực chiến: bạn muốn từ form Liên hệ của một khách, bấm một nút là xem ngay thẻ thành viên của họ. Nút đó không có sẵn — form Liên hệ thuộc app Contacts của Odoo. Để thêm nó, bạn ghép hai kỹ thuật đã học: inherit model (thêm method + field đếm vào res.partner) và inherit view (chèn nút vào form qua xpath). Kết quả là một "nút thống kê" (stat button) như những nút "12 Đơn hàng", "3 Cuộc họp" bạn thấy khắp Odoo. Bài này dựng nút đó thật.

Hai lớp: method ở model, nút ở view

# 1) Thêm method vào MODEL của form đó (res.partner)
class ResPartner(models.Model):
    _inherit = 'res.partner'
    so_the_thanh_vien = fields.Integer(compute='_compute_so_the')

    def action_xem_the(self):        # nút gọi tới
        self.ensure_one()
        return {'type': 'ir.actions.act_window', 'res_model': 'quan.the.thanh.vien',
                'view_mode': 'list,form', 'domain': [('partner_id', '=', self.id)]}
<!-- 2) Extension view: chèn nút vào button_box của form Liên hệ -->
<xpath expr="//div[@name='button_box']" position="inside">
  <button type="object" name="action_xem_the"
          class="oe_stat_button" icon="fa-id-card-o"
          invisible="so_the_thanh_vien == 0">
    <field name="so_the_thanh_vien" widget="statinfo" string="Thẻ TV"/>
  </button>
</xpath>

Ảnh chụp mã Python và XML nền tối, phần Python lớp ResPartner kế thừa res chấm partner có field so_the_thanh_vien compute và method action_xem_the trả về một act_window mở model quan chấm the chấm thanh chấm vien lọc theo partner_id bằng id hiện tại, phần XML một record view kế thừa base chấm view_partner_form với xpath trỏ div button_box position inside chèn một button type object name action_xem_the class oe_stat_button icon fa-id-card-o có invisible khi so_the_thanh_vien bằng 0 và bên trong field so_the_thanh_vien widget statinfo string Thẻ TV, dòng cuối ghi oe_stat_button cộng widget statinfo bằng nút thống kê số cộng nhãn ở góc phải form

Hình 1: Hai lớp phối hợp. Model: action_xem_the trả về một act_window mở danh sách thẻ đã lọc theo partner_id của khách đang xem. View: chèn <button type="object" name="action_xem_the"> vào //div[@name='button_box'] (hộp nút thống kê ở đầu form). class="oe_stat_button" + widget="statinfo" biến nó thành nút thống kê (số + nhãn). invisible="so_the_thanh_vien == 0" ẩn nút khi khách chưa có thẻ.

Xem thật: nút trên form Liên hệ

Cài xong, mở form Liên hệ của Nguyễn Văn An — nút "Thẻ TV" xuất hiện trong hộp nút thống kê (form Liên hệ có sẵn nhiều nút nên nút mới nằm trong nhóm "Thêm"):

Ảnh chụp form Liên hệ Odoo của Nguyễn Văn An, hộp nút thống kê ở đầu form gồm Cơ hội, Bán hàng, Đã xuất hóa đơn, Cuộc họp, Nhiệm vụ, Mua hàng, Số lô sê-ri và một nút Thêm đang mở dropdown, trong dropdown hiện nút Thẻ TV kèm số 1 với biểu tượng thẻ và nút Đi đến Trang web, cho thấy nút Thẻ TV do module thêm vào đã nằm trong hộp nút thống kê của form Liên hệ

Hình 2: Nút thật. Nút "Thẻ TV: 1" (icon thẻ, số 1 = so_the_thanh_vien qua statinfo) do module quan_ca_phe thêm vào form Liên hệ của app Contacts. Vì form này đã có nhiều nút thống kê (Cơ hội, Bán hàng, Hóa đơn...), Odoo tự gom nút mới vào dropdown "Thêm". Bấm nút là mở danh sách thẻ của đúng khách này (nhờ domain lọc partner_id).

Cả hai điều quan trọng đều real: form của app khác giờ có nút của module mình, và nút bấm được — mở đúng dữ liệu liên quan.

Vì sao stat button hay dùng

Nút thống kê (oe_stat_button) là mẫu chuẩn của Odoo để liên kết bản ghi với dữ liệu liên quan:

  • Nó hiện một con số (số bản ghi liên quan) — người dùng liếc là biết "khách này có 1 thẻ".
  • Bấm vào mở danh sách đã lọc — không cần tự tìm.
  • Đặt trong button_box để Odoo tự bố trí gọn ở đầu form.

Đây chính là cách form Đơn hàng có nút "Hóa đơn", form Khách có nút "Cơ hội"... — tất cả là stat button gọi act_window với domain.

Đặt nút ở đâu

  • //div[@name='button_box']: hộp nút thống kê (góc phải, đầu form) — cho nút "xem dữ liệu liên quan".
  • //header: đầu form — cho nút quy trình (Xác nhận, Duyệt, Huỷ) đổi trạng thái.
  • Chọn đúng chỗ theo ý nghĩa: thống kê/điều hướng → button_box; hành động quy trình → header.

Vài lưu ý Odoo 19

  • Method của nút phải nằm ở model của form — ở đây là res.partner, nên action_xem_the viết trong ResPartner (inherit res.partner), không phải model thẻ.
  • widget="statinfo" hiển thị field số + string làm nhãn; cần field số (thường computed đếm).
  • icon="fa-..." dùng Font Awesome cho biểu tượng nút thống kê.
  • invisible="field == 0" ẩn nút khi không có dữ liệu — tránh nút "0" vô nghĩa.
  • Nút overflow vào "Thêm" khi button_box đầy là hành vi bình thường của Odoo, không phải lỗi.

Ba ý mang về

  1. Thêm nút vào form app khác = inherit model (method + field) + inherit view (chèn nút qua xpath). Method phải ở model của form đó; nút type="object" gọi method, method trả act_window mở dữ liệu liên quan (lọc bằng domain).
  2. Stat button (class="oe_stat_button" + widget="statinfo") đặt trong //div[@name='button_box'] là mẫu chuẩn "liên kết bản ghi ↔ dữ liệu liên quan": hiện số, bấm mở danh sách đã lọc. Đã chứng minh: nút "Thẻ TV: 1" trên form Liên hệ mở thẻ của đúng khách.
  3. Chọn chỗ theo ý nghĩa: button_box cho thống kê/điều hướng, header cho nút quy trình; ẩn nút rỗng bằng invisible; nút mới có thể gom vào dropdown "Thêm" khi hộp đầy.

Xong nhóm bài tuỳ biến view. Lần sau tới kỹ thuật nâng cao nhất về view — sinh view bằng code lúc chạy: Phần sau mổ xẻ view động qua get_view (trước là fields_view_get) — cách một model tự sửa cấu trúc view của mình lúc chạy, cho những trường hợp XML tĩnh không đủ.