Jinja2-template-编写

Jinja 官方文档

Template Designer Documentation

简介

Jinja template 只是一个普通文件, 不需要有 specific extension. 但用 .jinja 可能会便于编辑器的 plugin 识别. (一般把所有 template 文件放在 templates 目录下也便于识别)

Template 中的 variables, expressions 以及 tags 会在模板引擎渲染时替换. 其语法倾向于 Django 和 Python.

Jinja2 API

Environment 对象

Environment 是一个核心对象, 其包含了加载, 编译和渲染模板所需的所有配置和信息. 其用作 Jinja2 模板引擎的主要入口点, 用于管理模板文件的加载, 模板渲染的设置和上下文.

可以用 Environment 对象来配置 Jinja2 的各种选项, 如板的搜索路径, 自定义过滤器和全局变量等.

如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from jinja2 import Environment, FileSystemLoader

# 创建一个 Jinja2 的环境对象
env = Environment(
loader=FileSystemLoader('templates'),
trim_blocks=True,
lstrip_blocks=True,
autoescape=True,
)

# 加载模板
template = env.get_template('template.html')

# 渲染模板并传入变量
rendered_template = template.render(name='Alice')

# 打印渲染后的结果
print(rendered_template)

一般一个配置就创建一个 Environment.

FileSystemLoader 加载器类

FileSystemLoader 是 Jinja2 中的加载器之一 (一个类), 用于从文件系统加载模板文件. 它提供了一种从指定目录中加载模板文件的方式.

1
2
3
4
5
6
7
from jinja2 import Environment, FileSystemLoader

# 创建一个 Jinja2 的环境对象,并指定模板文件的搜索路径
env = Environment(loader=FileSystemLoader('/path/to/templates'))

# 加载模板
template = env.get_template('template.html')

Template 对象

Template 可用于 rendered. Environment 就是为编译出一个 Template 而进行的设置. (也可以直接创建一个 template 不通过 Environment)

如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from jinja2 import Template

template_source = """
Hello, {{ name }}!

Additional content.
"""

template = Template(
template_source,
trim_blocks=True
)

rendered_template = template.render(name='Alice')

print(rendered_template)

基本语法

三种类型的 delimiters

 {% ... %}, for Statements
 {{ ... }}, for Expressions
 , for Comments

Variables

即在 template 中展开变量为 python 脚本的传入值, 如:

1
2
3
{{ foo }}
{{ foo.bar }}
{{ foo['bar'] }}

Filters

Filters 用于修改变量的值. 多个 filter 用 | 分割, 排在 variable 之后, 且用 () 传递参数, 如;

1
{{ name|striptags|title }}

即对 name 变量应用 striptagstitle 两个 filter.

传入参数如:

1
{{ listx|join(', ') }}

, 来连接传入的 list.

更多可用的 builtin filter 可查看 文档.

Tests

就是变种 if 语句, 用内置的一些比较方法来测试变量值, 如:

1
2
{% if loop.index is divisibleby 3 %}
{% if loop.index is divisibleby(3) %}

这里 ifis 是关键字, loop.index 是变量, divisibleby 是内置的方法.

可用的 Test 也可以查看 官方文档.

Comments

语法为:

1
{# ... #}

如:

1
2
3
4
5
{# note: commented-out template because we no longer use this
{% for user in users %}
...
{% endfor %}
#}

Whitespace Control

Jinja2 可以启用 trim_blockslstrip_blocks 选项, 前者会移除 a template tag 之后的第一个 newline, 后者会移除 block tag 前的 tabs 和 spaces.

1
2
3
4
5
6
from jinja2 import Environment, FileSystemLoader

env = Environment(loader=FileSystemLoader("templates/"), lstrip_blocks=True, trim_blocks=True)
template = env.get_template("hello.html")

print(template.render())

示例如:

1
2
3
4
5
<div>
{% if True %}
yay
{% endif %}
</div>

在渲染之后, 会变成:

1
2
3
<div>
yay
</div>

而不是:

1
2
3
4
5
<div>

yay

</div>

可以在 block 之前放置 + 来禁用 lstrip_blocks 选项, 如:

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

可以在 block 后放置 + 来禁用 trim_blocks 选项, 如:

1
2
3
4
5
<div>
{% if something +%}
yay
{% endif %}
</div>

(还有一些功能这里没记录, 可见文档)

Tags

Tags 为 {%` 和 `%} 包裹的部分, 作用类似于 python 中的关键词 (做一些逻辑控制, 如 if, for 等).

Conditional

语法为:

1
{% if ... %} ... {% else %} ... {% endif %}

如:

1
2
3
4
5
6
7
8
9
10
11
12
13
{# tamplates/all.txt #}
Hello {{ name }},

{% if score > 80 %}
I'm happy to inform you that you did vary well on today's {{ test_name }}.
{% else %}
Your score on today's test {{ test_name }} could have been better. More studying, less vampire hunting!
{% endif %}
You achieved {{ score }} out of {{ max_score }} points!

See you tomorrow,

Ms. Calendar

Loops

语法为:

1
{% for ... %} ... {% endfor %}

如:

1
2
3
4
5
6
<dl>
{% for key, value in my_dict.items() %}
<dt>{{ key|e }}</dt>
<dd>{{ value|e }}</dd>
{% endfor %}
</dl>

Macros

Macros 相当于是可复用的 functions, 示例:

1
2
3
4
{% macro input(name, value='', type='text', size=20) -%}
<input type="{{ type }}" name="{{ name }}" value="{{
value|e }}" size="{{ size }}">
{%- endmacro %}

使用如:

1
2
<p>{{ input('username') }}</p>
<p>{{ input('password', type='password') }}</p>

在 macros 中可以访问三个特殊变量:

  • varargs, “variable arguments”, 如果向 macro 传递的参数数量超过其接收的量, 多出的就存在这个变量中
  • kwargs, “keyword arguments”, 未被使用的参数存储在这里
  • caller, 如果 macro 由 call tag 调用, 则 caller 会存储在这里

如果一个 macro name 以下划线开头, 则其无法被导出.

Call

Call 用于实现传递一个 macro 给另一个 macro (call 就像一个没有名字的 macro, 可供其内部的 macro 使用), 如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{% macro dump_users(users) -%}
<ul>
{%- for user in users %}
<li><p>{{ user.username|e }}</p>{{ caller(user) }}</li>
{%- endfor %}
</ul>
{%- endmacro %}

{% call(user) dump_users(list_of_user) %}
<dl>
<dt>Realname</dt>
<dd>{{ user.realname|e }}</dd>
<dt>Description</dt>
<dd>{{ user.description }}</dd>
</dl>
{% endcall %}

Filters

Filter section 相当于用指定的 filter 处理 block 中的内容:

1
2
3
{% filter upper %}
This text becomes uppercase
{% endfilter %}

这里将 block 内的内容转为大写.

Assignments

用于创建变量来使用:

1
2
{% set navigation = [('index.html', 'Index'), ('about.html', 'About')] %}
{% set key, value = call_something() %}

但要注意, 这些变量在其他 block 中无法被使用.

Block Assignments

如:

1
2
3
4
{% set navigation %}
<li><a href="/">Index</a>
<li><a href="/downloads">Downloads</a>
{% endset %}

将 block 内的内容赋值给 navigation.

Include

用于渲染 another template 并输出到当前 template:

1
2
3
{% include 'header.html' %}
Body goes here.
{% include 'footer.html' %}

Import

用于引入其他 tamplates 中的 macros, 与 python 中 import 语句的语法一致.

假设一个 template 文件内容为:

1
2
3
4
5
6
7
8
{% macro input(name, value='', type='text') -%}
<input type="{{ type }}" value="{{ value|e }}" name="{{ name }}">
{%- endmacro %}

{%- macro textarea(name, value='', rows=10, cols=40) -%}
<textarea name="{{ name }}" rows="{{ rows }}" cols="{{ cols
}}">{{ value|e }}</textarea>
{%- endmacro %}

(这里定义了 inputtextarea 两个 macros)

使用:

1
2
3
4
5
6
7
8
{% import 'forms.html' as forms %}
<dl>
<dt>Username</dt>
<dd>{{ forms.input('username') }}</dd>
<dt>Password</dt>
<dd>{{ forms.input('password', type='password') }}</dd>
</dl>
<p>{{ forms.textarea('comment') }}</p>

或者只导入一个 macro:

1
2
3
4
5
6
7
8
{% from 'forms.html' import input as input_field, textarea %}
<dl>
<dt>Username</dt>
<dd>{{ input_field('username') }}</dd>
<dt>Password</dt>
<dd>{{ input_field('password', type='password') }}</dd>
</dl>
<p>{{ textarea('comment') }}</p>

Import Context Behavior

includeimport tag 对导入 template 的 context 的处理不同, 前者默认传递 context, 而后者默认不传递. 这些可通过添加 with contextwithout context 选项来改变:

1
2
{% from 'forms.html' import input with context %}
{% include 'header.html' without context %}

Escaping

使用:

1
{% raw %} ... {% endrow %}

block 可以输出 raw 文本, 即转义其中的所有特殊字符.

1
2
3
4
5
6
7
{% raw %}
<ul>
{% for item in seq %}
<li>{{ item }}</li>
{% endfor %}
</ul>
{% endraw %}

Line Statements

可以不使用类似 {% %} 来包裹 statement, 而是定义一个 “line statement prefix” (这里假设为 #) 下面两个语句等价:

1
2
3
4
5
6
7
8
9
10
11
<ul>
# for item in seq
<li>{{ item }}</li>
# endfor
</ul>

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

为了提高可读性, 可以在 block 头 (如 for, if, elif) 的末尾加上冒号, 如:

1
2
3
# for item in seq:
...
# endfor

这种语法也能跨多行写:

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

(这里并没有用类似 \ 之类的符号表示跨行)

除 statement 之外, 可定义 “line-comment prefix” (这里假设为 ##) 来更改注释的写法:

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

示例

1
2
3
4
5
6
7
8
9
10
11
from jinja2 import Environment, FileSystemLoader

env = Environment(
loader=FileSystemLoader("templates/"),
lstrip_blocks=True, trim_blocks=True,
line_statement_prefix="#",
line_comment_prefix="##"
)
template = env.get_template("hello.html")

print(template.render())

hello.html 的内容为:

1
2
3
4
5
<div>
# if True
yay ## hahaha
# endif
</div>

输出为:

1
2
3
<div>
yay
</div>

Template Inheritance

Template Inheritance 允许创建一个 base template (父模板), 其中包含通用的结构和布局, 然后在基础模板的基础上创建子模板, 继承父模板的结构, 并可以覆盖或扩展特定部分.

Base Template

base.html 文件内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!DOCTYPE html>
<html lang="en">
<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>
</html>

注意这里以 {% block %} tag 定义的部分都是等待 child template 来 override 的部分.

{% block %} tag 可以内嵌于其他 tag 中.

Child Template

如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{% 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 to my awesome homepage.
</p>
{% endblock %}

{% extends %} tag 指明 base template (也就是 parent), 即以哪一个 template 来扩充. 注意这里指定的文件名还是和 FileSystemLoader 设置的路径相关.

{% extends %} 之前的部分可以正常编写.

注意这里 child template 只是定义了 base template 中等待被 fill in 的部分, 若未被定义则使用 base template 中的设置.

若想重复使用一个 block 名称, 可以借助 self 变量写为:

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

Super Blocks

调用 super() 来获取渲染后的 parent block.

1
2
3
4
5
{% block sidebar %}
<h3>Table Of Contents</h3>
...
{{ super() }}
{% endblock %}

Nesting extends

在嵌套使用 {% extends %} 之后, 可以用 super.super() 的方式嵌套调用 super:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 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 %}

Named Block End-Tags

可以把 block name 也放在 end tag 来提高可读性:

1
2
3
4
5
{% block sidebar %}
{% block inner_sidebar %}
...
{% endblock inner_sidebar %}
{% endblock sidebar %}

(前提是两个名称得写一样)

Block Nesting and Scope

默认情况下, block 内不能访问外部的变量, 如:

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

这里最终输出 empty <li>, 因为 block 无法访问到 item.

可以通过添加 scoped 现象来使其可以访问:

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

Required Blocks

具体见文档. (感觉用不上)

Template Object

具体见文档. (感觉用不上)

示例

python interactive 模式示例

1
2
3
4
5
>>> import jinja2
>>> env = jinja2.Environment()
>>> template = env.from_string("Hello {{ name }}")
>>> template.render(name="World")
'Hello World!'

从一个文件中加载模板并使用基本 variable replacement

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
from jinja2 import Environment, FileSystemLoader

MAX_SCORE = 100
TEST_NAME = "Python Challenge"

students = [
{"name":"Wiliow", "score":100},
{"name":"Cordelia", "score":85},
{"name":"Oz", "score":95},
]

env = Environment(loader=FileSystemLoader("templates/"))

template = env.get_template("good.txt")

for student in students:
name = student["name"]
filename = f"output/message_{name.lower()}.txt"
content = template.render(
student,
max_score=MAX_SCORE,
test_name=TEST_NAME
)

with open(filename, mode="w", encoding="utf-8") as output:
output.write(content)
print("... wrote", filename)

print("Finish...")

good.txt 的内容为:

1
2
3
4
5
6
7
8
9
{# templates/good.txt #}
Hello {{ name }},

I'm happy to inform you that you did very well on today's {{ test_name }}. You
achived {{ score }} out of {{ max_score }} points!

See you tomorrow,

Ms. Calendar

Flask 中的应用

一个简单的 flask 程序如:

1
2
3
4
5
6
7
8
9
10
11
# app.py
import flask

app = flask.Flask(__name__)

@app.route("/hello/")
def hello():
return "Hello World!"

if __name__ = "__main__":
app.run(debug=True)

结合 jinja2 使用:

1
2
3
4
5
6
7
8
# app.py
import flask

app = flask.Flask(__name__)

@app.route("/")
def home():
return flask.render_template("base.html", title="Jenny's Server")

base.html 的内容为:

1
2
3
4
5
6
7
8
9
10
11
12
13
{# templates/base.html #}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>{{ title }}</title>
</head>
<body>
{% block content %}
<h1>Welcome to {{ title }}!</h1>
{% endblock content %}
</body>
</html>

Jinja2-template-编写
http://example.com/2024/03/20/Jinja2-template-编写/
作者
Jie
发布于
2024年3月20日
许可协议