Ta đã gặp QWeb ở hai nơi rất khác nhau: template của OWL (chạy trên trình duyệt, có t-on, component) và template của report (render ra PDF). Còn một chỗ thứ ba: QWeb website — render HTML phía server cho một trang web công khai. Cùng cú pháp t-foreach, t-esc, t-if, nhưng chạy ở nơi khác và cho ra thứ khác. Bài này dựng một trang thật để phân biệt rạch ròi.
Ba loại QWeb
| Chạy ở đâu | Ra cái gì | Đặc trưng | |
|---|---|---|---|
| OWL | trình duyệt | DOM tương tác | t-on-click, component, phản ứng |
| report | server | web.external_layout, khổ giấy |
|
| website | server | trang HTML | website.layout, controller type='http' |
Cả ba dùng chung các directive nền (t-if, t-foreach, t-esc, t-att-, t-call), nên dễ tưởng là một. Khác biệt cốt lõi: website QWeb render HTML ngay trên server rồi gửi về trình duyệt — không phải app JS, không phải file PDF, mà một trang web bình thường ai cũng xem được.
Controller + template
Một trang web gồm hai mảnh: một controller khai đường dẫn và trả kết quả render, và một template QWeb dựng HTML:

Hình 1: Controller (trên) và template (dưới). @http.route(..., type='http', auth='public', website=True): type='http' trả HTML (khác type='jsonrpc' trả JSON ở bài rpc), auth='public' cho khách vãng lai xem, website=True gắn nó vào hệ thống website. request.render('module.template', {ctx}) biến template thành HTML với dữ liệu truyền vào. Trong template, t-call="website.layout" mượn header/footer của site, t-foreach lặp qua các thẻ, t-esc in giá trị (có escape chống XSS).
Kết quả: một trang web thật
Nâng cấp module, mở /the-cua-quan. Đây là trang thật, có đầy đủ khung website:

Hình 2: Trang /the-cua-quan render thật. Header và footer (logo, menu, mạng xã hội) đến từ website.layout — mình không viết chúng, chỉ t-call là có. Phần giữa là nội dung riêng: tiêu đề và bảng bốn thẻ, sắp theo điểm (VIP-0003 Vàng 150, VIP-0001 Bạc 150, VIP-0002 85, VIP-0004 0), dựng bằng t-foreach. Nhãn hạng ("Vàng"/"Bạc") lấy từ nhãn hiển thị của field selection. Đây là một trang web bình thường — khách không đăng nhập vẫn xem được.
Vài lưu ý
website.layoutcho bạn cả bộ khung site miễn phí — header, menu, footer, responsive. Chỉt-calllà hoà vào giao diện website hiện có, không phải tự dựng.auth='public'mới cho khách vãng lai vào. Để mặc định (auth='user') là bắt đăng nhập — sai ý đồ một trang công khai.t-escescape,t-outcũng escape chuỗi thường (chỉ render HTML khi giá trị là markup) — như đã thấy ở bài OWL, đây là hàng rào XSS. Dữ liệu người dùng nhập thì luôn để QWeb escape.- Đây là server-side, không phản ứng. Trang chụp lại trạng thái lúc render; muốn cập nhật phải tải lại trang (hoặc thêm JS riêng). "Thời gian thực" ở đây nghĩa là mỗi lần mở trang đọc dữ liệu mới, không phải tự đổi khi đang xem.
Ba ý mang về
- QWeb có ba nơi dùng, đừng lẫn: OWL (trình duyệt, tương tác), report (server → PDF), website (server → trang HTML công khai). Cùng directive nền (
t-foreach,t-esc,t-if,t-call) nhưng chạy khác chỗ, ra khác thứ. - Trang web = controller + template. Controller
@http.route(path, type='http', auth='public', website=True)gọirequest.render('module.template', {ctx}); template<template>dùngt-call="website.layout"để có khung site rồi dựng nội dung riêng. website.layoutcho header/footer miễn phí;auth='public'cho khách xem; QWeb escape (t-esc) chống XSS. Trang là server-side, chụp trạng thái lúc render — không tự phản ứng như OWL.
Trong template trên ta gọi t-call="website.layout" để mượn cả một khung. Phần sau đào sâu t-call: tái sử dụng và lồng template QWeb, truyền biến vào template được gọi, và cách nó giúp tách phần dùng chung ra khỏi từng trang.