Một khối trên trang website hiển thị "Top thành viên" hay "Sản phẩm mới nhất". Nếu bạn gõ cứng tên và số vào HTML, hôm sau dữ liệu đổi mà trang vẫn hiện số cũ — nội dung tĩnh. Snippet động thì khác: nó đọc thẳng model, nên mỗi lần tải trang là dữ liệu mới nhất. Bài này dựng một snippet "Bảng điểm sống" đọc quan.the.thanh.vien trực tiếp, rồi chứng minh nó sống bằng cách đổi một con số trong CSDL và tải lại — cùng nhìn website.snippet.filter, cơ chế chuẩn mà builder dùng.

Snippet động = QWeb truy vấn model khi render

Cách đơn giản và thật nhất: một template QWeb (một <section class="s_..."> — chính là hình dạng của mọi snippet) mà truy vấn model ngay trong lúc render, bằng request.env. QWeb website cho phép gọi ORM inline qua t-value; Odoo dùng đúng kiểu này trong các snippet lõi.

Ảnh chụp mã nền tối hai phần. Phần trên snippet đọc model sống QWeb cộng request env: template id s_bang_diem_song name Bảng điểm sống, section class s_bang_diem_song data-snippet s_bang_diem_song, div container, t-set the_list t-value request env quan the thanh vien sudo search rỗng order diem desc chú thích truy vấn model ngay khi render trang, t-foreach the_list t-as the, div card với div t-esc the name và div t-esc the diem chú thích giá trị sống. Ghi chú mỗi lần tải trang t-value chạy lại search dữ liệu mới nhất khác nội dung tĩnh gõ cứng số sửa model là trang tự đổi. Phần dưới cách chuẩn của builder website snippet filter: class WebsiteSnippetFilter model website snippet filter với field_names cột hiển thị, limit 1 tới 16 bản ghi, hàm _render template_key limit search_domain builder gọi hàm này để đổ bản ghi vào s_dynamic_snippet

Hình 1: Snippet (trên) đặt the_list bằng request.env['quan.the.thanh.vien'].sudo().search([], order='diem desc') ngay trong template, rồi t-foreach vẽ từng thẻ. Vì t-value chạy lại mỗi lần render, dữ liệu luôn mới. Phần dưới là model website.snippet.filter — cách builder tích hợp sẵn (mục cuối bài).

Chứng minh "sống": before và after

Đây là điểm mấu chốt. Lần tải đầu, snippet hiển thị bốn thẻ theo điểm hiện tại:

Ảnh chụp trang website thật trên Odoo 19 tiêu đề Bảng điểm thành viên dữ liệu sống. Bốn thẻ card: VIP-0001 hạng bac 150 điểm, VIP-0003 hạng vang 150 điểm, VIP-0002 hạng bac 85 điểm, VIP-0004 hạng bac 0 điểm. Dòng dưới ghi Cập nhật lần tải 4 thẻ tổng 385 điểm

Hình 2: Lần tải thứ nhất — VIP-0001 (150), VIP-0003 (150), VIP-0002 (85), VIP-0004 (0, đứng cuối). Tổng 385 điểm.

Giờ tôi đổi điểm VIP-0004 từ 0 lên 999 trong CSDL (qua ORM, không đụng gì tới trang), rồi tải lại cùng URL đó:

Ảnh chụp cùng trang website thật trên Odoo 19 sau khi đổi dữ liệu. Bốn thẻ card giờ là: VIP-0004 hạng bac 999 điểm đứng đầu, VIP-0001 hạng bac 150 điểm, VIP-0003 hạng vang 150 điểm, VIP-0002 hạng bac 85 điểm. Dòng dưới ghi Cập nhật lần tải 4 thẻ tổng 1384 điểm

Hình 3: Tải lại cùng trang sau khi đổi CSDL — VIP-0004 nhảy lên 999 và đứng đầu (vì sắp theo diem desc), tổng thành 1384. Không sửa một dòng HTML nào; snippet tự truy vấn lại và sắp xếp theo dữ liệu mới. Đó là "sống".

Nếu là snippet tĩnh (gõ cứng "0"), trang vẫn hiện 0. Khác biệt nằm ở chỗ template hỏi model thay vì nhớ số.

Lưu ý khi dựng snippet động thủ công

  • sudo() có kiểm soát. Trang công khai chạy bằng Public user; sudo() để đọc, nhưng chỉ trả ra dữ liệu được phép công khai. Đừng vô tình lộ field nhạy cảm.
  • Giới hạn số bản ghi. search(..., limit=N) — đừng để một snippet search([]) kéo hàng nghìn bản ghi làm chậm trang. (Không phải ngẫu nhiên website.snippet.filter chặn limit trong khoảng 1–16.)
  • Cẩn thận cache. Odoo/trình duyệt có thể cache trang; khi test "sống" nhớ tải lại sạch cache. Với dữ liệu thay đổi liên tục, cân nhắc header cache phù hợp.
  • Query nặng trong template làm chậm render. Truy vấn phức tạp nên tính ở controller/model rồi truyền vào, hơn là nhồi hết vào t-value.

Cách chuẩn của builder: website.snippet.filter

Cách trên là "tự làm", đủ cho một khối cố định. Nhưng Odoo có hẳn một cơ chế để người quản trị tự cấu hình snippet động ngay trong trình dựng web, không cần code: website.snippet.filter.

Nhìn lõi: model này giữ field_names (những cột hiển thị), limit (1–16 bản ghi), và trỏ tới một ir.filter hoặc ir.actions.server để lấy bản ghi từ bất kỳ model nào. Nó có hàm _render(template_key, limit, search_domain, ...) mà snippet s_dynamic_snippet của website gọi để đổ dữ liệu vào. Nhờ vậy người dùng kéo snippet "Dynamic Snippet", chọn model/bộ lọc/số lượng trong bảng tùy chọn, và có ngay một khối động — dữ liệu do bạn khai một website.snippet.filter cung cấp.

Chọn cách nào: tự viết QWeb khi khối cố định, gắn liền module (như bảng điểm này); website.snippet.filter + s_dynamic_snippet khi muốn người vận hành tự đổi model/bộ lọc/kiểu hiển thị bằng chuột.

Ba ý mang về

  1. Snippet động đọc model khi render — một template QWeb (<section class="s_...">) đặt t-value="request.env[...].search(...)" rồi t-foreach; mỗi lần tải trang chạy lại truy vấn nên dữ liệu luôn mới (khác snippet tĩnh gõ cứng).
  2. "Sống" nghĩa là đổi model → trang tự đổi: đổi điểm trong CSDL rồi tải lại cùng URL, snippet sắp lại và hiện số mới — không sửa HTML.
  3. Builder có website.snippet.filter (field_names, limit 1–16, _render) để người quản trị tự cấu hình snippet động qua s_dynamic_snippet; tự viết QWeb cho khối cố định, dùng filter cho khối người dùng tự chỉnh.

Hết mảng website/controller, phần sau ta bước sang một khu chuyên biệt và rất hay dùng: Phần sau mổ xẻ eCommerce hook can thiệp giá sản phẩm — móc vào cách Odoo tính giá để áp khuyến mãi, giá theo nhóm khách, hay logic giá tùy biến của riêng bạn.