Mở bất kỳ form nào trong Odoo — một liên hệ, một sản phẩm — bạn sẽ thấy một dãy nút hình hộp ở góc trên: "3 Cơ hội", "6 Đơn bán", "$12.350.000 Đã xuất hóa đơn". Đó là smart button: mỗi nút vừa hiện một con số tổng hợp, vừa là lối tắt bấm-là-nhảy sang danh sách bản ghi liên quan. Nhìn thì "xịn" nhưng cơ chế rất gọn. Bài này dựng một smart button từ đầu cho model thẻ thành viên trong module demo: nút "Lượt tích điểm" đếm số dòng lịch sử, bấm vào mở đúng danh sách của thẻ đó.
Ba mảnh ghép
Một smart button luôn gồm đúng ba thứ:
- Một field compute giữ con số muốn hiện (ở đây: số dòng lịch sử điểm).
- Một
<button>trong<div name="button_box">ở đầu<sheet>, classoe_stat_button, chứa field đó vớiwidget="statinfo". - Một method (
type="object") trả về một dictir.actions.act_windowđể mở danh sách liên quan.

Hình 1: Ba mảnh ghép. so_dong_lich_su là field compute đếm len(lich_su_ids); nút trong button_box hiện nó qua widget="statinfo"; action_xem_lich_su trả act_window với domain=[('the_id','=', self.id)] để danh sách mở ra chỉ chứa dòng của đúng thẻ này.
Hai điểm đáng chú ý về cú pháp Odoo 19: dùng thẻ <button> với class="oe_stat_button" (không phải widget đặc biệt nào), và view_mode ghi list,form — chữ list chứ không phải tree như bản cũ.
Nút trên form thật
Sau khi thêm code và nâng cấp module, mở form thẻ VIP-0001. Nút xuất hiện ngay ở đầu form với con số thật:

Hình 2: Smart button "Lượt tích điểm" hiện 3 — đúng bằng số dòng lịch sử của thẻ VIP-0001. Con số này là field compute, tự cập nhật khi thêm/bớt dòng lịch sử (nhờ @api.depends('lich_su_ids')). Icon fa-history là biểu tượng Font Awesome gắn qua thuộc tính icon.
Con số không phải gõ tay — nó được tính lại tự động mỗi khi lich_su_ids đổi, vì @api.depends đã khai phụ thuộc vào đúng field đó. Thêm một dòng lịch sử, số nhảy lên 4 ngay.
Bấm vào thì sao
Vì name="action_xem_lich_su" và type="object", bấm nút gọi method cùng tên. Method trả về một act_window có domain lọc theo the_id, nên Odoo mở một danh sách chỉ gồm dòng lịch sử của thẻ đang xem:

Hình 3: Kết quả bấm nút — danh sách lịch sử điểm đã lọc sẵn cho VIP-0001 (3 dòng, tổng 120 điểm). Breadcrumb "Lịch sử điểm — VIP-0001" chính là name mà method đặt. context với default_the_id giúp nếu bấm "Mới" ở đây, dòng tạo ra tự gắn về đúng thẻ này.
Đây là điều làm smart button "thông minh": nó không chỉ hiện số, mà còn đưa người dùng thẳng tới đúng tập dữ liệu đằng sau con số đó, không phải tự đi lọc.
Vài lưu ý thực chiến
statinfocần một field.widget="statinfo"hiển thị giá trị + nhãnstring. Không có field bên trong thì nút trống trơn. Muốn hiện hai dòng số thì đặt hai field.- Nút bị ẩn khi không có gì để xem. Trên form liên hệ, nút "Thẻ TV" của demo có
invisible="so_the_thanh_vien == 0"— khách chưa có thẻ thì giấu nút đi cho gọn. Đây là mẫu rất hay dùng. - Coi chừng button_box quá tải. Form
res.partnerchuẩn đã có nhiều nút sẵn; thêm nút của bạn vào có thể bị dồn vào menu "Thêm ▾". Trên model của chính mình thì button_box trống nên nút luôn hiện — như Hình 2. _computechạy cho mỗi bản ghi hiển thị. Đếm bằnglen(self.lich_su_ids)ổn cho One2many; nhưng nếu đếm quasearch_counttrên bảng lớn và hiện nút đó trong danh sách hàng trăm dòng, hãy để ý số truy vấn.
Ba ý mang về
- Smart button = field compute +
<button class="oe_stat_button">trongbutton_box+ method trảact_window. Ba mảnh ghép, khoảng 20 dòng. widget="statinfo"hiện con số,@api.dependskhiến nó tự cập nhật;icon="fa-..."gắn biểu tượng. Cú pháp Odoo 19:<button>+view_mode="list,form".- Giá trị thật nằm ở method: trả
act_windowvớidomainlọc theo bản ghi hiện tại để bấm nút là mở đúng danh sách liên quan; thêmcontextdefault_...để tạo mới ở đó tự gắn quan hệ.
Nút đã đẹp, giờ ta quay lại chuyện hiển thị field cho tinh tế hơn. Phần sau bàn về widget cho many2many — khi nào dùng tags, khi nào dùng checkboxes, và chúng đổi trải nghiệm nhập liệu thế nào.