QWeb 模板

QWeb 是 Odoo 使用的主要模板引擎 2 。它是一种 XML 模板引擎 1 ,主要用于生成 HTML 片段和页面。

模板指令以 t- 为前缀的 XML 属性形式指定,例如用于条件语句的 t-if (参见 条件语句 ),而元素和其他属性会直接渲染。

为了避免元素渲染,还提供了一个占位符元素 <t> ,它会执行其指令但不会生成任何输出:

<t t-if="condition">
    <p>Test</p>
</t>

将导致::

<p>Test</p>

如果 condition 为真,但:

<div t-if="condition">
    <p>Test</p>
</div>

将导致::

<div>
    <p>Test</p>
</div>

数据输出

QWeb 的输出指令 out 会自动对输入进行 HTML 转义,从而在显示用户提供的内容时降低 XSS 风险。

out 接受一个表达式,对其进行求值并将结果注入文档中:

<p><t t-out="value"/></p>

value 设置为 42 时,渲染结果为:

<p>42</p>

更多高级主题(例如注入原始 HTML 等),请参见 高级输出

条件语句

QWeb 提供了一个条件指令 if ,它会对作为属性值提供的表达式进行求值:

<div>
    <t t-if="condition">
        <p>ok</p>
    </t>
</div>

如果条件为真,则渲染该元素:

<div>
    <p>ok</p>
</div>

但如果条件为假,则该元素将从结果中移除:

<div>
</div>

条件渲染适用于承载该指令的元素,而不必是 <t>

<div>
    <p t-if="condition">ok</p>
</div>

结果与之前的示例相同。

额外的条件分支指令 t-elift-else 也可用:

<div>
    <p t-if="user.birthday == today()">Happy birthday!</p>
    <p t-elif="user.login == 'root'">Welcome master!</p>
    <p t-else="">Welcome!</p>
</div>

循环

QWeb 提供了一个迭代指令 foreach ,它接受一个返回集合的表达式,并通过第二个参数 t-as 指定用于表示“当前项”的名称:

<t t-foreach="[1, 2, 3]" t-as="i">
    <p><t t-out="i"/></p>
</t>

将被渲染为:

<p>1</p>
<p>2</p>
<p>3</p>

与条件类似,foreach 适用于承载该指令属性的元素,且

<p t-foreach="[1, 2, 3]" t-as="i">
    <t t-out="i"/>
</p>

与之前的示例等效。

foreach 可以对数组(当前项为当前值)或映射(当前项为当前键)进行迭代。对整数的迭代(等同于在包含 0 且不包含所提供整数的数组上迭代)仍然支持,但已被弃用。

除了通过 t-as 传递的名称外,foreach 还提供了其他一些变量用于各种数据点:

警告

$as 将被替换为传递给 t-as 的名称

$as_all (已弃用)

正在迭代的对象

注解

此变量仅在 JavaScript QWeb 中可用,Python 中不可用。

$as_value

当前迭代值,对于列表和整数与 $as 相同,但对于映射,它提供的是值(而 $as 提供的是键)

$as_index

当前迭代索引(迭代中的第一项索引为 0)

$as_size

如果可用,则为集合的大小

$as_first

当前项是否为迭代中的第一个(等同于 $as_index == 0

$as_last

当前项是否为迭代中的最后一个(等同于 $as_index + 1 == $as_size ),需要迭代对象的大小可用

$as_parity (已弃用)

当前迭代轮次的奇偶性,值为 "even""odd"

$as_even (已弃用)

一个布尔标志,指示当前迭代轮次的索引为偶数

$as_odd (已弃用)

一个布尔标志,指示当前迭代轮次的索引为奇数

这些额外提供的变量以及在 foreach 中创建的所有新变量仅在 foreach 的作用域内可用。如果变量存在于 foreach 上下文之外,则其值会在 foreach 结束时复制到全局上下文中。

<t t-set="existing_variable" t-value="False"/>
<!-- existing_variable now False -->

<p t-foreach="[1, 2, 3]" t-as="i">
    <t t-set="existing_variable" t-value="True"/>
    <t t-set="new_variable" t-value="True"/>
    <!-- existing_variable and new_variable now True -->
</p>

<!-- existing_variable always True -->
<!-- new_variable undefined -->

attributes(属性)

QWeb 可以动态计算属性,并将计算结果设置到输出节点上。这是通过 t-att (属性)指令完成的,该指令有三种不同形式:

t-att-$name

创建一个名为 $name 的属性,属性值会被求值并将结果设置为该属性的值:

<div t-att-a="42"/>

将被渲染为:

<div a="42"></div>
t-attf-$name

与前一种形式相同,但参数是一个 格式化字符串 而非单纯的表达式,通常用于混合字面量和非字面量字符串(例如类名):

<t t-foreach="[1, 2, 3]" t-as="item">
    <li t-attf-class="row {{ (item_index % 2 === 0) ? 'even' : 'odd' }}">
        <t t-out="item"/>
    </li>
</t>

将被渲染为:

<li class="row even">1</li>
<li class="row odd">2</li>
<li class="row even">3</li>

小技巧

格式化字符串有两种等效语法:"plain_text {{code}}" (即 Jinja 风格)和 "plain_text #{code}" (即 Ruby 风格)。

t-att=mapping

如果参数是映射,则每对 (key, value) 会生成一个新的属性及其值:

<div t-att="{'a': 1, 'b': 2}"/>

将被渲染为:

<div a="1" b="2"></div>
t-att=pair

如果参数是一个对(包含两个元素的元组或数组),则对的第一个元素是属性名称,第二个元素是属性值:

<div t-att="['a', 'b']"/>

将被渲染为:

<div a="b"></div>

设置变量

QWeb 允许在模板中创建变量,以缓存计算结果(以便多次使用)、为数据片段提供更清晰的名称等。

这是通过 set 指令完成的,该指令接受要创建的变量名称。设置的值可以通过两种方式提供:

  • 一个包含表达式的 t-value 属性,其求值结果将被设置:

    <t t-set="foo" t-value="2 + 1"/>
    <t t-out="foo"/>
    

    将输出 3

  • 如果没有 t-value 属性,则节点主体会被渲染并设置为变量的值:

    <t t-set="foo">
        <li>ok</li>
    </t>
    <t t-out="foo"/>
    

调用子模板

QWeb 模板可用于顶层渲染,也可以通过 t-call 指令从另一个模板中调用(以避免重复或为模板部分命名):

<t t-call="other-template"/>

这会以父级的执行上下文调用指定的模板,如果 other_template 定义为:

<p><t t-value="var"/></p>

上述调用将被渲染为 <p/> (无内容),但:

<t t-set="var" t-value="1"/>
<t t-call="other-template"/>

将被渲染为 <p>1</p>

然而,这存在一个问题,即内容在 t-call 外部可见。或者,在 call 指令主体中设置的内容会在调用子模板 之前 被求值,并可以修改局部上下文:

<t t-call="other-template">
    <t t-set="var" t-value="1"/>
</t>
<!-- "var" does not exist here -->

call 指令的主体可以任意复杂(不仅限于 set 指令),其渲染形式将在被调用的模板中作为特殊的 0 变量可用:

<div>
    This template was called with content:
    <t t-out="0"/>
</div>

因此被调用:

<t t-call="other-template">
    <em>content</em>
</t>

将导致::

<div>
    This template was called with content:
    <em>content</em>
</div>

高级输出

默认情况下,out 会对需要转义的内容进行 HTML 转义,从而保护系统免受 XSS 攻击

不需要转义的内容将原样注入文档,并可能成为文档实际标记的一部分。

唯一跨平台的“安全”内容是 t-call 的输出,或使用“主体”的 t-set (与 t-valuet-valuef 相对)。

Python

通常您无需过多担心:对于合理的 API,它们会自动生成“安全”内容,并且一切应该透明地工作。

然而,为了更清晰地处理某些情况,以下 API 输出的安全内容在注入模板时默认不会(重新)转义:

  • HTML 字段

  • html_escape()markupsafe.escape() (它们是别名,没有双重转义的风险)。

  • html_sanitize()

  • markupsafe.Markup

    警告

    markupsafe.Markup 是一个不安全的 API,它是一种 断言 ,表明您希望内容是标记安全的,但不一定能检查到这一点,因此应谨慎使用。

  • to_text() 不会将内容标记为安全,但也不会从安全内容中剥离该信息。

强制双重转义

如果内容被标记为安全,但出于某种原因仍需转义(例如打印 HTML 字段的标记),可以将其转换回普通字符串以“剥离”安全标志,例如 Python 中的 str(content) 和 JavaScript 中的 String(content)

注解

由于 Markup 是比 Markup() 更丰富的类型,某些操作会从 Markup() 中剥离安全信息,而不会影响 Markup 。例如,字符串连接( '' + content )在 Python 中会生成一个 Markup ,其中另一个操作数已被正确转义,而在 JavaScript 中会生成一个 String() ,其中另一个操作数在连接之前 被转义。

已弃用的输出指令

esc

out 的别名,原本会对输入进行 HTML 转义。尚未正式弃用,因为 outesc 之间的唯一区别在于后者有些含糊/不准确。

raw

out 的一个版本, 从不 转义其内容。无论是否安全,内容都会原样输出。

15.0 版后已移除: 改用带有 markupsafe.Markup 值的 out

t-raw 已被弃用,因为随着生成内容的代码不断演变,很难追踪它将用于标记,从而导致审查更加复杂并带来更大的风险。

Python

专属指令

资产包

“智能记录”字段格式化

t-field 指令只能在对“智能”记录( browse 方法的结果)执行字段访问( a.b )时使用。它能够根据字段类型自动格式化,并集成到网站的富文本编辑中。

t-options 可用于自定义字段,最常见的选项是 widget ,其他选项则取决于字段或部件。

调试

t-debug

当值为空时,调用内置函数 breakpoint() ,该函数通常会调用调试器(默认为 pdb )。

其行为可以通过 PYTHONBREAKPOINTsys.breakpointhook() 配置。

辅助工具

基于请求

大多数 Python 端对 QWeb 的使用是在控制器中(以及在 HTTP 请求期间),在这种情况下,存储在数据库中的模板(如 视图 )可以通过调用 odoo.http.HttpRequest.render() 轻松渲染:

response = http.request.render('my-template', {
    'context_value': 42
})

这会自动生成一个 Response 对象,可以从控制器返回(或进一步定制以满足需求)。

基于视图

比前面的辅助工具更深层次的是 ir.qweb 上的 _render 方法(使用数据库)和公共模块方法 render (不使用数据库):

_render(id[, values])

通过数据库 ID 或 外部 ID 渲染 QWeb 视图/模板。模板会自动从 ir.qweb 记录中加载。

_prepare_environment 方法在渲染上下文中设置了一些默认值。http_routingwebsite 插件也会设置它们所需的默认值。您可以使用 minimal_qcontext=False 选项来避免这些默认值,就像公共方法 render 那样:

request

当前的 Request 对象(如果存在)

debug

当前请求(如果存在)是否处于 debug 模式

quote_plus

URL 编码实用函数

json

相应的标准库模块

time

相应的标准库模块

datetime

相应的标准库模块

relativedelta

参见模块

keep_query

keep_query 辅助函数

参数
  • values – 传递给 QWeb 用于渲染的上下文值

  • engine (str) – 用于渲染的 Odoo 模型名称,可用于局部扩展或自定义 QWeb(通过基于 ir.qweb 创建带有修改的“新”qweb)

render(template_name, values, load, **options)
load(ref)()

返回 etree 对象,ref

JavaScript

专属指令

定义模板

t-name 指令只能放置在模板文件的顶层(文档根的直接子元素):

<templates>
    <t t-name="template-name">
        <!-- template code -->
    </t>
</templates>

它不接受其他参数,但可以与 <t> 元素或其他任何元素一起使用。如果使用 <t> 元素,<t> 应该只有一个子元素。

模板名称是一个任意字符串,不过当多个模板相关联时(例如被调用的子模板),通常使用点分隔的名称来表示层次关系。

模板继承

模板继承用于以下目的之一:
  • 就地修改现有模板,例如向模板添加信息

由其他模块创建的模板。
  • 从给定的父模板创建一个新模板

模板继承是通过两个指令完成的:
  • t-inherit 是要继承的模板的名称,

  • t-inherit-mode 是继承行为:可以设置为 primary 以从父模板创建一个新的子模板,或者设置为 extension 以就地修改父模板。

还可以指定一个可选的 t-name 指令。如果在主模式下使用,它将是新创建模板的名称;否则,它将作为注释添加到转换后的模板中,以帮助追溯继承关系。

对于继承本身,更改是通过 XPath 指令完成的。有关完整的可用指令集,请参阅 XPATH 文档。

主继承(子模板):

<t t-name="child.template" t-inherit="base.template" t-inherit-mode="primary">
    <xpath expr="//ul" position="inside">
        <li>new element</li>
    </xpath>
</t>

扩展继承(就地转换):

<t t-inherit="base.template" t-inherit-mode="extension">
    <xpath expr="//tr[1]" position="after">
        <tr><td>new cell</td></tr>
    </xpath>
</t>

旧的继承机制(已弃用)

模板继承通过 t-extend 指令完成,该指令以要修改的模板名称作为参数。

当与 t-name 结合使用时,指令 t-extend 将充当主继承;单独使用时则作为扩展继承。

在这两种情况下,更改都是通过任意数量的 t-jquery 子指令完成的:

<t t-extend="base.template">
    <t t-jquery="ul" t-operation="append">
        <li>new element</li>
    </t>
</t>

t-jquery 指令接受一个 CSS 选择器 。此选择器用于扩展模板中,选择将应用指定 t-operation上下文节点

append

节点的主体被追加到上下文节点的末尾(在上下文节点的最后一个子节点之后)

prepend

节点的主体被前置到上下文节点(插入到上下文节点的第一个子节点之前)

before

节点的主体被插入到上下文节点之前

after

节点的主体被插入到上下文节点之后

inner

节点的主体替换上下文节点的子节点

replace

节点的主体用于替换上下文节点本身

attributes

节点的主体应包含任意数量的 attribute 元素,每个元素都有一个 name 属性和一些文本内容,上下文节点的命名属性将被设置为指定值(如果已存在则替换,否则添加)。

无操作

如果没有指定 t-operation ,模板主体将被解释为 JavaScript 代码,并以上下文节点作为 this 执行。

警告

尽管此模式比其他操作更强大,但它也更难调试和维护,建议避免使用。

调试

JavaScript QWeb 实现提供了一些调试钩子:

t-log

接受一个表达式参数,在渲染期间求值该表达式并使用 console.log 记录其结果:

<t t-set="foo" t-value="42"/>
<t t-log="foo"/>

将在控制台打印 42

t-debug

在模板渲染期间触发调试器断点:

<t t-if="a_test">
    <t t-debug=""/>
</t>

如果调试处于活动状态,则会停止执行(具体条件取决于浏览器及其开发工具)。

t-js

节点的主体是模板渲染期间执行的 JavaScript 代码。接受一个 context 参数,这是渲染上下文在 t-js 主体中的可用名称:

<t t-set="foo" t-value="42"/>
<t t-js="ctx">
    console.log("Foo is", ctx.foo);
</t>

辅助工具

core.qweb

(核心是 web.core 模块)一个加载了所有模块定义模板文件的 QWeb2.Engine() 实例,并引用标准辅助对象 _ (下划线)、 _t (翻译函数)和 JSON

core.qweb.render 可用于轻松渲染基本模块模板。

API

class QWeb2.Engine()

QWeb“渲染器”负责处理 QWeb 的大部分逻辑(加载、解析、编译和渲染模板)。

Odoo Web 在核心模块中为用户实例化一个 QWeb,并将其导出到 core.qweb 。它还会将所有模块的模板文件加载到该 QWeb 实例中。

一个 QWeb2.Engine() 也充当“模板命名空间”。

QWeb2.Engine.QWeb2.Engine.render(template[, context])

使用 context (如果提供)将先前加载的模板渲染为字符串,以查找模板渲染期间访问的变量(例如要显示的字符串)。

参数
  • template (String()) – 要渲染的模板名称

  • context (Object()) – 用于模板渲染的基本命名空间

返回

字符串

引擎公开了另一种方法,在某些情况下可能很有用(例如,如果您需要一个单独的模板命名空间,在 Odoo Web 中,看板视图会获得自己的 QWeb2.Engine() 实例,以避免其模板与更通用的“模块”模板冲突):

QWeb2.Engine.QWeb2.Engine.add_template(templates)

在 QWeb 实例中加载模板文件(模板集合)。模板可以指定为:

XML 字符串

QWeb 将尝试将其解析为 XML 文档然后加载。

URL

QWeb 将尝试下载 URL 内容,然后加载生成的 XML 字符串。

DocumentNode

QWeb 将遍历文档的第一层(提供的根节点的子节点),并加载任何命名模板或模板覆盖。

一个 QWeb2.Engine() 还公开了多种属性以进行行为自定义:

QWeb2.Engine.QWeb2.Engine.prefix

在解析期间用于识别指令的前缀。是一个字符串,默认为 t

QWeb2.Engine.QWeb2.Engine.debug

布尔标志,将引擎置于“调试模式”。通常,QWeb 会在模板执行期间拦截任何引发的错误。在调试模式下,它会让所有异常通过而不进行拦截。

QWeb2.Engine.QWeb2.Engine.jQuery

在模板继承处理期间使用的 jQuery 实例。默认为 window.jQuery

QWeb2.Engine.QWeb2.Engine.preprocess_node

一个 函数 。如果存在,则在将每个 DOM 节点编译为模板代码之前调用。在 Odoo Web 中,这用于自动翻译模板中的文本内容和某些属性。默认为 null

1

尽管它不使用(也不支持) XML 命名空间 ,但它在这方面与 Genshi 类似。

2

尽管由于历史原因或因为它们更适合用例,仍然使用了一些其他模板引擎。Odoo 9.0 仍然依赖于 JinjaMako