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 传递的名称替换

:samp:`{$as}_all`(已弃用)

正在被迭代的对象

注解

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

$as_值

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

$as_索引

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

$as_size

集合的大小,如果可用的话

$as_first

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

$as_last

是否当前项是迭代中的最后一项(等同于 $as_index + 1 == $as_size),需要可访问迭代器的大小

:samp:`{$as}_parity`(已弃用)

要么是 "even",要么是 "odd",表示当前迭代轮次的奇偶性

:samp:`{$as}_even`(已弃用)

一个布尔标志,表示当前迭代轮次位于偶数索引上

:samp:`{$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 -->

属性

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 的输出,或者是在使用“body”时的 t-set <reference/qweb/set>`(与 ``t-value`t-valuef 相对)。

Python

通常情况下,您不需要过于担心:对于有意义的 API,应会自动生成“安全”的内容,一切应能透明地工作。

不过在某些情况下,需要更明确的说明,以下 API 输出的内容是安全的,这些内容默认情况下在注入模板时不会被(重新)转义:

  • HTML 字段

  • html_escape() 和 :func:`markupsafe.escape`(它们是别名,不会出现重复转义的风险)。

  • html_sanitize()

  • markupsafe.Markup

    警告

    markupsafe.Markup 是一个不安全的接口,它是一个 断言,表示你希望内容是标记安全的,但并不能保证这一点,因此应谨慎使用。

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

强制双重转义

如果内容被标记为安全,但出于某些原因仍需要进行转义(例如打印 HTML 字段的标记),可以将其转换为普通字符串以“移除”安全标志,例如在 Python 中使用 str(content),在 JavaScript 中使用 String(content)

注解

因为 MarkupMarkup() 是一个更丰富的类型,一些操作会从 Markup() 中剥离安全信息,但不会从 Markup 中剥离。例如,在 Python 中字符串拼接('' + 内容)会生成一个 Markup,其中另一个操作数已经被正确转义;而在 JavaScript 中则会生成一个 String(),其中另一个操作数在拼接前 未被 转义。

已弃用的输出指令

转义

out 的别名,最初会对其输入进行 HTML 转义。目前尚未正式弃用,因为 outesc 之间的唯一区别是后者有些模糊/不正确。

原始

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

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

t-raw 已被弃用,因为随着生成内容的代码不断演变,很难跟踪该内容是否将用于标记语言,这会导致更复杂的审查流程和更危险的疏漏。

Python

专属指令

资产捆绑包

“智能记录” 字段格式化

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

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

调试

调试

如果值为空,则调用 breakpoint() 内置函数,该函数通常会调用调试器 (pdb 为默认选项)。

可以通过 PYTHONBREAKPOINTsys.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 将用于生成 缓存键。特殊情况:如果 键表达式 返回一个假值(Falsy value),则内容将不会被缓存。

示例:

<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-sett-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-sett-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 或 外部 ID 渲染 QWeb 视图/模板。模板会自动从 ir.qweb 记录中加载。

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

请求

当前的 Request 对象,如果有的话

调试

是否当前请求(如有)处于 debug 模式

quote_plus

用于 URL 编码的实用函数

json

相应的标准库模块

time

相应的标准库模块

datetime

相应的标准库模块

相对日期

see module

保留查询

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 指令。如果在主模式下使用,它将是新创建的模板名称;否则,它将作为注释添加到转换后的模板上,以帮助追溯继承关系。

对于继承本身,更改是通过 xpaths 指令完成的。有关可用指令的完整集,请参阅 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上下文节点

追加

该节点的内容会附加到上下文节点的末尾(在上下文节点最后一个子节点之后)。

前置

节点的内容被添加到上下文节点的前面(插入到上下文节点的第一个子节点之前)

before

该节点的内容会在上下文节点之前插入。

节点的内容在上下文节点之后立即插入

内部

节点的内容替换上下文节点的子节点

替换

该节点的正文用于替换上下文节点本身

属性

节点的主体应包含任意数量的 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 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

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

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

接口

class QWeb2.Engine()

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

Odoo Web 在核心模块中为用户实例化一个,并将其导出到 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 文档,然后加载它。

一个网址

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

一个 文档节点

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

它在这一点上与 Genshi 类似,尽管它不使用(也没有对)`XML 命名空间`_ 的支持。

2

尽管它还使用了一些其他模板引擎,有的是出于历史原因,有的是因为它们仍然是特定用例的更好选择。Odoo 9.0 仍然依赖 JinjaMako