Ở bài field widget ta viết một widget hiển thị điểm dạng huy hiệu — trong form. Nhưng một widget field không bị khoá vào form: nó chạy được ở bất kỳ view nào render field, kể cả thẻ Kanban. Bài này gắn một widget JS nhỏ vào từng thẻ trên bảng kanban — một thanh tiến độ cho thấy thẻ còn cách hạng Vàng bao xa.
Widget field dùng lại trong card
Thẻ kanban trong Odoo 19 là một template <t t-name="card"> chứa các <field>. Gắn widget="tên" lên một field trong đó, y như trong form, và mỗi thẻ sẽ render widget đó. Widget là một component OWL bình thường, kế thừa standardFieldProps (đã học ở bài field widget):

Hình 1: JS: DiemProgressField là một field widget như mọi field widget — static props = {...standardFieldProps}, đọc giá trị qua record.data[name], đăng ký vào category "fields" với supportedTypes: ["integer"]. Không có gì "riêng cho kanban". XML: trong <t t-name="card"> của kanban arch, gắn <field name="diem" widget="diem_progress"/> — thế là xong. Cùng một widget dùng được ở form, list, kanban.
Kết quả: mỗi thẻ một thanh tiến độ
Mở bảng kanban thẻ thành viên, mỗi thẻ giờ có thanh tiến độ riêng:

Hình 2: Widget diem_progress render thật trong từng thẻ. VIP-0001 và VIP-0003: 150/150, thanh vàng đầy + "Đạt Vàng ★" (getter mau trả bg-warning khi ≥150). VIP-0002: 85/150, thanh xanh hơn nửa (getter phanTram = round(85/150×100) ≈ 57%). VIP-0004: 0/150, thanh rỗng. Mỗi thẻ đọc diem của chính bản ghi nó và tự tính — vì widget nhận record của thẻ đó, không phải một giá trị chung.
Vì sao widget đọc đúng bản ghi của từng thẻ
Điểm hay: cùng một component, nhưng mỗi thẻ hiển thị số khác nhau. Đó là vì mỗi thẻ tạo một thể hiện (instance) của widget với props.record là bản ghi của thẻ đó. record.data[props.name] luôn lấy diem của đúng thẻ đang render — kanban lo việc truyền đúng record vào từng card. Bạn viết logic một lần cho "một thẻ", framework nhân nó ra cho mọi thẻ.
Vài cách gắn thành phần vào card
<field name="x" widget="ten"/>(như trên) — hiển thị một field bằng widget tự viết. Cách gọn nhất khi thứ bạn muốn vẽ dựa trên một field.<widget name="ten"/>— một view widget không gắn với field cụ thể (đăng ký vào category"view_widgets"), khi bạn cần một mảnh không tương ứng field nào.- Nút hành động —
<button type="object" name="method"/>gọi thẳng method Python của model, hợp cho thao tác nhanh trên card (không cần JS).
Chọn field widget khi mảnh của bạn là cách hiển thị một field; view widget khi nó độc lập với field.
Ba ý mang về
- Field widget viết cho form dùng lại được ngay trong thẻ Kanban — không có API riêng cho kanban. Gắn
<field name="x" widget="ten"/>vào<t t-name="card">là mỗi thẻ render widget đó. - Widget đọc đúng bản ghi của từng thẻ vì mỗi card tạo một instance với
props.recordlà bản ghi của nó;record.data[props.name]lấy giá trị của đúng thẻ. Viết logic cho "một thẻ", framework nhân ra mọi thẻ. - Ba cách gắn thành phần vào card:
field widget(hiển thị một field),<widget>view widget (độc lập field, categoryview_widgets), và<button type="object">(gọi method Python, không cần JS). Chọn theo việc mảnh của bạn có gắn với field hay không.
Widget diem_progress là widget tự viết. Nhưng Odoo có sẵn nhiều widget rất hữu dụng ít người biết. Phần sau mổ many2one_avatar và các biến thể — widget hiển thị một quan hệ many2one kèm ảnh đại diện, gặp khắp nơi trong Odoo mà ít ai để ý cách nó hoạt động.