Suốt các bài qua ta chỉnh từng mảnh: một field widget, một systray, một view kế thừa. Nhưng đôi khi thứ bạn cần không vừa bất kỳ khuôn nào có sẵn — một trang tổng quan, một bảng xếp hạng, một màn hình nhập liệu nhiều bước. Lúc đó bạn cần client action: cả một màn hình do OWL toàn quyền vẽ, mở từ menu như một app thực thụ. Cái "Bảng điều khiển" ta dùng làm demo suốt loạt này chính là một client action — bài này mổ nó ra và dựng một cái mới.
Client action là gì
Odoo có ba loại "action" chính: ir.actions.act_window (mở view của một model — list/form/kanban), ir.actions.report (in báo cáo), và ir.actions.client (chạy một component JS phía client). Loại thứ ba không gắn với model hay view nào — nó chỉ nói "hãy chạy component đăng ký dưới tag này", còn component vẽ gì là tuỳ bạn.
Cơ chế gồm hai nửa khớp nhau qua một tag:

Hình 1: Hai nửa. JS: một component OWL bình thường (dùng useService, useState, onWillStart như mọi component), rồi registry.category("actions").add("tag", Component). XML: một ir.actions.client có tag khớp đúng chuỗi đã đăng ký, cộng một menuitem để mở nó. Tag là sợi dây nối bản ghi action ở CSDL với class JS trên trình duyệt.
Component vẽ gì là tuỳ bạn
Điểm giải phóng của client action: không có ràng buộc view. Component nhận toàn bộ vùng nội dung và vẽ HTML tuỳ ý. Ở đây mình làm một bảng xếp hạng — đọc thẻ qua orm.searchRead (sắp theo diem desc), gắn thứ hạng và huy chương, rồi render thành bảng:
onWillStart(async () => {
const cards = await this.orm.searchRead(
"quan.the.thanh.vien", [], ["name", "diem", "hang_the"],
{ order: "diem desc, name asc" });
this.state.rows = cards.map((c, i) => ({
hang: i + 1, name: c.name, diem: c.diem,
huy: i === 0 ? "🥇" : i === 1 ? "🥈" : i === 2 ? "🥉" : "",
}));
});
Nâng cấp module, mở menu "Bảng xếp hạng". Đây là màn hình thật:

Hình 2: Client action xep_hang chạy thật. Toàn bộ trang — tiêu đề, bảng, huy chương, nhãn "Vàng" — do component vẽ, không đi qua bất kỳ view chuẩn nào. Dữ liệu thật: 4 thẻ sắp theo điểm (VIP-0001 và VIP-0003 cùng 150, rồi VIP-0002 85, VIP-0004 0). Đây là màn hình riêng, mở từ menu như mọi app khác.
act_window hay client action?
Đừng vội dùng client action cho mọi thứ. Ranh giới:
- Dùng
act_window(list/form/kanban) khi bạn hiển thị/sửa bản ghi của một model theo cách chuẩn. Bạn được miễn phí: tìm kiếm, lọc, phân trang, sắp xếp, phân quyền theo record rule. Viết lại những thứ này bằng tay là phí phạm và dễ sai. - Dùng
client actionkhi màn hình không map một-một với view của một model: trang tổng quan trộn nhiều nguồn, một wizard nhiều bước phức tạp, một biểu đồ tương tác, một bảng xếp hạng như trên.
Bảng xếp hạng này có thể làm bằng list view với default_order, nhưng huy chương và cách trình bày riêng thì client action tự do hơn. Ngược lại, nếu chỉ cần "danh sách thẻ sắp theo điểm", một list view chuẩn là đủ và tốt hơn.
Vài lưu ý:
- Tag phải khớp tuyệt đối giữa
registry.add("tag", ...)và<field name="tag">tag</field>. Lệch một ký tự thì mở action ra màn hình trống, lỗi chỉ hiện ở console. - Component là OWL bình thường — mọi hook, service dùng như thường. Nạp dữ liệu trong
onWillStart. - Không có record rule tự động.
act_windowáp quyền theo dòng của model; client action gọiormthẳng, nên bạn tự chịu trách nhiệm về những gì truy vấn và hiển thị.
Ba ý mang về
- Client action là một màn hình do component OWL toàn quyền vẽ, thuộc loại
ir.actions.client— không gắn model/view nào, mở từ menu như một app. Nối qua một tag:registry.category("actions").add("tag", Component)ở JS và<field name="tag">ở record XML. - Component là OWL bình thường (useService/useState/onWillStart) và vẽ HTML tuỳ ý — hợp cho màn hình không map một-một với view chuẩn (tổng quan, wizard phức tạp, bảng xếp hạng).
- Cân nhắc trước khi chọn.
act_windowcho bạn tìm/lọc/phân trang/phân quyền miễn phí; chỉ dùng client action khi thật sự cần tự do vẽ — và nhớ rằng lúc đó không có record rule tự động, bạn tự lo truy vấn.
Ta đã thêm rất nhiều thứ mới. Phần sau học cách sửa thứ đã có mà không chép đè: dùng patch để vá một component lõi của Odoo một cách an toàn, giữ nguyên phần còn lại.