QWeb 模板¶
QWeb 是 Odoo 使用的主要 templating 模板引擎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 还为各种数据点提供了其他一些变量:
Warning
$as 将被替换为传递给 t-as 的名称
$as_all(已弃用)被迭代的对象
Note
此变量仅在 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 (attribute,属性)指令实现的,它有 3 种不同形式:
t-att-$name会创建一个名为
$name的属性,对属性值求值并将结果设置为该属性的值:<div t-att-a="42"/>
将被渲染为:
<div a="42"></div>
t-attf-$name与上一个相同,但参数是 format string (格式化字符串)而不只是一个表达式,常用于混合字面与非字面字符串(例如 class):
<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>
Tip
格式化字符串有两种等价语法:
"plain_text {{code}}"(即 jinja 风格)和"plain_text #{code}"(即 ruby 风格)。t-att=mapping如果参数是一个映射,则每个(键,值)对都会生成一个新属性及其值:
<div t-att="{'a': 1, 'b': 2}"/>
将被渲染为:
<div a="1" b="2"></div>
t-att=pair如果参数是一个对(元组或包含 2 个元素的数组),则对的第一个元素是属性名,第二个元素是值:
<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 的输出,或与 “body”(而非 t-value 或 t-valuef)一起使用的 t-set 的输出。
Python¶
通常您无需太过在意:在合理的情况下,相关 API 应会自动生成”安全”内容,一切应能透明地工作。
不过对于需要更明确说明的情况,以下 API 会输出安全内容,这些内容在注入模板时默认不会(重新)转义:
HTML 字段。html_escape()和markupsafe.escape()(它们互为别名,不存在双重转义的风险)。html_sanitize().markupsafe.Markup.Warning
markupsafe.Markup是一个不安全的 API,它是一种 断言,表示您希望内容对标记安全,但无法对此进行必要的检查,应谨慎使用。to_text()不会将内容标记为安全,但也不会从安全内容中剥离该信息。
使用 Markup 创建安全内容¶
相关说明请参阅官方文档,但 Markup 的最大优势在于它是一个非常丰富的类型,它重写了 str 操作,从而 自动转义参数。
这意味着,对字符串字面量使用 Markup 并将用户提供的(因而可能不安全的)内容”格式化进去”,就能轻松创建 安全 的 HTML 片段:
>>> Markup('<em>Hello</em> ') + '<foo>'
Markup('<em>Hello</em> <foo>')
>>> Markup('<em>Hello</em> %s') % '<foo>'
Markup('<em>Hello</em> <foo>')
尽管这是件好事,但请注意其效果有时会很奇怪:
>>> Markup('<a>').replace('>', 'x')
Markup('<a>')
>>> Markup('<a>').replace(Markup('>'), 'x')
Markup('<ax')
>>> Markup('<a>').replace('>', 'x')
Markup('<ax')
>>> Markup('<a>').replace('>', '&')
Markup('<a&')
Tip
大多数内容安全的 API 实际上返回的是 Markup,以及随之而来的所有含义。
Javascript¶
Warning
由于缺少运算符重载,Markup() 是比 Markup 受限得多的类型。
因此它也不重写方法,任何涉及 Markup() 的操作都会返回普通的 String() (实际上连这都算不上,而是 “primitive string”)。
这意味着该回退是安全的,但在处理 Markup() 对象时很容易触发双重转义。
强制双重转义¶
如果内容已标记为安全,但出于某种原因仍需要转义(例如打印 HTML 字段的标记),只需将其转换回普通字符串即可”剥离”安全标志,例如在 Python 中使用 str(content),在 Javascript 中使用 String(content)。
Note
由于 Markup 是比 Markup 丰富得多的类型,某些操作会剥离 Markup 的安全信息,但不会剥离 Markup 的。例如在 Python 中进行字符串拼接('' + content)会得到一个 Markup,另一个操作数已被正确转义;而在 Javascript 中则会得到一个 String,另一个操作数在拼接前 并未 被转义。
已弃用的输出指令¶
escout的别名,最初会对其输入进行 HTML 转义。尚未正式弃用,因为out与esc之间的唯一区别仅在于后者含义略显模糊/不正确。rawout的一个版本,绝不 转义其内容。无论内容是否安全,都会原样输出。Deprecated since version 15.0: Use
outwith amarkupsafe.Markupvalue instead.t-raw之所以被弃用,是因为随着 生成 内容的代码不断演进,很难追踪它是否会被用于标记,这会导致审查更复杂、疏漏更危险。
Python¶
独占指令¶
资源包¶
“智能记录”字段格式化¶
t-field 指令只能在”智能”记录(browse 方法的返回结果)上执行字段访问(a.b)时使用。它能够根据字段类型自动格式化,并集成在网站的富文本编辑中。
t-options 可用于自定义字段,最常见的选项是 widget,其他选项取决于字段或 widget。
调试¶
t-debug使用 PDB 的
set_traceAPI 调用调试器。参数应为模块名称,会对该模块调用set_trace方法:<t t-debug="pdb"/>
等价于
importlib.import_module("pdb").set_trace()
渲染缓存:¶
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 来生成 缓存键。特殊情况:如果 键表达式 返回假值(Falsy),则内容不会被缓存。
示例:
<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" 的缓存模板部分一起被缓存,并每次都添加到作用域根值中。
辅助方法¶
基于请求¶
QWeb 在 Python 侧的多数用法位于控制器中(以及在 HTTP 请求期间),在这种情况下,数据库中存储为 视图 的模板可以直接调用 odoo.http.HttpRequest.render() 来渲染:
response = http.request.render('my-template', {
'context_value': 42
})
这会自动创建一个 Response 对象,可从控制器返回(或进一步定制以满足需要)。
基于视图¶
比前述辅助方法更底层的是 ir.qweb 上的 _render 方法(使用数据库)和公开的模块方法 render 不使用数据库):
- _render(id[, values])¶
按数据库 id 或 external id 渲染 QWeb 视图/模板。模板会自动从
ir.qweb记录加载。_prepare_environment方法会在渲染上下文中设置许多默认值。http_routing和website插件也会设置它们所需的默认值。您可以像公开方法render一样使用minimal_qcontext=False选项来避免这些默认值:request当前的
Request对象(如有)debug当前请求(如有)是否处于
debug模式quote_plusURL 编码工具函数
json对应的标准库模块
time对应的标准库模块
datetime对应的标准库模块
- relativedelta
参见该模块
keep_querykeep_query辅助函数
- 参数
values – context values to pass to QWeb for rendering
engine (str) – name of the Odoo model to use for rendering, can be used to expand or customize QWeb locally (by creating a “new” qweb based on
ir.qwebwith alterations)
- 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 指令。在 primary 模式下,它将作为新建模板的名称;否则它将作为注释添加到转换后的模板上,以帮助追溯继承关系。
至于继承本身,修改是通过 xpath 指令完成的。完整的可用指令集请参见 XPATH 文档。
primary 继承(子模板):
<t t-name="child.template" t-inherit="base.template" t-inherit-mode="primary">
<xpath expr="//ul" position="inside">
<li>new element</li>
</xpath>
</t>
extension 继承(就地转换):
<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-extend 指令与 t-name 组合使用时表现为 primary 继承,单独使用时表现为 extension 继承。
在这两种情况下,随后都使用任意数量的 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 代码执行,并以上下文节点作为thisWarning
虽然比其他操作强大得多,但该模式也更难调试和维护,建议避免使用
调试¶
javascript 版 QWeb 实现提供了一些调试钩子:
t-log接受一个表达式参数,在渲染过程中求值该表达式,并用
console.log记录其结果:<t t-set="foo" t-value="42"/> <t t-log="foo"/>
会在控制台打印
42t-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>
辅助方法¶
API¶
- class QWeb2.Engine()¶
QWeb 的“渲染器”,处理 QWeb 的大部分逻辑(加载、解析、编译和渲染模板)。
Odoo Web 会在 core 模块中为用户实例化一个,并将其导出为
core.qweb。它还会将各模块的所有模板文件加载到该 QWeb 实例中。QWeb2.Engine还充当“模板命名空间”。- QWeb2.Engine.QWeb2.Engine.render(template[, context])¶
将先前加载的模板渲染为字符串,使用
context如提供)来查找模板渲染期间访问的变量(例如要显示的字符串)。- 参数
template (
String()) – the name of the template to rendercontext (
Object()) – the basic namespace to use for template rendering
- 返回
String
引擎还暴露了另一个方法,在某些情况下可能有用(例如需要独立的模板命名空间时——在 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¶
一个
Function。如存在,会在将每个 DOM 节点编译为模板代码之前调用。在 Odoo Web 中,它用于自动翻译模板中的文本内容和部分属性。默认为null。