Python模板引擎Jinja2(一)

前言

来啦老铁!

笔者近期在工作中遇到将测试数据可视化的需求,且在Python语言背景下,当时借用的是团队既有经验,即:

  • 模板引擎技术

而模板引擎使用:Jinja2

看过笔者的Spring Boot全家桶系列文章的同学一定不会陌生,我们曾在文章Spring Boot视图技术中,一起学习了java体系下的视图技术(模板引擎技术),咱们今天也来学习学习Python体系下的模板引擎技术吧!

学习路径

  1. 常见的Python模板引擎有哪些?
  2. Jinja2简介;
  3. Jinja2入门使用;
  4. 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文件:

渲染html

浏览器打开resources/output/jinja2_demo_1.html文件:

浏览器访问html

嗯,没错,原来{{}}符号包裹的变量,均变成我们reportUtil.py代码中想渲染的真实值了,渲染演示成功!是不是很简单呀!

上述,演示了一个非常简单的Jinja2使用方法,接下来我们一起来边学习Jinja2的常用功能,边码代码、边演示、边学习吧!

4. Jinja2的基础知识点;

这里罗列一下基础知识点:

  • 基本语法;
  • 运算符;
  • 转义方法;
  • 测试器;
  • 如何在模版页面中引入静态文件?
  • 过滤器;
  • 宏;
  • 模板继承;

1). 基本语法;

  • 注释 {#...#}
    例如:{#这是一个注释#},这一行代码是不会被渲染、处理的,起注释作用;

  • 变量取值 {{...}}

如上面的例子, {{}}是占位符,Jinja2渲染时会将这些占位符替换成真实值。它支持python中的所有数据类型,如字符串、字典、列表等。

  • 控制结构 {% ... %}

该符号包裹的内容表示控制结构,如分支、循环,我们来演示一下:

(1). if语句:



    
    Jinja2-demo-1




    {#这是一个注释#}
    
    {% if index==1 %}
    
    {% elif index==2 %}
    
    {% else %}
    
    {% endif %}
    
INDEX STATUS MESSAGE CREATED_WHEN
{{index}} {{status}} {{message}} {{created_when}}
{{index}} {{status}}
{{index}} {{status}} {{message}}

很简单,当index为1,2,其他值时,表格显示的内容略有差异:

index为1时
index为2时
index为其他值时
(2). for语句:

我们使用for语句,演示渲染一个含有多条数据的表格,并且为了演示效果,我顺便从网上找了个样式,稍作调整,创建新的模板文件jinja2_demo_2.html:




    
    Jinja2-demo-2


{{title}}
{{title}}
最终结果:{{ status }}
发布日期:{{ created_when }}

{% for item in details %} {% endfor %}
INDEX STATUS MESSAGE CREATED_WHEN
{{item.index}} {{item.status}} {{item.message}} {{item.created_when}}

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后:
渲染新html
for语句渲染效果

这样,我们顺利将多条数据渲染进模板进行展示,数据为:

        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的数据;

details为空时
我们也可以遍历python字典进行展示,如:

模板:




    
    Jinja2-demo-3


{{title}}
{{title}}
最终结果:{{ status }}
发布日期:{{ created_when }}

{% for key,value in test_dict.items() %} {% endfor %}
KEY VALUE
{{key}} {{value}}

其中,关键代码:{% 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还拥有其他循环内置常量,有:

Jinja2中for循环内置常量-来自网络

篇幅有限,请读者自行了解、尝试哈!

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文件: