Mọi component, widget, patch, service ta viết suốt loạt này đều phải nằm trong một asset bundle thì web client mới nạp. Ta cứ khai vào web.assets_backend như phản xạ. Bài này giải thích vì sao đúng chỗ đó — và cho thấy hậu quả rất khó chịu khi khai nhầm: code im lặng không chạy, không một dòng lỗi.
Bundle là gì
Odoo không nạp từng file JS/CSS riêng lẻ. Nó gom chúng thành các bundle — mỗi bundle là một gói lớn được biên dịch, nén, và phục vụ như một tài nguyên. Quan trọng: mỗi loại trang chỉ nạp những bundle của nó. Web client (/odoo, sau đăng nhập) nạp web.assets_backend; trang website công khai nạp web.assets_frontend; PDF report nạp web.report_assets_common.
Khai file vào bundle nào là việc của __manifest__.py:

Hình 1: Khối assets thật của module demo. Mỗi khoá là tên một bundle, giá trị là danh sách đường dẫn file (nhận cả glob .../src/**/*). File OWL của web client — dashboard.js, dashboard.xml — nằm trong web.assets_backend. Thứ tự trong list chính là thứ tự nạp, quan trọng khi một file phụ thuộc file khác.
Bản đồ các bundle chính

Hình 2: Vài bundle hay gặp. Điều cần khắc cốt: mọi thứ của web client (OWL, registry, client action, widget) vào web.assets_backend. web.assets_frontend là cho website công khai — hoàn toàn tách biệt, không nạp trong /odoo.
Đặt nhầm bundle = code chết lặng
Đây là phần đáng nhớ nhất. Mình tạo một file bundletest.js chỉ làm đúng một việc: console.log(...) khi được nạp. Rồi thử khai nó ở hai bundle khác nhau và mở web client /odoo, bắt console:

Hình 3: Cùng một file, hai bundle, cùng mở /odoo (bắt console bằng Playwright). Trong web.assets_backend: console in ra dòng log — file được nạp. Chuyển sang web.assets_frontend: console rỗng — file không được nạp trong web client, và không có lỗi, không cảnh báo nào cả. Đây là cái bẫy: code không chạy nhưng mọi thứ trông "bình thường", bạn ngồi debug logic trong khi vấn đề chỉ là sai bundle.
Đúng như các bài OWL trước cảnh báo về "lỗi chỉ ở trình duyệt", nhưng còn tệ hơn: đây không phải lỗi, mà là im lặng. File nằm trong một bundle không được trang này nạp, nên nó đơn giản không tồn tại với web client. Không stack trace để lần.
Vài điều thực dụng
- OWL/registry của web client → luôn
web.assets_backend. Đây là 95% trường hợp bạn gặp khi mở rộng giao diện quản trị. - Template OWL (
.xml) cũng khai vào bundle, cùng chỗ với JS dùng nó. Quên khai file.xmllà component không có template và vỡ khi render (cái này có lỗi, khác với đặt nhầm bundle). - Thứ tự khai = thứ tự nạp. File
b.jscần cái gìa.jsđịnh nghĩa thìa.jsphải đứng trước. Có sẵn thao tác('after', path),('before', path),('replace', ...),('remove', ...)khi bạn cần chèn vào giữa hay gỡ file của module khác. - Sửa danh sách asset là phải
-umodule để manifest được đọc lại; sửa nội dung file thì cũng cần nâng cấp/khởi động lại để bundle biên dịch lại.
Ba ý mang về
- Odoo gom JS/CSS/template thành bundle; mỗi trang chỉ nạp bundle của nó. Web client nạp
web.assets_backend, website nạpweb.assets_frontend, PDF report nạpweb.report_assets_common. - Mọi thứ của web client (OWL, registry, client action, widget) khai vào
web.assets_backendtrong'assets'của__manifest__.py. Thứ tự trong list là thứ tự nạp. - Đặt nhầm bundle = code chết lặng lẽ — không chạy, không lỗi, không cảnh báo. Khi một mảnh JS "không có tác dụng gì" mà console sạch, hãy kiểm bundle trước khi đi lục logic.
Trong web.assets_backend ta khai cả file .scss. Phần sau đi vào SCSS trong Odoo: dùng biến, ghi đè biến theme của Odoo để đổi màu/khoảng cách toàn hệ thống, và cách SCSS của bạn hoà vào bộ style sẵn có.