第十三章 QWeb 模板

篇别:第一篇 基础知识

本章学习目标

  • 熟练使用 t-if / t-foreach / t-set / t-call / t-att 等指令编写可维护模板。
  • 能正确选择 t-out / t-esct-raw,从设计上规避 XSS
  • 理解 子模板复用变量作用域t-sett-call 中的传参)。
  • 能区分 Python 端 QWeb(报表、邮件)与 JS 端 的差异,并指向对应文档。

导读:QWeb 是什么

QWeb 是 Odoo 的 XML 模板语言,广泛用于:QWeb 报表网站页邮件模板、部分 前端动态片段(与 OWL 并存,趋势是前端逐步 OWL 化)。模板在服务端由 Python QWeb 引擎 渲染,或在浏览器由 JavaScript QWeb 渲染(能力集略有不同)。


13.1 模板指令

13.1.1 知识要点

指令 作用
t-if / t-elif / t-else 条件渲染
t-foreach + t-as 循环;t-key(若版本支持)稳定列表项
t-set 定义变量或整块内容
t-esc 转义为文本,防 HTML 注入
t-out 新版本推荐的安全输出(行为以文档为准,可结合 markup)
t-raw 不转义,仅用于可信 HTML
t-call 调用子模板
t-att-* / t-attf-* 动态属性与格式化属性

13.1.2 案例

案例 A:表格循环

<table class="table table-sm">
  <thead><tr><th>书名</th><th>数量</th></tr></thead>
  <tbody>
    <t t-foreach="doc.line_ids" t-as="line">
      <tr>
        <td t-esc="line.book_id.name"/>
        <td t-esc="line.qty"/>
      </tr>
    </t>
  </tbody>
</table>

案例 B:条件块

<t t-if="doc.partner_id">
  <p>借阅人:<span t-esc="doc.partner_id.name"/></p>
</t>
<t t-else="">
  <p>未指定借阅人</p>
</t>

案例 C:动态 class

<div t-attf-class="badge {{ 'bg-success' if doc.state == 'done' else 'bg-secondary' }}">
  <span t-esc="doc.state"/>
</div>

13.1.3 截图占位

图 13-1 报表 PDF:表格与循环

13.1.4 本节练习

  1. 实操:为借阅单报表增加 空列表 时显示「无明细」提示(t-if)。
  2. 简答t-foreachline_indexline_first 等内置变量(若可用)的用途?
  3. 判断t-esct-out 在任何版本行为完全一致。( )

参考答案提示:3. 错,以版本文档为准。


13.2 数据输出与变量(新增)

13.2.1 知识要点

  • 用户生成内容(昵称、评论、富文本)禁止直接 t-raw
  • t-out:在支持的环境中可配合 markup 等安全策略(查阅当前版本)。
  • t-set
    • t-value 为表达式;
    • t-value 时可在标签内放 整块子模板 赋给变量。
  • 作用域:内层 t-set遮蔽 外层;t-call 内联变量规则见 13.3。

13.2.2 案例

<t t-set="title" t-value="doc.name"/>
<h1 t-out="title"/>

<t t-set="warning">
  <p class="text-danger">超期未还</p>
</t>
<t t-out="warning"/>

第二段若 warning 含不可信 HTML,仍须消毒或改 t-esc 纯文本。

13.2.3 截图占位

图 13-2 官方文档 QWeb 安全与输出

13.2.4 本节练习

  1. 改错:用户昵称 user_input 使用 t-raw="user_input" → 如何改?
  2. 简答:邮件客户端打开 HTML 邮件时,与浏览器 XSS 有何相似风险?

参考答案提示:1. t-esct-out + 可信消毒链路。2. 可执行脚本/钓鱼链接风险。


13.3 子模板调用(新增)

13.3.1 知识要点

  • t-call="module.template_id":执行子模板,默认传入当前上下文doccompany 等)。
  • 传参:在 t-call 体内使用 t-set,对子模板相当于 局部变量
  • 复用:页眉/页脚/条款段落多处 t-call,减少复制粘贴。

13.3.2 案例

子模板 my_module.report_header

<div class="header row">
  <div class="col-6" t-if="show_logo">
    <img t-att-src="image_data_uri(company.logo)" style="max-height: 60px;" alt=""/>
  </div>
  <div class="col-6 text-end">
    <strong t-esc="company.name"/>
  </div>
</div>

主模板

<t t-call="my_module.report_header">
  <t t-set="show_logo" t-value="True"/>
</t>

13.3.3 截图占位

图 13-3 主模板与子模板文件树

13.3.4 本节练习

  1. 实操:抽离 页脚(页码、打印时间)为子模板,被 两个报表 t-call
  2. 简答:子模板能否访问主模板未 t-set 的变量?

参考答案提示:2. 可访问渲染上下文中已有对象(如 company),视 t-call 作用域规则而定,以文档为准。


13.4 Python 与 JavaScript 中的 QWeb(新增)

13.4.1 知识要点

场景 引擎 备注
报表 PDF/HTML Python QWeb ir.actions.report_render 相关 API
邮件模板 Python QWeb 动态字段 object
网站老片段 / 部分 legacy JS QWeb 与 OWL 模板语法可能不同步

后端渲染常传入:docs/docidsdoccompanyusertimeformat_* 等辅助对象(以报表 render 上下文为准)。

13.4.2 案例(概念)

# 伪代码:在模型或报表逻辑中触发渲染,具体 API 见 ir.actions.report
# report = self.env['ir.actions.report']._render_qweb_pdf(report_ref, res_ids=doc.ids)

13.4.3 截图占位

图 13-4 邮件模板代码视图

13.4.4 本节练习

  1. 简答:为何邮件模板也要防止 XSS?
  2. 拓展:在源码中搜索 render_template,记录一种调用路径(模块名即可)。

13.5 报表排版常用技巧

13.5.1 知识要点

  • 使用 web.external_layout / web.basic_layout 统一页眉页脚(以版本模板为准)。
  • CSS:打印媒体 @media print;避免依赖复杂 JS。
  • 分页:大表格 thead 重复、避免单行切断(CSS page-break-inside)。

13.5.2 案例

<t t-call="web.external_layout">
  <div class="page">
    <h2>借阅收据</h2>
    ...
  </div>
</t>

13.5.3 截图占位

图 13-5 PDF 分页与页眉重复效果

13.5.4 本节练习

  1. 实操:调大某表格字号后检查 分页是否截断行
  2. 简答:QWeb 报表中嵌入 外链图片 的利弊?

本章综合练习

  1. 实现:借阅收据 PDF:公司抬头借阅人明细表页脚页码t-call 复用页脚)。
  2. 对比:同一批借阅数据,用 列表视图导出QWeb 报表 各适合什么业务角色?
  3. 安全审计:标出你项目某邮件模板中所有 t-raw,分类为「可信 / 需整改」。
  4. 阅读:官方 QWeb 文档中关于 deprecated 指令(若有)一条。

本章对应白皮书目录:第十三章 QWeb 模板。