跳到主要内容

jinja2

官方文档:https://jinja.palletsprojects.com/en/3.1.x/templates/

Playground:https://j2live.ttl255.com

模板只是一个文本文件。它可以生成任何 text-based format(HTML、XML、CSV、LaTex 等等);

模板并没有特定的扩展名, .html.xml 都可以,但是使用 .jinja 后缀可以帮助 IDE 更好地识别

模板包含变量(variables)或表达式(expressions),两者在模板求值的时候会被替换为值;还有标签(tags)来实现控制逻辑

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html lang="en">
<head>
<title>My Webpage</title>
</head>
<body>
<ul id="navigation">
{% for item in navigation %}
<li><a href="{{ item.href }}">{{ item.caption }}</a></li>
{% endfor %}
</ul>

<h1>My Webpage</h1>
{{ a_variable }}
</body>
</html>
  • 以下模板语法都可以称为 block:

    • {% ... %}:Statements,容纳条件语句

    • {{ ... }} :Expressions,把表达式的结果打印出来

    • {# ... #}:Comments,注释

变量 Variables

模板变量(Template variables)实际上被传入模板的上下文字典(context dictionary)定义

使用点 (.) 或者 Python 的 __getitem__ 语法 ([]) 来获取属性

{{ foo.bar }}
{{ foo['bar'] }}

如果变量或属性不存在,会返回 undefined。对此空值的默认处理方法是:

  1. 打印一个空字符串
  2. 可以迭代
  3. 其他操作会失败

过滤器 Filters

一个 filter 过滤器的本质就是一个函数。使用格式为:变量名 | 函数

它的作用是,把变量传给函数,然后再把函数返回值作为这个代码块的值。

<!-- 带参数的 -->
{{ 变量 | 函数名(*args) }}

<!-- 不带参数可以省略括号 -->
{{ 变量 | 函数名 }}

可以使用 | 来链式调用(管道式)

{{ "hello world" | reverse | upper }}

文本块调用(将中间的所有文字都作为变量内容传入到过滤器中)

{% filter upper %}
一大堆文字
{% endfilter %}

注释

要把模板中一行的部分注释掉,默认使用 {# ... #} 注释语法。

{# note: disabled template because we no longer use this
{% for user in users %}
...
{% endfor %}
#}

空白控制

默认配置中

  • 单独的文本末尾换行符会被去除
  • 其他空白字符(空格、制表符、换行符等)会保持不变

如果应用配置了 Jinja 的 trim_blocks,模板标签后的第一个换行符会被自动移除(像 PHP 中一样);lstrip_blocks 会将 block

此外,你也可以手动剥离模板中的空白。当你在块(比如一个 for 标签、一段注释或变 量表达式)的开始或结束放置一个减号( - ),可以移除块前或块后的空白:

{% for item in seq -%}
{{ item }}
{%- endfor %}

这会产出中间不带空白的所有元素。如果 seq 是 19 的数字的列表, 输出会是 123456789

如果开启了 行语句 ,它们会自动去除行首的空白。

提示

标签和减号之间不能有空白。

有效的:

{%- if foo -%}...{% endif %}

无效的:

{% - if foo - %}...{% endif %}

常用例子

字符串操作:

safe:禁用转义
<p>{{ '<em>hello</em>' | safe }}</p>

capitalize:把变量值的首字母转成大写,其余字母转小写
<p>{{ 'hello' | capitalize }}</p>

lower:把值转成小写
<p>{{ 'HELLO' | lower }}</p>

upper:把值转成大写
<p>{{ 'hello' | upper }}</p>

title:把值中的每个单词的首字母都转成大写
<p>{{ 'hello' | title }}</p>

reverse:字符串反转
<p>{{ 'olleh' | reverse }}</p>

format:格式化输出
<p>{{ '%s is %d' | format('name',17) }}</p>

striptags:渲染之前把值中所有的HTML标签都删掉
<p>{{ '<em>hello</em>' | striptags }}</p>

truncate: 字符串截断
<p>{{ 'hello every one' | truncate(9)}}</p>

列表操作:

first:取第一个元素
<p>{{ [1,2,3,4,5,6] | first }}</p>

last:取最后一个元素
<p>{{ [1,2,3,4,5,6] | last }}</p>

length:获取列表长度
<p>{{ [1,2,3,4,5,6] | length }}</p>

sum:列表求和
<p>{{ [1,2,3,4,5,6] | sum }}</p>

sort:列表排序
<p>{{ [6,2,3,1,5,4] | sort }}</p>

测试变量 Tests

Jinja2 提供的 tests 可以用来在语句里对变量或表达式进行测试;如果要测试一个变量,可以在变量后加上 “is” 和 test 名,比如:

{% if user.age is equalto 42 %} {# 这里也可以写成... is equalto(42) #}
Ha, you are 42!
{% endif %}

如果要传入参数,可以在 test 后增加括号,也可以直接写在后面

常用的 test

  • boolean
  • defined
  • equalto
  • escaped
  • none
  • sequence
  • string
  • number
  • reverse
  • replace

控制结构 Control Structures

for

列表

<h1>Members</h1>
<ul>
{% for user in users %}
<li>{{ user.username|e }}</li>
{% endfor %}
</ul>

字典

<dl>
{% for key, value in my_dict.items() %}
<dt>{{ key|e }}</dt>
<dd>{{ value|e }}</dd>
{% endfor %}
</dl>

在一个 for 循环块中你可以访问这些特殊的变量:

VariableDescription
loop.index当前迭代到的下标 (1 indexed)
loop.index0当前迭代到的下标 (0 indexed)
loop.revindex到循环结束需要迭代的次数 (1 indexed)
loop.revindex0到循环结束需要迭代的次数 (0 indexed)
loop.firstTrue if first iteration.
loop.lastTrue if last iteration.
loop.length序列中的元素数量
loop.cycleA helper function to cycle between a list of sequences. See the explanation below.
loop.depthIndicates how deep in a recursive loop the rendering currently is. Starts at level 1
loop.depth0Indicates how deep in a recursive loop the rendering currently is. Starts at level 0
loop.previtemThe item from the previous iteration of the loop. Undefined during the first iteration.
loop.nextitemThe item from the following iteration of the loop. Undefined during the last iteration.
loop.changed(*val)True if previously called with a different value (or not called at all).

在 for 循环中,可以使用特殊的 loop.cycle 辅助函数,伴随循环在一个字符串/变 量列表中周期取值:

{% for row in rows %}
<li class="{{ loop.cycle('odd', 'even') }}">{{ row }}</li>
{% endfor %}

if

其他

空白

默认情况下,block 前后的 whitespace(包括空格、制表符、换行符)都不会被移除(除了单个尾随换行符会被移除)

因此当你渲染下面的模板时

<div>
{% if True %}
yay
{% endif %}
</div>

将会得到

<div>

yay

</div>

可以使用 trim_blockslstrip_blocks 设置来分别移除 block 开头和结尾的 whitespace

<div>
yay
</div>

在 block 开头添加 + 来临时地屏蔽 lstrip_blocks 的行为

<div>
{%+ if something %}yay{% endif %}
</div>

在 block 末尾添加 + 来临时地屏蔽 trim_blocks 的行为

<div>
{% if something +%}
yay
{% endif %}
</div>

还可以在 block 的开头/末尾添加 - 来手动地移除 whitespace

{% for item in seq -%}
{{ item }}
{%- endfor %}

转义 Escaping

  1. 直接打印字符串
{{ '{{' }}
  1. 使用 raw block
{% raw %}
<ul>
{% for item in seq %}
<li>{{ item }}</li>
{% endfor %}
</ul>
{% endraw %}

行语句 Line Statements

若启用了行语句,就可以把一个行标记为一个语句。例如如果行语句前缀配置为 # ,下面的两个例子是等价的:

<ul>
# for item in seq
<li>{{ item }}</li>
# endfor
</ul>


<ul>
{% for item in seq %}
<li>{{ item }}</li>
{% endfor %}
</ul>

行语句前缀可以出现在一行的任意位置,只要它前面没有文本。为了语句有更好的可读性,在块的开始(比如 for 、 if 、 elif 等等)以冒号结尾:

# for item in seq:
...
# endfor

提示

若有未闭合的圆括号、花括号或方括号,行语句可以跨越多行:

<ul>
# for href, caption in [('index.html', 'Index'),
('about.html', 'About')]:
<li><a href="{{ href }}">{{ caption }}</a></li>
# endfor
</ul>

从 Jinja 2.2 开始,行注释(line-based comments)也可以使用了。例如如果配置 ## 为行注释前缀, 行中所有 ## 之后的内容(不包括换行符)会被忽略:

# for item in seq:
<li>{{ item }}</li> ## this comment is ignored
# endfor

模板继承 Template Inheritance

Jinja 中最强大的部分就是模板继承。模板继承允许你构建一个基本模板“骨架”,它可以包含公众元素,并定义子模板可以重写的 block

基本模板

这个模板,我们会把它叫做 base.html ,定义了一个简单的 HTML 骨架文档,你可 能使用一个简单的两栏页面。用内容填充空的块是子模板的工作:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html lang="en">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
{% block head %}
<link rel="stylesheet" href="style.css" />
<title>{% block title %}{% endblock %} - My Webpage</title>
{% endblock %}
</head>
<body>
<div id="content">{% block content %}{% endblock %}</div>
<div id="footer">
{% block footer %}
&copy; Copyright 2008 by <a href="http://domain.invalid/">you</a>.
{% endblock %}
</div>
</body>

{% block %} 标签划定了子模版可以重写的 block,并且其名称不可重复

如果想重复打印一个 {% block %} 标签,可以使用 self 语法

<title>{% block title %}{% endblock %}</title>
<h1>{{ self.title() }}</h1>
{% block body %}{% endblock %}

子模版

一个子模板看起来是这样:

{% extends "base.html" %}
{% block title %}Index{% endblock %}
{% block head %}
{{ super() }}
<style type="text/css">
.important { color: #336699; }
</style>
{% endblock %}
{% block content %}
<h1>Index</h1>
<p class="important">
Welcome on my awesome homepage.
</p>
{% endblock %}

{% extend %} 标签告诉模板引擎这个模板继承自另一个模板。

模板的文件名依赖于模板加载器。例如 FileSystemLoader 允许你用文件名访 问其它模板。你可以使用斜线访问子目录中的模板:

{% extends "layout/default.html" %}

这种行为也可能依赖于应用内嵌的 Jinja 。注意子模板没有定义 footer 块,会 使用父模板中的值。

你不能在同一个模板中定义多个同名的 {% block %} 标签。因为块标签以两种 方向工作,所以存在这种限制。即一个块标签不仅提供一个可以填充的部分,也在父级 定义填充的内容。如果同一个模板中有两个同名的 {% blok %} 标签,父模板 无法获知要使用哪一个块的内容。

Super Block

可以调用 super 来渲染父级块的内容。这会返回父级块的结果:

{% block sidebar %}
<h3>Table Of Contents</h3>
...
{{ super() }}
{% endblock %}

为了应对多层嵌套 {% extends %} 的情况,super 也可以嵌套调用

# parent.tmpl
body: {% block body %}Hi from parent.{% endblock %}

# child.tmpl
{% extends "parent.tmpl" %}
{% block body %}Hi from child. {{ super() }}{% endblock %}

# grandchild1.tmpl
{% extends "child.tmpl" %}
{% block body %}Hi from grandchild1.{% endblock %}

# grandchild2.tmpl
{% extends "child.tmpl" %}
{% block body %}Hi from grandchild2. {{ super.super() }} {% endblock %}

给结束 block 标签命名

Named Block End-Tags

在 block 的结束标签中加入的名称以增强可读性

{% block sidebar %}
{% block inner_sidebar %}
...
{% endblock inner_sidebar %}
{% endblock sidebar %}

block 的嵌套与作用域

Block Nesting and Scope

嵌套块可以胜任更复杂的布局。不过,默认的 block 不允许访问块作用域外面的变量

例如,下面的模板只会输出空的 <li>,因为 item 在块中是不可访问的。其原因是,如果 block 被子模板替换,变量在其块中可能是未定义的或未被传递到上下文。

{% for item in seq %}
<li>{% block loop_item %}{{ item }}{% endblock %}</li>
{% endfor %}

从 Jinja 2.2 开始,你可以显式地指定在 block 中可用的变量,只需在块声明中添加 scoped 修饰,就把块设定到作用域中:

{% for item in seq %}
<li>{% block loop_item scoped %}{{ item }}{% endblock %}</li>
{% endfor %}

当覆盖一个块时,不需要提供 scoped 修饰。

必须被继承的 block

使用 required 将 block 定义为必须被继承的(They must be overridden at some point, but not necessarily by the direct child template)

required block 中只能有空白或注释,不能包含任何实质性的东西(跟抽象类很像啊)

# page.txt
{% block body required %}{% endblock %}

# issue.txt
{% extends "page.txt" %}

# bug_report.txt
{% extends "issue.txt" %}
{% block body %}Provide steps to demonstrate the bug.{% endblock %}

直接渲染 page.txt or issue.txt will raise TemplateRuntimeError 因为他们并没有重写 body block;而渲染 bug_report.txt 就没问题

scoped 一起使用时, required 要放在后面:

{% block body scoped %}{% endblock %}
{% block body required %}{% endblock %}
{% block body scoped required %}{% endblock %}

模板对象 template objects

extends, includeimport can take a template object instead of the name of a template to load. This could be useful in some advanced situations, since you can use Python code to load a template first and pass it in to render.

if debug_mode:
layout = env.get_template("debug_layout.html")
else:
layout = env.get_template("layout.html")

user_detail = env.get_template("user/detail.html")
return user_detail.render(layout=layout)
{% extends layout %}

注意 extends 后面是被传到 render() 里的 template object,而不是传统的文件路径