Ta đã gặp QWeb thoáng qua ở bài kanban — cái template t-name="card" vẽ mỗi bản ghi thành một thẻ. Nhưng QWeb lớn hơn thế nhiều: nó là ngôn ngữ template chung của Odoo, dùng cho kanban, báo cáo PDF, email, và cả website. Nắm các directive t-* cốt lõi là bạn dựng được template cho mọi thứ đó. Bài này viết một template QWeb thật, render nó bằng ir.qweb, và xem HTML nó sinh ra.
Các directive t-* cốt lõi
Một template QWeb là HTML thường, cộng các thuộc tính bắt đầu bằng t-:
<template id="the_report_demo">
<div class="bao-cao-the">
<t t-set="tong" t-value="0"/> <!-- biến cục bộ -->
<t t-foreach="cards" t-as="c" t-key="c.id"> <!-- lặp -->
<div t-attf-class="the-item #{c.diem >= 100 and 'vip' or 'thuong'}">
<span class="ten"><t t-esc="c.name"/></span> <!-- chèn text -->
<t t-if="c.partner_id"> <!-- điều kiện -->
<span>— <t t-esc="c.partner_id.name"/></span>
</t>
<t t-else=""><span>— chưa gán khách</span></t>
<span><t t-esc="c.diem"/> điểm</span>
</div>
<t t-set="tong" t-value="tong + c.diem"/> <!-- cộng dồn -->
</t>
<div>Tổng điểm: <t t-esc="tong"/></div>
</div>
</template>

Hình 1: Template QWeb với đủ directive cốt lõi. t-foreach/t-as/t-key lặp qua danh sách. t-esc chèn giá trị (đã escape an toàn). t-if/t-else rẽ nhánh. t-set/t-value đặt/tính biến (ở đây cộng dồn tong). t-attf-class dựng class động bằng #{...}. Đây là những viên gạch của mọi template Odoo.
Render thật bằng ir.qweb
QWeb không phải lý thuyết — mình render template trên bằng ir.qweb với 4 thẻ thật:
cards = env['quan.the.thanh.vien'].search([], order='diem desc')
html = env['ir.qweb']._render('quan_ca_phe.the_report_demo', {'cards': cards})
_render(template, values) trả về chuỗi HTML. Đây là HTML thật nó sinh ra, hiển thị lại:

Hình 2: HTML thật do QWeb render. t-foreach cho ra 4 dòng (sắp giảm theo điểm). t-attf-class tô viền xanh cho thẻ VIP (≥100: VIP-0003, VIP-0001) và viền xám cho thẻ thường. t-if hiện "Nguyễn Văn An" ở VIP-0001 (có khách), t-else hiện "chưa gán khách" ở các thẻ còn lại. t-set cộng dồn ra Tổng điểm: 385 (150+150+85+0). Mọi directive chạy đúng.
Bảng directive hay dùng
t-esc="expr": chèn giá trị, escape HTML (an toàn, chống XSS). Dùng cho text thường.t-out="expr": chèn giá trị cho phép HTML (dùng khi nội dung là markup tin cậy, ví dụ field Html).t-if/t-elif/t-else: rẽ nhánh, ẩn/hiện cả khối.t-foreach="list" t-as="x": lặp; thêmt-keyđể React/OWL nhận diện phần tử (bắt buộc trong kanban).t-set="ten" t-value="expr": đặt biến cục bộ; có thể tính toán (cộng dồn, gọi hàm).t-att-X="expr"/t-attf-X="...#{expr}...": đặt thuộc tính động (giá trị / chuỗi nội suy).t-call="template": gọi (nhúng) một template khác — tách template lớn thành phần dùng lại.t-field="record.field": render field của bản ghi theo định dạng chuẩn (tiền có đơn vị, ngày theo locale) — dùng nhiều trong báo cáo.
QWeb dùng ở đâu
Cùng một cú pháp QWeb phục vụ nhiều nơi:
- Kanban view (đã gặp): mỗi bản ghi một thẻ.
- Báo cáo PDF (
ir.actions.report): template QWeb → HTML → PDF qua wkhtmltopdf. - Email template: thân email động theo bản ghi.
- Website/Portal: trang web dựng bằng QWeb.
Học QWeb một lần, dùng được ở cả bốn.
Vài lưu ý Odoo 19
t-escvst-out: mặc định dùngt-esc(an toàn). Chỉt-outkhi chắc chắn nội dung là HTML tin cậy — nếu không là mở cửa XSS._rendertrả chuỗi (str/bytes); đây là điểm hay dùng để test template hoặc sinh HTML ngoài luồng chuẩn.t-keybắt buộc trongt-foreachcủa kanban (OWL cần) — bỏ qua thì cảnh báo/hỏng cập nhật.- Biến
t-setcó phạm vi cục bộ:tongcộng dồn trong vòng lặp hoạt động vì QWeb backend xử lý tuần tự. - Template là
ir.ui.viewtypeqweb— khai bằng<template id="...">(đường tắt của một record view qweb).
Ba ý mang về
- QWeb là ngôn ngữ template chung của Odoo (kanban, báo cáo, email, web); các directive cốt lõi:
t-foreach/t-as/t-key,t-if/t-else,t-esc/t-out,t-set/t-value,t-att/t-attf,t-call,t-field. - Render thật bằng
env['ir.qweb']._render(template, values)trả chuỗi HTML — đã chứng minh: template lặp 4 thẻ, tô viền vip/thường theo điểm, hiện/ẩn tên khách, cộng tổng 385. t-escescape an toàn (mặc định),t-outcho HTML tin cậy;t-keybắt buộc trong kanban;t-fieldrender field theo định dạng chuẩn — rất hợp cho báo cáo.
Nói tới template và view, còn một kỹ thuật nền tảng để sửa view có sẵn mà không chép lại: Phần sau mổ xẻ view inheritance — dùng xpath và position để chèn/sửa/xoá phần tử trong view của module khác, nền tảng của mọi tuỳ biến giao diện Odoo.