Jinja2 render和generate函数

Jinja2

本文全文都来自:欢迎来到 Jinja2 — Jinja2 2.7 documentation

简介

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(sourceglobals=Nonetemplate_class=None)

    从字符串加载一个模板。

  • get_or_select_template(template_name_or_listparent=Noneglobals=None)

    如果给了一个可迭代的模板列表,会使用 select_template() ,否则会使用 get_template()

  • get_template(nameparent=Noneglobals=None)

    从加载器加载模板。如果 parent 参数不为空,则会拼接 parent 文件夹路径来获取模板的真实路径。

  • join_path(templateparent)

    连接模板和 parent路径

  • list_templates(extensions=Nonefilter_func=None)

    返回所有的模板列表。

  • select_template(namesparent=Noneglobals=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()
  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值