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-elif
和 t-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-value
或 t-valuef
相对)。
Python¶
通常您无需过多担心:对于合理的 API,它们会自动生成“安全”内容,并且一切应该透明地工作。
然而,为了更清晰地处理某些情况,以下 API 输出的安全内容在注入模板时默认不会(重新)转义:
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 转义。尚未正式弃用,因为out
和esc
之间的唯一区别在于后者有些含糊/不准确。raw
out
的一个版本,从不 转义其内容。无论是否安全,内容都会原样输出。15.0 版后已移除: 改用带有
markupsafe.Markup
值的out
。t-raw
已被弃用,因为随着生成内容的代码不断演变,很难追踪它将用于标记,从而导致审查更加复杂并带来更大的风险。
Python¶
专属指令¶
资产包¶
“智能记录”字段格式化¶
t-field
指令只能在对“智能”记录(browse
方法的结果)执行字段访问(a.b
)时使用。它能够根据字段类型自动格式化,并集成到网站的富文本编辑中。
t-options
可用于自定义字段,最常见的选项是 widget
,其他选项则取决于字段或部件。
调试¶
t-debug
当值为空时,调用内置函数
breakpoint()
,该函数通常会调用调试器(默认为pdb
)。其行为可以通过
PYTHONBREAKPOINT
或sys.breakpointhook()
配置。
渲染缓存:¶
t-cache="key_cache"
标记模板的一部分在渲染时进行缓存。所有子指令仅在首次渲染时调用。这意味着在这些子指令渲染期间执行的 SQL 查询也只会执行一次。
t-nocache="documentation"
标记模板的一部分每次都会重新渲染。内容只能使用根值。
为什么要使用以及何时使用 t-cache
?¶
此指令通过缓存最终文档的部分内容来加速渲染,从而可能减少对数据库的查询。然而,应谨慎使用,因为 t-cache
不可避免地会使模板复杂化(例如对 t-set
的理解)。
然而,为了真正节省数据库查询,可能需要使用延迟求值的值来渲染模板。如果这些延迟值被用于缓存部分,则当缓存中已存在该部分内容时,它们不会被求值。
t-cache
指令适用于使用依赖于有限数据量的值的模板部分。我们建议使用性能分析工具分析模板的渲染(通过激活“添加 QWeb 指令上下文”选项)。在控制器中传递延迟值到渲染过程,可以显示使用这些值的指令并触发查询。
使用这种缓存的一个问题是使其对不同用户可用(不同用户应以相同方式渲染缓存部分)。另一个潜在问题是必要时使缓存条目失效。对于后者,应明智选择 键表达式。例如,使用记录集的 write_date
可以使缓存键过期,而无需显式从缓存中丢弃它。
还应注意,t-cache
部分中的值是有作用域的。这意味着,如果模板的这一部分中有 t-set
指令,则其后内容的渲染可能会与没有 t-cache
指令时有所不同。
如果一个 t-cache
内部还有另一个 t-cache
会怎样?¶
这些部分会被缓存,每个部分仅包含与其渲染对应的字符串。因此,内部的 t-cache
可能较少被读取,其缓存键也不一定会被使用。如果必须如此,则可能需要添加一个 t-nocache
(在同一节点或父节点上)。
t-nocache
的用途是什么?¶
如果您希望使用 t-cache
缓存模板的一部分,但其中一小部分必须保持动态并在缓存时求值。然而,t-nocache
部分无法访问模板的 t-set
值,只有控制器提供的值可在其中访问。例如,菜单是缓存的,因为它始终不变且渲染耗时(使用性能开发工具和 QWeb 上下文可以帮助您调查)。然而,在菜单中,我们希望电子商务购物车始终保持最新状态。因此,使用 t-nocache
使这部分保持动态。
t-cache
的基础¶
t-cache
指令允许存储模板的渲染结果。 键表达式 (例如 42: t-cache="42"
)将作为 Python 表达式求值,并用于生成 缓存键 。因此,同一模板部分可以有不同的 缓存值 (缓存的渲染部分)。如果 键表达式 是元组或列表,则在生成 缓存键 时会对其进行搜索。如果 键表达式 返回一个或多个记录集,则将使用模型、ID 及其对应的 write_date 来生成 缓存键 。特殊情况:如果 键表达式 返回假值,则内容不会被缓存。
示例:
<div t-cache="record,bool(condition)">
<span t-if="condition" t-field="record.partner_id.name">
<span t-else="" t-field="record.partner_id" t-options-widget="contact">
</div>
在这种情况下,缓存中可能包含与每个已返回真条件的记录相对应的值(字符串),以及与假条件相对应的值。如果某个模块修改了记录,write_date 被更改,则缓存值将被丢弃。
t-cache
和作用域值(t-set
、t-foreach
…)¶
t-cache
中的值是作用域化的,这会导致父节点中是否具有 t-cache
的行为变化。请记住,Odoo 使用了大量模板、t-call
和视图继承。因此,添加 t-cache
可能会修改您在编辑时看不到的模板渲染。(t-foreach
类似于每次迭代中的 t-set
)
示例:
<div>
<t t-set="a" t-value="1"/>
<inside>
<t t-set="a" t-value="2"/>
<t t-out="a"/>
</inside>
<outside t-out="a"/>
<t t-set="b" t-value="1"/>
<inside t-cache="True">
<t t-set="b" t-value="2"/>
<t t-out="b"/>
</inside>
<outside t-out="b"/>
</div>
将渲染为:
<div>
<inside>2</inside>
<outside>2</inside>
<inside>2</inside>
<outside>1</inside>
</div>
t-nocache
的基础¶
带有 t-nocache
属性的节点中的模板部分不会被缓存。因此,这部分内容是 动态的 并会被系统地渲染。然而,可用的值是由控制器提供(调用 _render
方法时)的值。
示例:
<section>
<article t-cache="record">
<title><t t-out="record.name"/> <i t-nocache="">(views: <t t-out="counter"/>)</i></titlle>
<content t-out="record.description"/>
</article>
</section>
将渲染为(counter = 1):
<section>
<article>
<title>The record name <i>(views: 1)</i></titlle>
<content>Record description</content>
</article>
</section>
这里的包含容器的 <i>
标签将始终被渲染,而其余部分则作为单个字符串存储在缓存中。
t-nocache
和作用域根值(t-set
、t-foreach
…)¶
t-nocache
标签的内容可用于文档说明并解释为何添加该指令。这些值被限定在 t-nocache
中,仅限于根值(由控制器提供和/或在调用 ir.qweb
的 _render
方法时提供)。可以在模板部分执行 t-set
,但这些值在其他地方不可用。
示例:
<section>
<t t-set="counter" t-value="counter * 10"/>
<header t-nocache="">
<t t-set="counter" t-value="counter + 5"/>
(views: <t t-out="counter"/>)
</header>
<article t-cache="record">
<title><t t-out="record.name"/> <i t-nocache="">(views: <t t-out="counter"/>)</i></titlle>
<content t-out="record.description"/>
</article>
<footer>(views: <t t-out="counter"/>)</footer>
</section>
将渲染为(counter = 1):
<section>
<header>
(views: 6)
</header>
<article>
<title>The record name <i>(views: 1)</i></titlle>
<content>Record description</content>
</article>
<footer>(views: 10)</footer>
</section>
这里的包含容器的 <i>
标签将始终被渲染,而其余部分则作为单个字符串存储在缓存中。计数器不会因 t-nocache
外部的 t-set
而更新。
t-nocache-*
在缓存中添加一些原始值¶
为了能够使用模板中生成的值,可以将其缓存。该指令用法为 t-nocache-*="expr"
,其中 *
是选定值的名称,expr
是 Python 表达式,其结果将被缓存。缓存的值必须是原始类型。
示例:
<section t-cache="records">
<article t-foreach="records" t-as="record">
<header>
<title t-field="record.get_method_title()"/>
</header>
<footer t-nocache="This part has a dynamic counter and must be rendered all the time."
t-nocache-cached_value="record.get_base_counter()">
<span t-out="counter + cached_value"/>
</footer>
</article>
</section>
值 cached_value
会与 t-cache="records"
的缓存模板部分一起缓存,并每次都添加到作用域根值中。
辅助工具¶
基于请求¶
大多数 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_routing
和website
插件也会设置它们所需的默认值。您可以使用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 字符串。
Document
或Node
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
。