前言
来啦老铁!
笔者近期在工作中遇到将测试数据可视化的需求,且在Python语言背景下,当时借用的是团队既有经验,即:
-
模板引擎技术
而模板引擎使用:Jinja2
看过笔者的Spring Boot全家桶系列文章的同学一定不会陌生,我们曾在文章Spring Boot视图技术中,一起学习了java体系下的视图技术(模板引擎技术),咱们今天也来学习学习Python体系下的模板引擎技术吧!
学习路径
- 常见的Python模板引擎有哪些?
- Jinja2简介;
- Jinja2入门使用;
- Jinja2的基础知识点;
1. 常见的Python模板引擎有哪些?
首先我们来看看网上的大佬们都玩过哪些Python的模板引擎吧:
- pyTemplate
- pyTenjin
- Tornado.template
- PyJade
- Genshi
- Django
- Smarty
- Mako
- Jinja2
- 等。
这么多?笔者只听过Django和Jinja2,孤陋寡闻了,有兴趣的朋友请移步百度~
2. Jinja2简介;
来源:https://www.cnblogs.com/yanjiayi098-001/p/11701150.html
1). Jinja2介绍:
jinja2是Flask作者开发的一个模板系统,起初是仿django模板的一个模板引擎,为Flask提供模板支持,由于其灵活,快速和安全等优点被广泛使用。
2). Jinja2的优点:
- 相对于Template,jinja2更加灵活,它提供了控制结构,表达式和继承等;
- 相对于Mako,jinja2仅有控制结构,不允许在模板中编写太多的业务逻辑;
- 相对于Django模板,jinja2性能更好;
- Jinja2模板的可读性很棒。
3. Jinja2入门使用;
话不多说,咱直接开整,代码仓库:
- https://github.com/dylanz666/jinja2-demo/
我们直接开始撸一个简单的渲染例子,当然,在使用Jinja2前,需要安装Jinja2哟:
pip3 install jinja2
1). 创建模板文件;
模板文件即html文件,我们在项目内创建一个python包,如resources包,在resources包内创建一个模板文件夹templates,用于存放Jinja模板,最后在templates文件夹下创建一模板文件如:jinja2_demo_1.html;
2). 编写模板文件;
在jinja2_demo_1.html文件中,编写简单的Jinja模板,代码如下:
Jinja2-demo-1
INDEX
STATUS
MESSAGE
CREATED_WHEN
{{index}}
{{status}}
{{message}}
{{created_when}}
我们会发现,模板中没有写死的数据,而是以{{}}符号包裹变量,如果这时候直接浏览器打开resources/templates/jinja2_demo_1.html文件,效果为:
没错,就是个模板而已,没有真实值。接下来我们就来将{{}}符号包裹变量变成我们想展示的值吧!
3). 创建模板渲染逻辑处理类;
在项目根目录下创建一个python包,如utils包,在包内创建一个python类,如reportUtil.py,类中编写一简单的模板渲染逻辑,如下:
#!/usr/local/bin/python3
# -*- coding:utf-8 -*-
__author__ = "diren"
__date__ = "2021-01-03 15:50"
import os
import time
from jinja2 import Environment, PackageLoader
class ReportUtil:
def __init__(self):
pass
def create_jinja2_demo_1_html(self):
index = 1
status = "成功"
message = "这是一个测试文本"
created_when = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
env = Environment(loader=PackageLoader('resources', 'templates'))
template_name = "jinja2_demo_1.html"
template = env.get_template(template_name)
html = template.render(index=index, message=message, created_when=created_when, status=status)
current_dir = os.getcwd()
output_dir = os.path.join(current_dir, "../resources/output/")
if not os.path.exists(output_dir):
os.mkdir(output_dir)
os.chdir(output_dir)
fo = open(template_name, "w")
fo.writelines(html)
fo.close()
os.chdir(current_dir)
print(f"create {template_name}:", "完成")
if __name__ == "__main__":
ReportUtil().create_jinja2_demo_1_html()
这里稍微讲解一下我们的目的:
- 我们希望通过reportUtil.py,将一组数据渲染进resources/templates/jinja2_demo_1.html文件中表格的第一行;
- 渲染的数据为:
index = 1
status = "成功"
message = "这是一个测试文本"
created_when = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
- 渲染后的文件存储于resources/output/jinja2_demo_1.html中,
渲染预期为一个简单表格;
4). 项目整体结构;
5). 演示;
运行reportUtil.py,我们能看到resources包下生成了一个output文件夹,output文件夹下有一个html文件:jinja2_demo_1.html,该文件就是我们在reportUtil.py渲染出来的静态html文件:
浏览器打开resources/output/jinja2_demo_1.html文件:
嗯,没错,原来{{}}符号包裹的变量,均变成我们reportUtil.py代码中想渲染的真实值了,渲染演示成功!是不是很简单呀!
上述,演示了一个非常简单的Jinja2使用方法,接下来我们一起来边学习Jinja2的常用功能,边码代码、边演示、边学习吧!
4. Jinja2的基础知识点;
这里罗列一下基础知识点:
- 基本语法;
- 运算符;
- 转义方法;
- 测试器;
- 如何在模版页面中引入静态文件?
- 过滤器;
- 宏;
- 模板继承;
1). 基本语法;
注释 {#...#}
例如:{#这是一个注释#},这一行代码是不会被渲染、处理的,起注释作用;变量取值 {{...}}
如上面的例子, {{}}是占位符,Jinja2渲染时会将这些占位符替换成真实值。它支持python中的所有数据类型,如字符串、字典、列表等。
- 控制结构 {% ... %}
该符号包裹的内容表示控制结构,如分支、循环,我们来演示一下:
(1). if语句:
Jinja2-demo-1
INDEX
STATUS
MESSAGE
CREATED_WHEN
{#这是一个注释#}
{% if index==1 %}
{{index}}
{{status}}
{{message}}
{{created_when}}
{% elif index==2 %}
{{index}}
{{status}}
{% else %}
{{index}}
{{status}}
{{message}}
{% endif %}
很简单,当index为1,2,其他值时,表格显示的内容略有差异:
(2). for语句:
我们使用for语句,演示渲染一个含有多条数据的表格,并且为了演示效果,我顺便从网上找了个样式,稍作调整,创建新的模板文件jinja2_demo_2.html:
Jinja2-demo-2
{{title}}
{{title}}
最终结果:{{ status }}
发布日期:{{ created_when }}
INDEX
STATUS
MESSAGE
CREATED_WHEN
{% for item in details %}
{{item.index}}
{{item.status}}
{{item.message}}
{{item.created_when}}
{% endfor %}
reportUtil.py中也新增一渲染方法create_jinja2_demo_2_report,代码如下:
#!/usr/local/bin/python3
# -*- coding:utf-8 -*-
__author__ = "diren"
__date__ = "2021-01-03 15:50"
import os
import time
from jinja2 import Environment, PackageLoader
class ReportUtil:
def __init__(self):
pass
def create_jinja2_demo_1_html(self):
index = 1
status = "成功"
message = "这是一个测试文本"
created_when = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
env = Environment(loader=PackageLoader('resources', 'templates'))
template_name = "jinja2_demo_1.html"
template = env.get_template(template_name)
html = template.render(index=index, message=message, created_when=created_when, status=status)
current_dir = os.getcwd()
output_dir = os.path.join(current_dir, "../resources/output/")
if not os.path.exists(output_dir):
os.mkdir(output_dir)
os.chdir(output_dir)
fo = open(template_name, "w")
fo.writelines(html)
fo.close()
os.chdir(current_dir)
print(f"create {template_name}:", "完成")
def create_jinja2_demo_2_html(self):
title = "测试报告"
status = "失败"
created_when = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
details = [{
"status": "成功",
"message": "测试文本1",
"created_when": time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
}, {
"index": 2,
"status": "失败",
"message": "测试文本2",
"created_when": time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
}, {
"index": 3,
"status": "成功",
"message": "测试文本2",
"created_when": time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
}]
env = Environment(loader=PackageLoader('resources', 'templates'))
template_name = 'jinja2_demo_2.html'
template = env.get_template(template_name)
html = template.render(title=title, status=status, created_when=created_when, details=details)
current_dir = os.getcwd()
output_dir = os.path.join(current_dir, "../resources/output/")
if not os.path.exists(output_dir):
os.mkdir(output_dir)
os.chdir(output_dir)
fo = open(template_name, "w")
fo.writelines(html)
fo.close()
os.chdir(current_dir)
print(f"create {template_name}:", "完成")
if __name__ == "__main__":
ReportUtil().create_jinja2_demo_1_html()
ReportUtil().create_jinja2_demo_2_html()
运行reportUtil.py后:
这样,我们顺利将多条数据渲染进模板进行展示,数据为:
title = "测试报告"
status = "失败"
created_when = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
details = [{
"index": 1,
"status": "成功",
"message": "测试文本1",
"created_when": time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
}, {
"index": 2,
"status": "失败",
"message": "测试文本2",
"created_when": time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
}, {
"index": 3,
"status": "成功",
"message": "测试文本2",
"created_when": time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
}]
其中表格主题数据为:details;
我们还可以在循环中加入条件语句,如:
{% for item in details %}
{{item.index}}
{{item.status}}
{{item.message}}
{{item.created_when}}
{% else %}
no data found
no data found
no data found
no data found
{% endfor %}
则当details为空时,表格会展示一行no data found的数据;
我们也可以遍历python字典进行展示,如:
模板:
Jinja2-demo-3
{{title}}
{{title}}
最终结果:{{ status }}
发布日期:{{ created_when }}
KEY
VALUE
{% for key,value in test_dict.items() %}
{{key}}
{{value}}
{% endfor %}
其中,关键代码:{% for key,value in test_dict.items() %}...{% endfor %}
reportUtil.py中新增create_jinja2_demo_3_html方法:
#!/usr/local/bin/python3
# -*- coding:utf-8 -*-
__author__ = "diren"
__date__ = "2021-01-03 15:50"
import os
import time
from jinja2 import Environment, PackageLoader
class ReportUtil:
def __init__(self):
pass
def create_jinja2_demo_1_html(self):
index = 1
status = "成功"
message = "这是一个测试文本"
created_when = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
env = Environment(loader=PackageLoader('resources', 'templates'))
template_name = "jinja2_demo_1.html"
template = env.get_template(template_name)
html = template.render(index=index, message=message, created_when=created_when, status=status)
current_dir = os.getcwd()
output_dir = os.path.join(current_dir, "../resources/output/")
if not os.path.exists(output_dir):
os.mkdir(output_dir)
os.chdir(output_dir)
fo = open(template_name, "w")
fo.writelines(html)
fo.close()
os.chdir(current_dir)
print(f"create {template_name}:", "完成")
def create_jinja2_demo_2_html(self):
title = "测试报告"
status = "失败"
created_when = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
details = [{
"status": "成功",
"message": "测试文本1",
"created_when": time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
}, {
"index": 2,
"status": "失败",
"message": "测试文本2",
"created_when": time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
}, {
"index": 3,
"status": "成功",
"message": "测试文本2",
"created_when": time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
}]
env = Environment(loader=PackageLoader('resources', 'templates'))
template_name = 'jinja2_demo_2.html'
template = env.get_template(template_name)
html = template.render(title=title, status=status, created_when=created_when, details=details)
current_dir = os.getcwd()
output_dir = os.path.join(current_dir, "../resources/output/")
if not os.path.exists(output_dir):
os.mkdir(output_dir)
os.chdir(output_dir)
fo = open(template_name, "w")
fo.writelines(html)
fo.close()
os.chdir(current_dir)
print(f"create {template_name}:", "完成")
def create_jinja2_demo_3_html(self):
title = "人员信息"
status = "采集成功"
created_when = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
test_dict = {
"name": "dylanz",
"gender": "male",
"age": 18,
"professional": "TA"
}
env = Environment(loader=PackageLoader('resources', 'templates'))
template_name = 'jinja2_demo_3.html'
template = env.get_template(template_name)
html = template.render(title=title, status=status, created_when=created_when, test_dict=test_dict)
current_dir = os.getcwd()
output_dir = os.path.join(current_dir, "../resources/output/")
if not os.path.exists(output_dir):
os.mkdir(output_dir)
os.chdir(output_dir)
fo = open(template_name, "w")
fo.writelines(html)
fo.close()
os.chdir(current_dir)
print(f"create {template_name}:", "完成")
if __name__ == "__main__":
ReportUtil().create_jinja2_demo_1_html()
ReportUtil().create_jinja2_demo_2_html()
ReportUtil().create_jinja2_demo_3_html()
效果:
Jinja2中for循环内置常量
细心的同学会发现,在for循环中,我们使用loop.cycle来交替使用单元格样式'odd', 'even',loop.cycle为Jinja2中循环计数内置变量,而Jinja2还拥有其他循环内置常量,有:
篇幅有限,请读者自行了解、尝试哈!
2). 运算符;
+号运算符:可以完成数字相加,字符串相加,列表相加。但是并不推荐使用+运算符来操作字符串,字符串相加应该使用~运算符。
-号运算符:只能针对两个数字相减。
/号运算符:对两个数进行相除。
%号运算符:取余运算。
*号运算符:乘号运算符,并且可以对字符进行相乘。
**号运算符:次幂运算符,比如2**3=8。
in操作符:跟python中的in一样使用,比如{{1 in [1,2,3]}}返回true。
~号运算符:拼接多个字符串,比如{{"Hello" ~ "World"}}将返回HelloWorld。
3). 转义方法;
开启转义:
在模板渲染字符串的时候,字符串有可能包括一些非常危险的字符比如<、>等,这些字符会破坏掉原来HTML标签的结构,更严重的可能会发生XSS跨域脚本攻击,因此如果碰到<、>这些字符的时候,应该转义成HTML能正确表示这些字符的写法。
- 对引用的变量值进行转义:{{ 变量名|e }}或{{ 变量名|escape }}
- 将代码段进行自动转义:{%autoescape true%}...{%endautoescape%},如:
{% autoescape true %}
自动转义已开启
{{ 变量引用值会被自动转义 }}
{% endautoescape %}
关闭转义:
- 对引用的变量值关闭转义:{{变量名|safe }}
- 将代码段关闭自动转义:{%autoescape false%}...{%endautoescape%},如:
{% autoescape false %}
自动转义已关闭
{{ 变量引用值不会被自动转义 }}
{% endautoescape %}
4). 测试器;
测试器主要用来判断一个值是否满足某种类型,语法是:if...is...,例如:
{% if 变量名 is string%}
字符串变量的值为: {{ 变量名 }}
{% else %}
不是字符串变量
{% endif %}
其中string就是测试器。
Jinja2中测试器有:
- callable:是否可调用;
- defined:是否已经被定义了;
- escaped:是否已经被转义了;
- upper:是否全是大写;
- lower:是否全是小写;
- string:是否是一个字符串;
- sequence:是否是一个序列;
- number:是否是一个数字;
- odd:是否是奇数;
- even:是否是偶数;
5). 如何在模版页面中引入静态文件?
在Jinja中加载静态文件只需要通过url_for全局函数就可以实现,例如引入static目录下的style.css文件:
该用法需要在Flask框架下,指定静态文件路径方可,此处开卷有益~
6). 过滤器;
前面提到的几个用法:{{ 变量名|e }}或{{ 变量名|escape }}、{{变量名|safe }},这些其实是利用了Jinja2中的过滤器功能,过滤器是通过“|”符号进行使用的,符号前为过滤器输入,符号后为过滤器函数、方法、规则,意思是变量值不直接进行渲染,而是根据过滤器函数的规则进行转换后,再进行渲染。
过滤器函数可将变量值转成绝对值、大小写、转义等再进行渲染,Jinja2拥有许多过滤器规则,我们一起来学习一下吧:
来源:https://www.cnblogs.com/songyifan427/p/10144506.html
- abs(value):返回一个数值的绝对值,示例:-1|abs;
- default(value,default_value,boolean=false):如果当前变量没有值,则会使用参数中的值来代替。示例:name|default('xiaotuo')——如果name不存在,则会使用xiaotuo来替代;boolean=False默认是在只有这个变量为undefined的时候才会使用default中的值,如果想使用python的形式判断是否为false,则可以传递boolean=true。也可以使用or来替换;
- escape(value)或e:转义字符,会将<、>等符号转义成HTML中的符号。示例:content|escape或content|e;
- first(value):返回一个序列的第一个元素。示例:names|first;
- format(value,*arags,**kwargs):格式化字符串。比如:
{{ "%s" - "%s"|format('Hello?',"Foo!") }}
将输出:Helloo? - Foo!
last(value):返回一个序列的最后一个元素。示例:names|last;
length(value):返回一个序列或者字典的长度。示例:names|length;
join(value,d=u''):将一个序列用d这个参数的值拼接成字符串;
safe(value):如果开启了全局转义,那么safe过滤器会将变量关掉转义。示例:content_html|safe;
int(value):将值转换为int类型;
float(value):将值转换为float类型;
lower(value):将字符串转换为小写;
upper(value):将字符串转换为小写;
replace(value,old,new): 替换将old替换为new的字符串;
truncate(value,length=255,killwords=False):截取length长度的字符串;
striptags(value):删除字符串中所有的HTML标签,如果出现多个空格,将替换成一个空格;
trim:截取字符串前面和后面的空白字符;
string(value):将变量转换成字符串;
wordcount(s):计算一个长字符串中单词的个数;
我们可以将过滤器理解为高级版的运算符!
知识点有点多,再学下去是不是要吐了?
那我们今天就到此为止吧,下一篇,我们再继续学习Jinja2的另外2个重要概念:
-
宏
-
模板继承
看着就很有食欲啊,敬请期待!
如果本文对您有帮助,麻烦动动手指点点赞?
谢谢!