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 还提供了一些用于各种数据点的其他变量:
警告
$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如果参数是映射,则每个(键、值)对都会生成一个新属性及其值::
<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 的输出,或与“主体”一起使用的 t-set (相对于 t-value 或 t-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 是比 :js Markup 丰富得多的类型,某些操作会从 :js Markup 中剥离安全信息,但不会从 Markup 中剥离。例如在 Python 中拼接字符串('' + content)会得到 Markup,其中另一个操作数已被正确转义;而在 Javascript 中则会得到 :js String,其中另一个操作数在拼接前 未 被转义。
Python¶
专有指令¶
资源包¶
“智能记录”字段格式化¶
t-field 指令只能在对“智能”记录(browse 方法的结果)执行字段访问(a.b)时使用。它能够根据字段类型自动格式化,并且已集成到网站的富文本编辑中。
t-options 可用于自定义字段,最常见的选项是 widget,其他选项取决于字段或小组件。
调试¶
t-debug值为空时,会调用
breakpoint()内置函数,该函数通常会调用调试器(默认为pdb)。该行为可通过
PYTHONBREAKPOINT或sys.breakpointhook()进行配置。
辅助函数¶
基于请求¶
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 或 外部标识符 渲染 QWeb 视图/模板。模板会从
ir.qweb记录中自动加载。_prepare_environment方法会在渲染上下文中设置一些默认值。http_routing和website插件模块也会提供它们所需的默认值。你可以使用minimal_qcontext=False选项来避免这些默认值,就像公共方法render那样:request当前的
Request对象(如果有)debug当前请求(如果有)是否处于
debug模式quote_plusURL 编码工具函数
json对应的标准库模块
time对应的标准库模块
datetime对应的标准库模块
- relativedelta
参见模块
keep_querykeep_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-extend 指令与 t-name 组合使用时作为主继承,单独使用时作为扩展继承。
在两种情况下,修改都通过任意数量的 t-jquery 子指令来完成::
<t t-extend="base.template">
<t t-jquery="ul" t-operation="append">
<li>new element</li>
</t>
</t>
t-jquery 指令的参数是一个 CSS selector :该选择器在扩展模板上用于选取 上下文节点,指定的 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>
辅助函数¶
API¶
- class QWeb2.Engine()¶
QWeb 的“渲染器”,负责处理 QWeb 的大部分逻辑(加载、解析、编译和渲染模板)。
Odoo Web 在 core 模块中为用户实例化一个,并将其导出为
core.qweb。它还会将各个模块的所有模板文件加载到该 QWeb 实例中。:js
QWeb2.Engine还充当“模板命名空间”。- QWeb2.Engine.QWeb2.Engine.render(template[, context])¶
将先前加载的模板渲染为字符串,并使用
context(如果提供)来查找模板渲染期间访问的变量(例如要显示的字符串)。- 参数
template (
String()) – 要渲染的模板的名称context (
Object()) – 用于模板渲染的基础命名空间
- 退货
字符串
引擎还提供了另一个在某些情况下可能有用的方法(例如,如果你需要一个独立的模板命名空间:在 Odoo Web 中,看板视图会拥有自己的 :js
QWeb2.Engine实例,这样它们的模板就不会与更通用的“模块”模板冲突):- QWeb2.Engine.QWeb2.Engine.add_template(templates)¶
在 QWeb 实例中加载一个模板文件(一组模板的集合)。模板可以按以下方式指定:
- XML 字符串
QWeb 会尝试将其解析为 XML 文档,然后加载它。
- 一个 URL
QWeb 会尝试下载该 URL 的内容,然后加载得到的 XML 字符串。
- 一个
Document或Node QWeb 会遍历文档的第一层(所提供根节点的子节点),并加载其中任何命名模板或模板覆盖。
:js
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。