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。对此空值的默认处理方法是:
- 打印一个空字符串
- 可以迭代
- 其他操作会失败
过滤器 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 是 1
到 9
的数字的列表, 输出会是 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 循环块中你可以访问这些特殊的变量:
Variable | Description |
---|---|
loop.index | 当前迭代到的下标 (1 indexed) |
loop.index0 | 当前迭代到的下标 (0 indexed) |
loop.revindex | 到循环结束需要迭代的次数 (1 indexed) |
loop.revindex0 | 到循环结束需要迭代的次数 (0 indexed) |
loop.first | True if first iteration. |
loop.last | True if last iteration. |
loop.length | 序列中的元素数量 |
loop.cycle | A helper function to cycle between a list of sequences. See the explanation below. |
loop.depth | Indicates how deep in a recursive loop the rendering currently is. Starts at level 1 |
loop.depth0 | Indicates how deep in a recursive loop the rendering currently is. Starts at level 0 |
loop.previtem | The item from the previous iteration of the loop. Undefined during the first iteration. |
loop.nextitem | The 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_blocks
和 lstrip_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
- 直接打印字符串
{{ '{{' }}
- 使用 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 %}
© 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
, include
和 import
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,而不是传统的文件路径