简介

对于jinjia2来说,模板仅仅是文本文件,可以生成任何基于文本的文件格式,例如HTML、XML、CSV、LaTex 等等,以下是基础的模板内容:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

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

</html>

其中{% %}是用来执行表达式或者赋值,{{ }}则是用来显示对应变量的值,可以是表达式中的值,也可以是应用传入的值,{# #}相当于是python的注释,这个是模板中的注释。除此之外,这些标识符都可以在Environment中进行自定义修改,不过一般不会修改。

变量

众所周知,模板文件中存在表达式和变量,与python类中的变量类似,以一个下划线或者多个下划线的变量为私有变量,外部无法获取与调用,那么变量是怎么来的呢?当然是在调用render渲染模板时传入的,那么变量有哪几种类型呢?
1.普通值
模板文件

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <h1>{{name}}</h1>
    <h2>{{gender}}</h2>
</body>
</html>

调用模板文件传递普通值(存在、不存在)以及不传部分值

from jinja2 import Environment, FileSystemLoader
import pathlib

TEMPLATE_PATH = (
    pathlib.Path(__file__).parent.joinpath("template_demo").joinpath("templates")
)

env = Environment(loader=FileSystemLoader(TEMPLATE_PATH))
template = env.get_template("template.html")
# 如下两种方式都可以进行传递
# data = template.render(name="tom", age=18)
data = template.render({"name": "tom", "age": 18})
print(data)

结果

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <h1>tom</h1>
    <h2></h2>
</body>
</html>

由结果可以看出,当传递一个不存在值或者不传递模板中所需要的值时,默认的值为空
2.字典
模板文件

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <ul>
        <li>{{ item.name }}</li>
        <li>{{ item.age }}</li>
        <li>{{ item['gender'] }}</li>
    </ul>
</body>

</html>

调用模板文件传递字典(值存在、不存在)以及不传部分值

from jinja2 import Environment, FileSystemLoader
import pathlib

TEMPLATE_PATH = (
    pathlib.Path(__file__).parent.joinpath("template_demo").joinpath("templates")
)

env = Environment(loader=FileSystemLoader(TEMPLATE_PATH))
template = env.get_template("template2.html")
data = template.render(item={"name": "tom", "gender": "man", "city": "nanjing"}, age=18)
print(data)

结果

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <ul>
        <li>tom</li>
        <li></li>
        <li>man</li>
    </ul>
</body>

</html>

由结果可以看出,与普通值类似,当传递一个不存在值或者不传递模板中所需要的值时,默认的值为空。
需要注意的是当传入字典时,在模板文件中有两种方式可以接收字典的值,一种是item.attr,另一种是item['attr'],这两种效果是一致的,在官方文档中说明有如下:

Jinja2 中 item.attr 在 Python 层中做下面的事情:
检查 item 上是否有一个名为 attr 的属性。
如果没有,检查 item 中是否有一个 'attr' 项 。
如果没有,返回一个未定义对象。

item['attr'] 的方式相反,只在顺序上有细小差异:
检查在 item 中是否有一个 'attr' 项。
如果没有,检查 item 上是否有一个名为 attr 的属性。
如果没有,返回一个未定义对象。

如果一个对象有同名的项和属性,这很重要。此外,有一个attr()过滤器,它只查找属性。
那么什么情况下会出现同名的项和属性呢,如下

class Person(object):
    def __init__(self, name, age, gender) -> None:
        self.name = name
        self.age = age
        self.gender = gender
        self.data = {"name": "mike"}

    def __getitem__(self, key):
        print('__getitem__')
        return self.data[key]

    def __setitem__(self, key, value):
        print('__setitem__')
        self.data[key] = value

    def __delitem__(self, key):
        print('__delitem__')
        del self.data[key]


p = Person("tom", "123", "man")
print(p.name)
print(p['name'])
tom
__getitem__
mike

p.namep['name']分别是由不同的内置函数所控制的,p.name是由__setattr __、__getattr __将对应数据从__dict__中添加或者获取,p['name']是由__getitem __、__setitem __、__delitem __将对应数据从自定义字典中添加或获取、删除,只需要保证二者对应的字典中出现重名的即可。
3.序列(列表、集合、元组等)
模板文件

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

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

</html>

调用模板文件传递字典(值存在、不存在)以及不传部分值

from jinja2 import Environment, FileSystemLoader
import pathlib

TEMPLATE_PATH = (
    pathlib.Path(__file__).parent.joinpath("template_demo").joinpath("templates")
)

env = Environment(loader=FileSystemLoader(TEMPLATE_PATH))
template = env.get_template("template3.html")
data = template.render(seq=[1, 2, 3, 4], item=[1, 2, 3])
print(data)

结果

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <ul>

        <li>1</li>

        <li>2</li>

        <li>3</li>

        <li>4</li>


    </ul>

</body>

</html>

由结果可以看出,与上述类似,当传递一个不存在值或者不传递模板中所需要的值时,默认的值为空(如果处于循环中则不显示)。除此之外,序列中元素可以在模板中进行遍历。
4.自定义对象
对象就可以是上述字典中所描述的重名对象,
模板文件如下:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <ul>
        <li>{{ item.name }}</li>
        <li>{{ item['name'] }}</li>
        <li>{{ item.age }}</li>
        <li>{{ item['gender'] }}</li>
    </ul>
    
</body>

</html>

调用模板文件传递对象

p = Person("tom", "123", "man")
print(p.name)
print(p['name'])
TEMPLATE_PATH = (
    pathlib.Path(__file__).parent.joinpath("template_demo").joinpath("templates")
)

env = Environment(loader=FileSystemLoader(TEMPLATE_PATH))
template = env.get_template("template4.html")
data = template.render(item=p)
print(data)

结果

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <ul>
        <li>tom</li>
        <li>mike</li>
        <li>123</li>
        <li>man</li>
    </ul>

</body>

</html>

由结果可见,item.nameitem['name']虽然同名,但是不同值,当然可以使用attr过滤器只查询属性,只需要将变量改为如下方式,就只会获取到属性,详细用法到下面过滤器再进行说明。

<li>{{ item|attr("name")}}</li>

变量的赋值

jinjia2和python一样可以自定义变量进行赋值并可以在其他地方导入使用,使用set标签给变量赋值

{% set name = "mike" %}
# 也支持多个变量的赋值
{% set a, b = 1, 2 %}

当需要导入其他模板的赋值的变量时,和python导入变量类似,使用{% from "base.html" import name %}

过滤器

顾名思义,就是对传入模板的变量进行过滤得到符合要求的信息,
在jinjia2中使用过滤器有几种操作方式:
1.使用|

item|过滤器(参数)
例如获取对象的属性:
{{item|attr("name")}}
{{item | upper}}

2.使用filter标签

{%filter upper%}
{{item}}
{% endfilter %}

除此之外,过滤器还支持多条件过滤,每一次过滤后的结果作为下一个过滤器的参数,例如

将值进行绝对值后再转变成浮点型
{{ item.sale|abs|float}}

常用内置过滤器如下:

过滤器名称

过滤器的作用

使用方法

abs(number)

返回number的绝对值

item|abs

attr(obj, name)

获取obj的name属性,类似于obj.name

foo|attr("bar")

batch(value, linecount, fill_with=None)

批处理value的过滤器,value必须为可迭代对象(字典、字符串、列表、字典等),根据linecount将value分成多个linecount个数的块,不够的使用fill_with的参数补全,返回的是一个生成器。

item|batch(2, '&nbsp;')

capitalize(s)

将字符串s首字母大写

item|capitalize

center(value, width=80)

将value值在width字符中居中, 默认width为80

item|center(20)

default(value, default_value=u'', boolean=False)

当value未传值时显示default_value默认值,需要注意当value为空字符串时,default_value将不会显示,需将boolean设置为true才会显示

item|default("test")

dictsort(value, case_sensitive=False, by='key')

将value字典按照键值对的形式形成元组并排序添加到列表中,类似于[('age', 10), ('name', 20)],case_sensitive:排序时是否区分大小写,by:排序的依据,默认为key

item|dictsort(true)

escape(s)

将特殊字符( &, <, >, ‘, ”)转换成安全的html,就是转义

item|escape

filesizeformat(value, binary=False)

将文件格式化成人类可读的文件大小,默认十进制,binary为true时使用二进制

item\filesizeformat

first(seq)

返回序列的第一个元素

item|first

float(value, default=0.0)

将value转为浮点类型,默认为0.0

ite|float

forceescape(value)

强制value html转义

item|forceescape

format(value, *args, **kwargs)

格式化字符串

{{ "%s - %s"|format("Hello?", "Foo!") }} Hello? - Foo!

更多过滤器可以查看List of Builtin Filters

  • batch(value, linecount, fill_with=None)详解
    当前存在如下模板文件
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <table>
        {{item}}
        {{item|batch(2, ' ')|list}}
        {%- for row in item|batch(2, ' ') %}
          <tr>
          {%- for column in row %}
            <td>{{ column }}</td>
          {%- endfor %}
          </tr>
        {%- endfor %}
        </table>
</body>
</html>

python代码

from jinja2 import Environment, FileSystemLoader
import pathlib


TEMPLATE_PATH = (
    pathlib.Path(__file__).parent.joinpath("template_demo").joinpath("templates")
)

env = Environment(loader=FileSystemLoader(TEMPLATE_PATH))
template = env.get_template("template5.html")
data = template.render(item="asdadsa")
print(data)

结果

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <table>
        asdadsa
        [['a', 's'], ['d', 'a'], ['d', 's'], ['a', ' ']]
          <tr>
            <td>a</td>
            <td>s</td>
          </tr>
          <tr>
            <td>d</td>
            <td>a</td>
          </tr>
          <tr>
            <td>d</td>
            <td>s</td>
          </tr>
          <tr>
            <td>a</td>
            <td> </td>
          </tr>
        </table>
</body>
</html>

由结果可以看见,将传入的asdadsa变为了[['a', 's'], ['d', 'a'], ['d', 's'], ['a', '&nbsp;']],item|batch(2, '&nbsp;')将传入值按照每两个一组的形式,缺少的使用 补充,由此形成了一个内部嵌套可迭代对象的生成器,需要使用两次for循环获取数据,此处是因为使用了list转变成了列表。这方法非常适用于数据中存在分段数据格式需要统一(例如表格中的数据,可以传入所有数据,分块自动显示数据)。

测试

测试主要是测试一个变量或者一个表达式,使用is,可以使用在例如for循环的过滤,判断属性是否存在或者已定义等,

内置测试清单

可见:List of Builtin Tests

注释

在模板中使用注释,需要注意,原有的模板文件的格式注释不适用,需要使用{# #},也可以自定义注释的风格,使用Environment中的comment_start_stringcomment_end_string进行修改,注意需要首尾呼应。

空白控制

默认情况下,模板引擎不会对模板中的空白做任何修改,如果应用配置了Environment的 trim_blocks ,模板标签后的第一个换行符会被自动移除。
此外,也支持手动将空白删除,只需在需要的块前或者块后添加-即可,例如,清除了for之前的空白及属性后的空白,需要注意的是标签-之间不能留空白。

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

转义

当希望Jinjia2忽略某些变量或者块、标签时,尤其是内置的标签,例如当你需要显示{{,使用{{ {{ }}会报错,需要使用{{ '{{' }} 如果对于较大的块的话,例如想在网页展示jinjia2的语法,如下,使用{% raw %}{% endraw %}包裹即可。

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

行语句

使用Enviromentblock_start_stringblock_end_string设置行语句前缀为#时,默认会清除行首的空白,如下两种方式是一致的, 需要注意的是当启用行语句后,除了#后面的逻辑操作之外,不建议在同一行进行其他操作。

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

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

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

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

除了行语句之外还有行注释,使用Environment中的comment_start_stringcomment_end_string修改即可设置行注释, 例如修改为#, #之后的都将作为注释。

模板继承

模板继承是jinjia2的核心功能,可以将重复的元素抽取成一个主模板,由子模板填充内容。下面用实际例子来进行讲解,如下有一个基本模板文件,base.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 %}{% 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 %},一般在这个标签在模板文件的最上方,说明当前模板是继承于哪个模板,需要注意的是

  • 当使用extend继承模板时,(你可以在一个文件中使用多次继承,但是 只会执行其中的一个)这是官方文档的话语,但是目前发现当继承多个模板时会之间抛出一个异常。
jinja2.exceptions.TemplateRuntimeError: extended multiple times
  • 继承模板中的路径问题是基于模板文件加载器的(loader),例如 FileSystemLoader 允许你用文件名访 问其它模板。你可以使用斜线访问子目录中的模板:
{% extends "layout/base.html" %}
  • 如果子模板未实现父模板的所有block标签,默认使用父模板自己的内容
  • 父模板和子模板中的block标签名称不应该出现重复,当需要重复使用某一个块时使用self.block标签名称()来调用,例如
{{self.title()}}
  • 当block标签中嵌套block标签时,外层的标签需要使用{{super()}}获取父模板标签中的内容,否则会出现外层block覆盖内层标签的内容
  • 为了更好的可读性,建议在使用block标签时对齐标签名称,提高了可读性,例如
{% block sidebar %}
	{% block inner_sidebar %}
		...
	{% endblock inner_sidebar %}
{% endblock sidebar %}
  • 默认情况下块无法访问块之外的变量,如果需要指定块可以访问的变量,添加scoped即可,还是上述的父模板和子模板,修改如下:
    父模板,添加loop_item block标签
<!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 title%} - My Webpage</title>
    {% endblock head%}
</head>
<body>
    <div id="content">{% block content %}
            <div class="content_in">
                <ul>
                    {% for item in sep %}
                    <li >{% block loop_item %}{{item}}{% endblock %}</li>
                    {% endfor %}
                </ul>
            </div>
        {% endblock content%}</div>
    <div id="footer">
        {% block footer %}
        © Copyright 2008 by <a href="http://domain.invalid/">you</a>.
        {% endblock footer%}
    </div>
</body>

子模板

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

渲染子模板

from jinja2 import Environment, FileSystemLoader
import pathlib


TEMPLATE_PATH = (
    pathlib.Path(__file__).parent.joinpath("template_demo").joinpath("templates")
)

env = Environment(loader=FileSystemLoader(TEMPLATE_PATH))
template = env.get_template("template_child.html")
data = template.render(sep=[1, 2, 4])
print(data)

html_path = pathlib.Path(__file__).parent.joinpath("test3.html")
with open(html_path, mode="w", encoding="utf-8") as f:
    f.write(data)

结果

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html lang="en">
<html xmlns="http://www.w3.org/1999/xhtml">       
<head>
    
    
    <link rel="stylesheet" href="style.css" />    
    <title>Index - My Webpage</title>
    
    <style type="text/css">
        .important { color: #336699; }
    </style>

</head>
<body>
    <div id="content">

            <div class="content_in">
                <ul>

                    <li >
<h1></h1>
</li>

                    <li >
<h1></h1>
</li>

                    <li >
<h1></h1>
</li>

                </ul>
            </div>

    <h1>Index</h1>
    <p class="important">
      Welcome on my awesome homepage.
    </p>
</div>
    <div id="footer">

        © Copyright 2008 by <a href="http://domain.invalid/">you</a>.

    </div>
</body>

由结果看出,item为空,因为当loop_item被子模板覆盖时,变量在上下文中将可能是未定义的或者未传递给上下文的,因此需要在父模板中指定块的变量,或者说是将父模板的变量传递给子模板,只需在block标签末尾添加scoped即可,如下

<!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 title%} - My Webpage</title>
    {% endblock head%}
</head>
<body>
    <div id="content">{% block content %}
            <div class="content_in">
                <ul>
                    {% for item in sep %}
                    <li >{% block loop_item scoped%}{{item}}{% endblock %}</li>
                    {% endfor %}
                </ul>
            </div>
        {% endblock content%}</div>
    <div id="footer">
        {% block footer %}
        © Copyright 2008 by <a href="http://domain.invalid/">you</a>.
        {% endblock footer%}
    </div>
</body>

这里就相当于把父模板的item以及与item相关的sep传递给了子模板,子模板可以操作指定的块变量,结果如下

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html lang="en">
<html xmlns="http://www.w3.org/1999/xhtml">       
<head>
    
    
    <link rel="stylesheet" href="style.css" />    
    <title>Index - My Webpage</title>
    
    <style type="text/css">
        .important { color: #336699; }
    </style>

</head>
<body>
    <div id="content">

            <div class="content_in">
                <ul>

                    <li >
<h1>1</h1>
</li>

                    <li >
<h1>2</h1>
</li>

                    <li >
<h1>4</h1>
</li>

                </ul>
            </div>

    <h1>Index</h1>
    <p class="important">
      Welcome on my awesome homepage.
    </p>
</div>
    <div id="footer">

        © Copyright 2008 by <a href="http://domain.invalid/">you</a>.

    </div>
</body>
  • 可以手动设置block标签在继承时必须被重写,只需在block后添加required,但是需要注意的是必须要被重写的block标签中不能有内容。
  • extends、include 和 import 可以采用模板对象而不是要加载的模板名称。 这在某些高级情况下可能很有用,因为您可以使用 Python 代码首先加载模板并将其传递给渲染。这对于获取父模板文件位置的更为准确。还是上述的父模板,子模板有所变动,修改继承的对象为模板对象layout,如下:
{% extends layout %}
{% block title %}Index{% endblock %}
{% block head %}
    {{super()}}
    <style type="text/css">
        .important { color: #336699; }
    </style>
{% endblock %}
{% block loop_item %}
<h1>{{item}}</h1>
{% endblock %}
{% block content %}
    {{super()}}
    <h1>Index</h1>
    <p class="important">
      Welcome on my awesome homepage.
    </p>
{% endblock %}

渲染子模板,获取需要继承的模板对象并在子模板渲染时传递进去

from jinja2 import Environment, FileSystemLoader
import pathlib


TEMPLATE_PATH = (
    pathlib.Path(__file__).parent.joinpath("template_demo").joinpath("templates")
)

env = Environment(loader=FileSystemLoader(TEMPLATE_PATH))
layout = env.get_template("template_parent.html")

template = env.get_template("template_child_object.html")
data = template.render(sep=[1, 2, 4], layout=layout)
print(data)

html_path = pathlib.Path(__file__).parent.joinpath("test3.html")
with open(html_path, mode="w", encoding="utf-8") as f:
    f.write(data)

html转义

当从模板生成html时,变量中可能会存在一些html字符,存在一些风险,因此jinjia2提供了对于转义的两种操作方式,jinjia2默认是不开启自动转义的。

手动转义

对于可能包含 > 、 < 、 & 或 " 字符的变量,需要手动进行转义,在python代码中使用escape()Markup.escape(),也可以在模板文件中使用过滤器 |e 来实现

自动转义

只需要设置Environment中的autoescape为True即可,但是在变量转义后未标记为安全时会出现二次转义
变量和表达式可以在以下任一位置标记为安全:

  • 应用程序使用 markupsafe.Markup 中的过滤器(from markupsafe import Markup)
  • 带有 |safe 过滤器的模板。

控制结构清单

for

和python一样,支持遍历序列、字典等元素
序列

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

字典, 默认字典元素不是有序的,如果希望字典是有序的,建议使用dictsort过滤器。

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

for循环中可以访问一些内置变量,如下:

变量

描述

loop.index

当前循环迭代的次数(从 1 开始)

loop.index0

当前循环迭代的次数(从 0 开始)

loop.revindex

到循环结束需要迭代的次数(从 1 开始)

loop.revindex0

到循环结束需要迭代的次数(从 0 开始)

loop.first

如果是第一次迭代,为 True 。

loop.last

如果是最后一次迭代,为 True 。

loop.length

序列中的项目数。

loop.cycle

在一串序列间期取值的辅助函数。见下面的解释。

loop.depth

指示当前渲染在递归循环中的深度。 从 1 级开始

loop.depth0

指示当前渲染在递归循环中的深度。 从 0 级开始

loop.changed(*val)

如果之前使用不同的值调用(或根本没有调用),则为真

当在for循环取值时,需要对某个变量再进行取值,可以使用到loop.cycle(),例如:

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

结果如下

<tr>
   <td class="odd">a</td>
   <td class="even"><</td>
</tr>

jinjia2的循环不支持中断,所以for循环不支持使用breakcontinue,但可以一开始就过滤序列

{% for user in users if not user.hidden %}
    <li>{{ user.username|e }}</li>
{% endfor %}

当for循环没走(序列为空,条件不满足)可以使用else作为辅助判定,相当于是if else

<ul>
{% for user in users %}
    <li>{{ user.username|e }}</li>
{% else %}
    <li><em>no users found</em></li>
{% endfor %}
</ul>

递归循环,当在for循环中,需要对获得的可迭代变量进行进一步循环时,只需要在循环定义中加上 recursive修饰,并在你想使用递归的地方,对可迭代量调用loop变量。例如:

待更新

if

和python一样,有if-else-elif等,一般可以用于变量的判断,以及循环的过滤

{% if kenny.sick %}
...
{% elif kenny.dead %}
...
{% else %}
...
{% endif %}

宏相当于是python中的函数,定义了一些通用的函数,方便在其他地方使用。使用macro 来标识,例如

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

从公共模板中导入

可能宏是申明在一个公共的模板中,其他模板调用时需要使用import将其导入方可使用,导入方法于python导入模块的方式类似,例如:

{% import 'base.html' as test %}
{% test.input() %}
{% test.textarea() %}

{% from 'base.html' import input, textarea %}

从模板对象中导入

与上述模板继承类似,也可以通过导入模板对象的方式,获取模板中的宏。
需要注意的是,当一个宏前面由一个或者多个下划线时,这个宏将变成私有的,无法导入或者导出使用。

宏中的可访问的变量,和python的参数类型很相似

  • varargs
    如果有多于宏接受的参数个数的位置参数被传入,它们会作为列表的值保存在 varargs 变量上。
  • kwargs
    同 varargs ,但只针对关键字参数。所有未使用的关键字参数会存储在 这个特殊变量中。
  • caller
    如果宏通过 call 标签调用,调用者会作为可调用的宏被存储在这个 变量中。
    存在如下宏
{% macro input(name, value='', type='text', size=20) -%}
    <input type="{{ type }}" name="{{ name }}" value="{{
        value|e }}" size="{{ size }}">
        {{varargs}} || 
        {{kwargs}}
{%- endmacro %}

在宏调用时如果多传参数,以及关键字参数(a=b),会出现在varargskwargs变量中

<p>{{ input('password', "test1", "test2", "test3", "test4", {"test5": "test6"}, test="test2") }}</p>

效果如下:

<p><input type="test2" name="password" value="test1" size="test3">
('test4', {'test5': 'test6'}) ||
{'test': 'test2'}</p>

宏中可以访问的内部属性

  • name
    宏的名称。 {{ input.name }} 会打印 input 。
  • arguments
    一个宏接受的参数名的元组。
  • defaults
    默认值的元组。
  • catch_kwargs
    如果宏接受额外的关键字参数(也就是访问特殊的 kwargs 变量),为 true 。
  • catch_varargs
    如果宏接受额外的位置参数(也就是访问特殊的 varargs 变量),为 true 。
  • caller
    如果宏访问特殊的 caller 变量且由 call 标签调用,为 true 。
    与上述内部变量类似,可以通过宏.属性名获取对应属性。

宏之间的调用

有时候我们需要在一个函数中调用另一个函数并获取函数的结果,这时候就涉及到宏之间的调用了,下面将以实例来说明此内容
当前存在一个模板文件如下, 其中label(name)为正常的宏,其下方则出现了调用宏并传入对应数据,使用的标签为call,并且需要注意的是在被调用的宏中有一个caller方法,其提供了调用者的返回,也就是前面所介绍的caller变量:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    {%- macro label(name)%}
        <label for="">{{name}}</label>
        <h2>{{ caller() }}</h2>
    {%- endmacro %}
    {%- call label('test') -%}
        "caller test"
    {%- endcall %}
</body>
</html>

渲染模板文件

from jinja2 import Environment, FileSystemLoader
import pathlib


TEMPLATE_PATH = (
    pathlib.Path(__file__).parent.joinpath("template_demo").joinpath("templates")
)

env = Environment(loader=FileSystemLoader(TEMPLATE_PATH))
template = env.get_template("template7.html")
data = template.render()
print(data)

结果

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
        <label for="">test</label>
        <h2>"caller test"</h2>
</body>
</html>

由此可见,调用者调用宏时,除了已有的被调用宏的操作,还包含了调用者的caller的宏的操作。
上述是针对于非参数传递的调用,可能在一些场景下我们需要去在调用宏之后获取一些参数后做一些操作,例如获取用户信息后,处理用户信息,如下:

{% 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>
        <dl>Realname</dl>
        <dd>{{ user.realname|e }}</dd>
        <dl>Description</dl>
        <dd>{{ user.description }}</dd>
    </dl>
{% endcall %}

与不传参类似,只需要在被调用宏的caller中添加所需参数并且在调用者调用宏时传递对应参数即可。

块分配

上述我们知道了块其实就是一行语句或者多行语句,那么对于变量的赋值,可以赋值块吗?
从 Jinja 2.8 开始,还可以使用块分配将块的内容捕获到变量名中。 这在某些情况下作为宏的替代方案很有用。 在这种情况下,您无需使用等号和值,只需编写变量名,然后编写所有内容,直到 {% endset %}被捕获。

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

从 Jinja2.10开始,使用set进行块分配时,可以支持过滤器,过滤器是对set包裹的块内元素生效。

{% set reply | wordwrap %}
    You wrote:
    {{ message }}
{% endset %}

块与宏的区别

  • 块不可以传参,只能传递相对固定的内容
  • 宏类似于函数,可以传递参数,自定义内容
  • 二者的使用和导入方式都是类似的

包含(模板文件中的引用)

上述,我们说到模板继承是jinja2核心,此处的包含则是另一种模板继承的内容呈现, 我们可以在模板中通过include将其他模板的内容引用进来,并且被包含的模板可以访问包含模板文件的上下文。例如

{% include "template9.html" %}

当被包含的模板文件不存在,却被包含时,可以使用如下ignore missing忽略,需要注意的是,with contextwithout context需在上下文可见性语句出现之前。

{% include "sidebar.html" ignore missing %}
{% include "sidebar.html" ignore missing with context %}
{% include "sidebar.html" ignore missing without context %}

与上述模板继承类似,除了可以包含正常的模板文件名称之外,还可以传入模板对象,使模板对象被包含。

模板继承与包含的差异

模板继承是子模板使用父模板的框架,在框架里添加内容;包含则是子模板从模板库里选择内容填充到模板中,图解如下:

模板继承

python jinja 模板 段落 python jinjia_赋值

包含

python jinja 模板 段落 python jinjia_python jinja 模板 段落_02

模板导入

模板导入分为整个模板的导入和模板中的特定属性的导入,之前我们在变量中导入就是模板中特定属性的导,需要注意:

  • 默认情况下,导入的模板不能访问当前模板的非全局变量。
  • 名称以一个或更多下划线开始的宏和变量是私有的,不能被导入。

整个模板的导入

{% from "base.html" as base%}

我们可以通过base.宏、属性等获取模板中的元素

模板中特定属性的导入

{% from "base.html" import attr, method %}

我们可以直接使用对应的属性和宏

模板导入时可以使用模板对象

模板继承包含类似,可以使用模板对象传递

包含(include)与导入(import)的区别

包含的模板默认可以访问当前模板的上下文,但导入的模板不能访问当前模板的上下文,可以在包含或者导入时手动选择是否获取上下文,with context获取上下文,without contextb=不获取上下文

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

表达式

jinja2由于与python语法十分相似,因此比较符号、常见数据类型、if、逻辑运算符等都与ptython是差不多的,此处不再赘述。

全局函数

  • range([start, ]stop[, step])
    返回一个从start到stop,步长为step的列表,和python的range()很像,这边返回的是一个列表,python则是一个生成器。
  • lipsum(n=5, html=True, min=20, max=100)
    在模板中生成测试数据。默认会生成5段HTML,每段在 20 到 100 词之间。如果 HTML 被禁用,会返回常规文本。这在测试布局时生成简单内容时很有用。
  • dict(**items)
    方便的字典字面量替代品。 {'foo' : 'bar'} 与 dict(foo=bar) 等价。
  • joiner(sep=', ')
    一个小巧的辅助函数用于“连接”多个节。连接器接受一个字符串,每次被调用时返回 那个字符串,除了第一次调用时返回一个空字符串。你可以使用它来连接
  • cycler(*items)
    周期计允许你在若干个值中循环,类似 loop.cycle 的工作方式。不同于 loop.cycle 的是,无论如何你都可以在循环外或在多重循环中使用它。
  • 周期计中的方法
  • reset()
    重置周期计到第一个项。
  • next()
    返回当前项并跳转到下一个。
  • current
    返回当前项。