Jinja2
本文全文都来自:http://docs.jinkan.org/docs/jinja2/index.html
简介
jinja2 是一个模仿 Django 模板语言而重新开发的模板引擎,因为Django模板引擎限制比较多,Django的开发者认为模板语言就应该干模板该做的事情,后端的事情放在后端处理,不应该在前端模板中写太多编程语言和处理逻辑,因此限制比较多。所以如果你比较熟悉Django模板,那么jinja2你也很快就能了解。
安装
pip install Jinja2
基本使用
from jinja2 import Template
# 直接使用字符串,创建一个模板。模板包含了一个变量 {{ name }}
temp = Template("<p>{{ name }}</p>")
# 给模板传递变量,并渲染. 可以传递关键字参数,或者字典
content = temp.render(name="Hello")
# content = temp.render({"name": "Hello"})
print(content)
"{{ }}" 是用来在模板中声明变量的。模板自带了一个 render() 函数,可以给模板传递变量(context,也叫上下文变量),然后渲染它的内容。
Template 对象
模板对象, 就是我们上面用到的:
from jinja2 import Template
temp = Template("<p>{{ name }}</p>")
content = temp.render(name="Hello")
print(content)
大模板文件,可以使用生成器,来减少内存压力:
from jinja2 import Template
temp = Template("{{ name }}")
gen = temp.generate(name="Wang") # 返回的是生成器
print(gen)
for g in gen:
print(g) # 渲染后的内容
常见的属性和方法:
globals
该模板的全局变量字典。修改这个字典是不安全的,因为它可能与其它模板或 加载这个模板的环境共享。
name
模板的加载名。如果模板从字符串加载,这个值为 None 。
filename
模板在文件系统上的文件名,如果没有从文件系统加载,这个值为 None 。
render
([context])渲染模板
generate
([context])和
render
类似。如果一个模板文件很大,我们可以使用这个函数,来返回一个生成器缓解内存压力。然后迭代这个生成器,来获取渲染后的内容。
stream
([context])和
generate()
一样,只不过返回TemplateStream
.
TemplateStream 对象
当我们对 Template 对象使用 .stream()
,就可以获取一个模板流。它有两个方法:
disable_buffering
()禁用输出时缓存
dump
(fp, encoding=None, errors='strict')将整个数据流保存到文件中:
Template('Hello {{ name }}!').stream(name='foo').dump('hello.html')
Environment 对象
除了上面我们介绍的直接使用 Template
对象,我们还可以使用 Environment
对象,来保存一些配置,譬如:
from jinja2 import Environment, PackageLoader, Template
# 声明一个 package 加载器,会自动去 temp 这个 python包下的 templates 文件夹找所有的文件,即:./temp/templates/*.*
loader = PackageLoader("temp", "templates")
env = Environment(loader=loader) # 生成环境
template = env.get_template("test.html") # 加载某个文件,文件后缀随意,因为它只是一个文件,会从:./temp/templates/ 路径下查找这个 test.html
print(template.render(name="ahha"))
Environemt 的好处是,可以保存一些配置,以后所有使用这个环境所获取的模板,都会统一使用这个配置。
我们可以通过
.get_template()
来获取某个模板,然后通过.render(context)
来渲染模板。
参数:
- block_start_string
- 声明 block 时的起始字符串,默认:
'{%'
.- block_end_string
- 声明 block 时的结束字符串,默认:
'%}'
.- variable_start_string
- 声明 变量 时的起始字符串,默认:
'{{'
.- variable_end_string
- 声明 变量 时的结尾字符串,默认:
'}}'
.- comment_start_string
- 声明 注释 时的起始字符串,默认:
'{#'
.- comment_end_string
- 注释的结尾字符串:
'#}'
.- line_statement_prefix
- 行语句的起始字符, 以此字符开头的行,会被当场语句执行 行语句.
- line_comment_prefix
- 注释的起始字符,如果以此字符开头,会被当成注释(上面我们说过默认的注释是{# #}): See also 行语句.
- newline_sequence
- 为序列开启新行。必须是
'\r'
,'\n'
or'\r\n'
. 默认:'\n'
- extensions
- 扩展插件
- autoescape
- 如果设置成 True,会自动转义一些特殊字符
- loader
- 模板加载器
- auto_reload
- 如果模板更改,自动重新加载模板(为了性能,最好设置成 False)
Environment 对象的一些属性:
filters
该环境的过滤器字典。只要没有加载过模板,添加新过滤器或删除旧的都是 安全的。自定义过滤器见 自定义过滤器 。有效的过滤器名称见 标识符的说明 。
tests
该环境的测试函数字典。只要没有加载过模板,修改这个字典都是安全的。 自定义测试见 see 自定义测试 。有效的测试名见 标识符的说明 。
globals
一个全局变量字典。这些变量在模板中总是可用。只要没有加载过模板,修 改这个字典都是安全的。更多细节见 全局命名空间 。有效的 对象名见 标识符的说明 。
add_extension
(extension)给环境添加一个扩展
from_string
(source, globals=None, template_class=None)从字符串加载一个模板。
get_or_select_template
(template_name_or_list, parent=None, globals=None)如果给了一个可迭代的模板列表,会使用
select_template()
,否则会使用get_template()
get_template
(name, parent=None, globals=None)从加载器加载模板。如果 parent 参数不为空,则会拼接 parent 文件夹来获取模板的真实路径。
join_path
(template, parent)连接模板和 parent路径
list_templates
(extensions=None, filter_func=None)返回所有的模板列表。
select_template
(names, parent=None, globals=None)和
get_template()
很像,但是会尝试多次获取模板.如果找不到模板会抛出TemplatesNotFound
Loader 加载器
在环境对象这一小节中,我们用到了加载器,下面有几种不同的加载器:
FileSystemLoader(pathes, encoding='utf-8')
文件系统加载器,可以直接使用路径,或者路径列表作为参数,来加载这些路径下的所有模板文件:
loader = FileSystemLoader('./temp/templates')
loader = FileSystemLoader(['./temp/templates', './temp/others'])
PackageLoader(package_name, package_path='templates', encoding='utf-8')
python包加载器,会从python的包(带有__init__.py
)中的 templates 文件夹下加载所有的模板:
loader = PackageLoader('package1', 'templates')
DictLoader(mapping)
字典加载器,可以使用一个字典对象加载模板,字典的键是模板名,值是模板的文本字符串:
from jinja2 import Environment, DictLoader
loader = DictLoader({'index.html': 'source {{ name }} here'})
env = Environment(loader=loader)
template = env.get_template("index.html")
print(template.render(name="TEST"))
PrefixLoader(mapping, delimiter='/')
一个前缀加载器,接收一个字典,字典的键是前缀,字典的值是一个加载器,之后就可以使用 前缀+delimiter+模板名
来加载模板:
from jinja2 import Environment, PackageLoader, PrefixLoader
loader = PrefixLoader({
'app1': PackageLoader('temp', "templates")
}, delimiter="&")
env = Environment(loader=loader)
# 直接使用 app1 + delimiter + 模板名 就可以找到模板
template = env.get_template("app1&test.html")
print(template.render(name="TEST"))
ChoiceLoader(loaders)
一个可选加载器,接收一个加载器列表。如果第一个加载器找不到相应的模板,则会从第二个加载器开始找,并以此类推 ...
from jinja2 import Environment, ChoiceLoader, FileSystemLoader
loader = ChoiceLoader([
FileSystemLoader('./'), # 这个路径下没有模板
FileSystemLoader('./temp/templates') # 这个路径有模板:test.html
])
env = Environment(loader=loader)
template = env.get_template("test.html") # 依然能找到
print(template.render(name="TEST"))
转义
为了安全起见,所有用户输入的文本,都应该进行转义,因为用户可能输入不安全的 html 字符,从而进行 跨站脚本 攻击(Cross Site Scripting)
from markupsafe import Markup
s = "<p>Hello world!</p>" # html 字符串
m = Markup(s) # markup 对象
t = m.striptags() # 清除标签,只剩下文本
e = Markup.escape(s) # 转义特殊字符
o = e.unescape() # 特殊字符重新转义回文本
print(
m, # <p>Hello world!</p>
t, # Hello world!
e, # <p>Hello world!</p>
o, # <p>Hello world!</p>
sep='\n'
)
过滤器
过滤器就是python函数,只不过它可以用特殊的方式,在模板中使用,渲染模板的时候,会自动执行这个函数。
过滤器的语法糖是:|
譬如:
<p>
{{ 40|addFilter(30) }}
</p>
假设我们有一个叫做
addFilter
的特殊过滤器函数,上面的代码会调用addFilter(40, 30)
然后将返回值渲染到页面上。
from jinja2 import Environment
# 一个普通函数
def addFilter(x, y):
return x + y
env = Environment()
env.filters['addFilter'] = addFilter # 添加一个过滤器
con = env.from_string("""
{{ 40|addFilter(30) }}
""").render()
print(con)
测试
所谓测试,其实就是判断语句,比如 python:
x = 2
if x == 2:
return True
else:
return False
jinja2 示例:
from jinja2 import Environment, FileSystemLoader, Template
from jinja2.nodes import EvalContext
# 一个普通的函数
def is_odd(n):
if n % 2 == 0:
return False
else:
return True
env = Environment()
# 给环境添加一个自定义的测试
env.tests["odd"] = is_odd
# 从字符串加载一个模板
temp = env.from_string("""
<p>
{% if 3 is odd %}
<span>3 is odd</span>
{% else %}
<span>3 is not odd</span>
{% endif %}
</p>
""")
# 渲染模板,返回内容
content = temp.render()
print(content)
模板语法
注释
在模板中,注释使用 {# ... #}
表示:
<p>
{# this is comment,
and this is comment too.
#}
</p>
变量
变量在模板中的语法,用 {{
和 }}
括起来:
<p>
{{ name }} <!-- name 就是一个变量 -->
{{ obj.name }} <!-- 提取 obj 对象的属性 -->
{{ obj["name"] }} <!-- 和 obj.name 等效 -->
</p>
针对上述模板,我们后台如下:
from jinja2 import Template
class Obj:
name = "wang"
temp = Template("""<p>
{{ name }} <!-- name 就是一个变量 -->
{{ obj.name }} <!-- 提取 obj 对象的属性 -->
{{ obj["name"] }} <!-- 和 obj.name 等效 -->
</p>""")
gen = temp.render(obj=Obj(), name="Fake")
print(gen)
可以看出,我们可以像是使用普通的 python 语法一样,在模板中提取属性或者字典的值
消除空白
jinja2 会严格按照模板渲染,也就是说,如果你的模板中写入了空格,或者在标签之间换行了,渲染的内容也会原封不动的换行:
譬如:
from jinja2 import Template
class Obj:
name = "wang"
temp = Template("""<p> {# <p>后面有个换行符 #}
{{ name }} {{ obj.name }} {# 这两个变量会在一行 #}
{{ obj["name"] }} {# 后面也有换行符 #}
</p>""")
gen = temp.render(obj=Obj(), name="Fake")
print(gen)
会渲染成:
<p>
Fake wang
wang
</p>
如果你想将三个变量和标签都显示在一行,只能这样:
temp = Template("""<p>{{ name }}{{ obj.name }}{{ obj["name"] }}</p>""")
会渲染成:
<p>Fakewangwang</p>
如果我们想要在模板中好看(模板中换行),但是实际渲染的效果要在一行,可以使用 -
符号。
譬如:
temp = Template("""<p>
{{- name -}}
{{- obj.name -}}
{{- obj["name"] -}}
</p>""")
会渲染成:
<p>Fakewangwang</p>
要点1:
-
可以不成对出现要点2:
-
和{{
或}}
之间没有空格要点3:
{{-
代表消除变量之前的空白符,-}}
代表消除变量之后的空白符。要点4:
-
不仅可以用在{{ .. }}
上,也可以用在{% .. %}
上
转义自身语法
如果你想要转义 {{
本身,可以使用:
{{ '{{' }}
对于较大的段落,可以使用 raw
来将里面的内容全部当作原生字符
{% raw %}
<ul>
{% for item in seq %}
<li>{{ item }}</li>
{% endfor %}
</ul>
{% endraw %}
行语句
我们之前曾经提到过行语句。其实就是自定义一个符号,然后在模板中,所有以这个符号开头的字符串,都会被当作语句来执行,譬如:
from jinja2 import Template
s = """
<p>
# for i in [
'a',
'b',
'c'
]:
{{ i }}
# endfor
</p>
"""
temp = Template(s, line_statement_prefix="#") # 以 # 作为语句定义符号
gen = temp.render()
print(gen)
上面的以'#'开头的行,会作为语句执行,并且语句结尾可以加冒号,并且如果遇到[],()等,可以换行
模板 block
编程语言有 继承 的概念,模板也可以有。我们可以写一个基本模板,然后让子模板继承这个模板
基本模板: mother.html
{% block title %} {# 声明一个名为 title 的block #}
<p>This is title</p>
{% block content %} {# title 内部嵌套了一个名为 content 的block #}
{% endblock %}
{% endblock %}
{% block foot %}
<span>This is foot</span>
{% endblock %}
上面我们编写了一个母版,它里面定义了很多的
block
, 每个 block 都有自己的名字(block的名字不能重复):{% block blok_name %}...{% endblock %}
,在 block 中,我们可以写入一些 html 代码,让子模板继承。各个 block 之间是可以嵌套的
注意每个 block 要有一个
{% endblock %}
子模板:son.html
{% extends "mother.html" %} {# 继承母版 #}
{% block content %} {# 重写某个block #}
<span>This is content, and the mother.html doesn't have this.</span>
{% endblock %}
{% block foot %}
{{ super() }} {# 继承母版中的 foot block 的内容 #}
<span>New foot content</span>
{% endblock %}
{% extend %}
非常关键:它告诉模板要继承另一个模板。并且这个标签要放在模板的最上面。当然,继承的标签可以写路径:
{% extends "layout/default.html" %}
如果子模板没有重写母版中的某个block,则会默认使用母版中的block。
命名 block 的结束标签
针对一个block,我们还可以在 endblock 时写上它的名字,当然像上面的例子一样不写也行。
{% block sidebar %}
{% endblock sidebar %}
块作用域
一个 block 的内容,无法和block外部的内容互动,它有自己的作用域。譬如,你想在一个 for 循环中循环某个block,而block却无法获取for循环的作用域:
{% extends "mother.html" %} {# 继承母版 #}
{% for i in [1,2,3] %}
{% block foot scoped %} {# 后面加了一个 scoped, 就可以获取 for 循环中的变量了 #}
{{ i }}
{% endblock %}
{% endfor %}
转义字符串
from jinja2 import Template
temp = Template("""
{{ value|safe }} {# safe 过滤器,不转义字符 :<script>test</script> #}
{{ value|e }} {# e 过滤器,转义字符 :<script>test</script> #}
""")
x = temp.render(value="<script>test</script>")
print(x)
当然,你也可以自动转义:
{% autoescape true %}
自动转义在这块文本中是开启的。
{% endautoescape %}
{% autoescape false %}
自动转义在这块文本中是关闭的。
{% endautoescape %}
控制结构
For
for 可以用来遍历序列,如列表,字典等。
{% for item in items %}
{{ item }}
{% endfor %}
{% for key, value in my_dict.items() %}
{{ key }}
{% endfor %}
{% for i in [0, 1, 2] if not i %}
{{ i }}
{% else %} {# else 会在 for 循环没有成功执行的情况下执行 #}
<span>List is empty</span>
{% endfor %}
查看循环的索引(循环到第几个元素了)
from jinja2 import Template
temp = Template("""
{% for key, value in my_dict.items() %}
{{ key }}
{{ loop.index }} {# loop 是一个特殊对象,可以获取当前循环的索引位置 #}
{% endfor %}
""")
x = temp.render(my_dict={"a":"b", "b":"a"})
print(x)
loop 的几个特殊属性:
变量 描述 loop.index 当前循环迭代的次数(从 1 开始) loop.index0 当前循环迭代的次数(从 0 开始) loop.revindex 到循环结束需要迭代的次数(从 1 开始) loop.revindex0 到循环结束需要迭代的次数(从 0 开始) loop.first 如果是第一次迭代,为 True 。 loop.last 如果是最后一次迭代,为 True 。 loop.length 序列中的项目数。 loop.cycle 在一串序列间期取值的辅助函数。见下面的解释。
from jinja2 import Template
temp = Template("""
{% for i in [1,2,3] %}
{{ loop.cycle('A', "C", "B") }} {# loop.cycle会循环执行里面的A-C-B #}
{% endfor %}
""")
x = temp.render()
print(x)
If
和python中的if一样:
{% if x %}
...
{% elif y %}
...
{% else %}
...
{% endif %}
跳出循环
continue, break
import jinja2.ext
from jinja2 import Template
temp = Template("""
{% for i in [1,2,3] %}
{% if i == 1 %}
{{ i }}
{% continue %}
{% else %}
{% break %}
{% endif %}
{% endfor %}
""", extensions=[jinja2.ext.loopcontrols]) # 要额外加载一个扩展,才能使用 continue 和 break
x = temp.render()
print(x)
宏
宏类似于函数。我们可以定义一个宏,然后定义宏的内容。以后我们可以像调用函数一样调用宏。
{# 声明了一个名为 input 的宏,它还带有几个参数 #}
{% macro input(name, value='', type='text', size=20) -%}
<input type="{{ type }}" name="{{ name }}" value="{{ value|e }}" size="{{ size }}">
{%- endmacro %}
{# 调用宏,并传参 #}
<p>{{ input('username') }}</p>
<p>{{ input('password', type='password') }}</p>
include
include 可以直接将另一个模板包含进当前模板,相当于将另一个模板直接嵌套进来。
{% include 'header.html' %}
{% include "sidebar.html" ignore missing %} {# ignore missing:如果找不到模板,可以忽略 #}
{% include ['special_sidebar.html', 'sidebar.html'] ignore missing %} {# 可以使用列表 #}
{% include "sidebar.html" ignore missing without context %} {# without context 可以不携带上下文 #}
{% include "sidebar.html" ignore missing with context %} {# with context 可以携带上下文 #}
什么是上下文:
上下文其实就是模板中定义的变量,我们渲染时会将上下文传递给模板:
template.render(context)
,而我们嵌套其他模板时,也可以将它们中的上下文包含进来,这样在当前模板中也可以使用被嵌套模板中的上下文。
导入
假设现有:forms.html
{% 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 %}
我们可以像导入模块一样导入它:
{% import 'forms.html' as forms %} {# 导入 #}
{% from 'forms.html' import input as input_field, textarea %} {# 也可使用 from .. import .. as . #}
<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>
还可以导入时带入上下文:
{% from 'forms.html' import input with context %}
表达式
在模板中,可以正常使用python中常见的表达式:
数学计算:
+
-
*
/
//
%
字面量:
dict
list
tuple
str
true
false
比较运算:
==
>=
<=
!=
>
<
逻辑运算:
and
or
not
is
in
| # 过滤器
() # 调用函数
./[] # 用来获取对象的属性