Ở hai phần trước controller của ta trả JSON. Nhưng nhiều lúc bạn cần trả về cả một trang web hoàn chỉnh — có header, footer, menu, đúng giao diện website của công ty: trang bảng xếp hạng, trang tra cứu, một landing page động. Odoo làm việc này bằng bộ đôi request.render + template QWeb. Bài này dựng thật một trang "bảng vàng thành viên" từ controller, cho nó mượn giao diện website của Odoo, rồi chụp trang thật đang chạy để thấy kết quả.

request.render: hàm trả về một trang

Thay vì trả chuỗi HTML tự viết, controller gọi request.render('module.template_id', {context}): đối số đầu là external id của một template QWeb, đối số sau là dict dữ liệu truyền vào template. Odoo render template thành HTML đầy đủ và trả về. Route đặt website=True để trang hoà vào hệ thống website (đa ngôn ngữ, theme, chỉnh sửa được sau này).

Ảnh chụp mã nền tối gồm hai phần. Phần controller: class BangVangController kế thừa http Controller có route http quan_ca_phe bang_vang type http auth public website True, hàm bang_vang lấy danh sách thẻ từ model quan the thanh vien sudo search sắp theo diem giảm dần, rồi return request render với external id quan_ca_phe bang_vang_template và dict gồm the_list và tong_diem bằng tổng mapped diem. Phần template QWeb: thẻ template id bang_vang_template name Bảng vàng thành viên chứa t t-call website layout để mượn header footer website, bên trong div container có h1 Bảng vàng thành viên, đoạn p Tổng cộng t-esc len the_list thẻ t-esc tong_diem điểm, và bảng table table-striped với tr t-foreach the_list t-as the lặp từng thẻ in td t-esc the name và td t-esc the diem

Hình 1: Controller (trên) gọi request.render với external id của template và dữ liệu. Template QWeb (dưới) bắt đầu bằng <t t-call="website.layout"> — mượn nguyên bộ khung website — rồi đổ dữ liệu ra bằng t-esc/t-foreach. Template phải nằm trong file XML khai trong data của manifest.

Đây là mấu chốt để trang trông "thuộc về" website chứ không phải một trang HTML trơ trọi. <t t-call="website.layout"> gọi tới template khung của module website; mọi thứ bạn đặt bên trong nó được nhét vào phần thân, còn header (logo, menu, giỏ hàng, nút Sign in) và footer (liên kết, mạng xã hội) do website.layout tự lo. Nếu không cần bộ khung website đầy đủ mà chỉ cần khung tối giản, có web.frontend_layout hoặc web.layout.

Chạy thật, trang hiện ra như một trang website đúng nghĩa:

Ảnh chụp trang web thật render từ controller trên Odoo 19. Trên cùng là header của website layout gồm Your Logo, menu Home Shop dấu cộng, biểu tượng giỏ hàng và kính lúp, số điện thoại, chọn ngôn ngữ English US, nút Sign in và nút Contact Us. Thân trang là nội dung của template: tiêu đề lớn Bảng vàng thành viên, dòng Tổng cộng 4 thẻ 385 điểm, và bảng có cột số thứ tự Tên thẻ Hạng Điểm liệt kê VIP-0003 hạng vang 150 điểm, VIP-0001 hạng bac 150, VIP-0002 hạng bac 85, VIP-0004 hạng bac 0. Dưới cùng là footer website gồm Useful Links, About us, Connect with us

Hình 2: Trang thật tại /quan_ca_phe/bang_vang. Header và footer là của website.layout (Your Logo, menu, Sign in, footer mạng xã hội) — ta không viết chúng. Phần giữa là nội dung template: tiêu đề, dòng "Tổng cộng 4 thẻ, 385 điểm", và bảng 4 thẻ sắp theo điểm giảm dần (VIP-0003 vàng 150 → VIP-0004 0). Dữ liệu thật từ CSDL, đổ ra bằng t-foreach.

Truyền dữ liệu và đổ ra template

Dict thứ hai của request.render chính là "biến" mà template đọc được. Trong template, các directive QWeb hiển thị chúng:

  • t-esc (hoặc t-out) — in một giá trị, có escape HTML (an toàn với dữ liệu người dùng). <t t-esc="the.name"/> in tên thẻ.
  • t-foreach / t-as — lặp qua một recordset hay list. <tr t-foreach="the_list" t-as="the"> sinh một hàng cho mỗi thẻ; trong vòng lặp có sẵn the_index (chỉ số), the_first, the_last.
  • Gọi thẳng hàm Python đơn giản được: t-esc="len(the_list)", t-esc="tong_diem".

Vì the_list là recordset thật, trong template bạn truy cập field như trong code server: the.name, the.diem, the.hang_the. Không cần chuyển sang dict trước.

website=True cho bạn thêm gì

Đặt website=True trên route (thay vì chỉ type='http') kích hoạt tầng website:

  • Đa ngôn ngữ: URL tự nhận tiền tố ngôn ngữ, template dịch được.
  • Theme & chỉnh sửa: trang dùng đúng theme website hiện tại; người quản trị có thể mở trình soạn để sửa (với template phù hợp).
  • request.website: trong controller có sẵn đối tượng website hiện tại (công ty, ngôn ngữ, cấu hình).

Nếu chỉ cần một trang HTML nội bộ không liên quan website (ví dụ trang in, trang kỹ thuật), bỏ website=True và t-call web.layout là đủ nhẹ.

Vài lưu ý

  • Template phải khai trong manifest. File XML chứa <template> phải nằm trong danh sách data của __manifest__.py, và module phải depends website nếu t-call="website.layout" — thiếu là external id không phân giải được lúc cài.
  • t-esc escape, t-out/t-raw không. Với dữ liệu người dùng nhập, luôn t-esc; chỉ dùng t-out với field Html đã sanitize (như field Html ở phần 162).
  • Quyền vẫn quan trọng. Trang auth='public' chạy bằng Public user; ở đây tôi sudo() để đọc danh sách thẻ, và trong thực tế phải cân nhắc kỹ dữ liệu nào được lộ ra công khai.

Ba ý mang về

  1. request.render('module.template_id', {ctx}) biến controller thành trang web — trả về HTML đầy đủ từ một template QWeb, dữ liệu truyền qua dict thứ hai.
  2. <t t-call="website.layout"> mượn header/footer của website để trang trông đúng giao diện; đổ dữ liệu bằng t-esc (có escape) và t-foreach/t-as, truy cập field recordset trực tiếp (the.name).
  3. website=True kích hoạt đa ngôn ngữ, theme, request.website; template phải khai trong data của manifest và module depends website khi dùng website.layout.

Ta vừa dựng trang bằng controller + code. Có một cách khác không cần controller: khai thẳng trang bằng XML. Phần sau làm trang website khai bằng XML — dùng <template> với thuộc tính đặc biệt để Odoo tự tạo route, không phải viết một dòng Python nào.