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>

Ảnh chụp mã template QWeb nền tối, thẻ template id the_report_demo chứa div, một t-set đặt biến tong bằng 0, một t-foreach lặp cards với t-as c và t-key c chấm id, bên trong div dùng t-attf-class ghép the-item với vip khi diem lớn hơn hoặc bằng 100 hoặc thuong, span ten dùng t-esc c chấm name, một t-if c chấm partner_id hiển thị tên khách bằng t-esc còn t-else hiển thị chưa gán khách, span điểm dùng t-esc c chấm diem, sau vòng lặp một t-set cộng dồn tong cộng c chấm diem, cuối cùng div tổng điểm dùng t-esc tong, và dòng chú thích render thật bằng env ir qweb render

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:

Ảnh chụp kết quả HTML do QWeb sinh ra, một bảng báo cáo bốn dòng thẻ, dòng VIP-0003 150 điểm và dòng VIP-0001 Nguyễn Văn An 150 điểm có viền trái xanh lá vì thuộc nhóm vip từ 100 điểm, dòng VIP-0002 85 điểm và VIP-0004 0 điểm có viền trái xám vì thuộc nhóm thường, các dòng chưa gán khách hiển thị chữ nghiêng mờ chưa gán khách còn VIP-0001 hiển thị tên Nguyễn Văn An, chân bảng ghi Tổng điểm 385

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êm t-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-esc vs t-out: mặc định dùng t-esc (an toàn). Chỉ t-out khi chắc chắn nội dung là HTML tin cậy — nếu không là mở cửa XSS.
  • _render trả chuỗi (str/bytes); đây là điểm hay dùng để test template hoặc sinh HTML ngoài luồng chuẩn.
  • t-key bắt buộc trong t-foreach của kanban (OWL cần) — bỏ qua thì cảnh báo/hỏng cập nhật.
  • Biến t-set có phạm vi cục bộ: tong cộng dồn trong vòng lặp hoạt động vì QWeb backend xử lý tuần tự.
  • Template là ir.ui.view type qweb — khai bằng <template id="..."> (đường tắt của một record view qweb).

Ba ý mang về

  1. 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.
  2. 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.
  3. t-esc escape an toàn (mặc định), t-out cho HTML tin cậy; t-key bắt buộc trong kanban; t-field render 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.