jinja2 简单整理

Jinja2

本文全文都来自:http://docs.jinkan.org/docs/jinja2/index.html

简介

jinja2 是一个模仿 Django 模板语言而重新开发的模板引擎,因为 Django 模板引擎限制比较多,因此有人开发出了 jinja2 这个库。所以如果你比较熟悉 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")  # 返回的是生成器
for g in gen:
	print(g)  # 渲染后的内容

Template 对象常见的属性和方法:

  • 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 的好处是,可以保存一些配置,以后所有使用这个环境所获取的模板,都会统一使用这个配置。

我们可以通过 env.get_template() 来获取某个模板对象,然后通过 template_obj.render(context) 来渲染模板。

Environment 参数:

  • block_start_string
    • 声明 block 时的起始字符串,默认: '{%'. (所谓 block,可以看作一个代码块,譬如:{% if 3 ==3 %} ... {% endif %}
  • 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")  # 前提是 test.html 需要存在于 ./temp/templates 这个路径下

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,  # &lt;p&gt;Hello world!&lt;/p&gt;
	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 %}   <!-- 这是一个代码块,odd 不是一个普通字符串,而是 odd 测试函数; '3 is odd',相当于执行: is_odd(3) -->
			<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 过滤器,会转义字符 :&lt;script&gt;test&lt;/script&gt; #}
""")

x = temp.render(value="<script>test</script>")
print(x)

当然,你也可以自动转义:

{% autoescape true %}
	自动转义在这块文本中是开启的。
{% endautoescape %}

{% autoescape false %}
	自动转义在这块文本中是关闭的。
{% endautoescape %}

控制结构

For

for 可以用来遍历序列,如列表,字典等。

{% for item in items %}   <!-- 类似 for 这种代码块里面用到的变量,不需要额外加 {{}}, 譬如 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 是 jinjia2 的一个特殊对象,可以获取当前循环的索引位置 #}
{% 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 %}
<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 .. import .. as . 来单独导入模板中的宏 #}
{% from 'forms.html' import input as input_field, textarea as txt_field %}
<dl>
    <dt>Username</dt>
    <dd>{{ input_field('username') }}</dd>
    <dt>Password</dt>
    <dd>{{ input_field('password', type='password') }}</dd>
</dl>
<p>{{ txt_field('comment') }}</p>

还可以导入时带入上下文:

{% from 'forms.html' import input with context %}

表达式

在模板中,可以正常使用python中常见的表达式:

数学计算:

+ 
-
*
/
//
%

譬如:

{{ 1 + 2 }}

字面量:

dict
list
tuple
str

true
false

譬如:

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

或者:

{% for key in {‘dict’: ‘of’, ‘key’: ‘and’, ‘value’: ‘pairs’} %}
    ...
{% endfor %}

比较运算:

==
>=
<=
!=
>
<

逻辑运算:

and
or
not


is   # 用于测试
in
|    # 用于过滤器
()   # 调用函数
./[] # 用来获取对象的属性

譬如:

{% if 1 in [1, 2, 3] and 2==2 %}
   ...
{% endif %}


内置过滤器和测试

内置过滤器

abs() float() lower() select() truncate()
attr() forceescape() map() selectattr() upper()
batch() format() pprint() slice() urlencode()
capitalize() groupby() random() sort() urlize()
center() indent() reject() string() wordcount()
default() int() rejectattr() striptags() wordwrap()
dictsort() join() replace() sum() xmlattr()
escape() last() reverse() title()
filesizeformat() length() round() tojson()
first() list() safe() trim()

内置测试

callable() escaped() lessthan() number() string()
defined() even() lower() odd() undefined()
divisibleby() greaterthan() mapping() sameas() upper()
equalto() iterable() none() sequence()
posted @ 2022-03-25 14:39  wztshine  阅读(3553)  评论(0编辑  收藏  举报