第3章Jinja 2模板引擎
在Flask中通常使用Jinja 2模板引擎来实现复杂的页面渲染。Jinja 2被认为是灵活、快速安全的模板引擎技术,被广泛使用。Jinja 2的设计思想来源于Django模板引擎, 它功能强大、速度 快,并且提供了可选的沙箱模板执行环境安全保证机制,具有沙箱中执行、强大的HTML转义系统、模板继承等诸多优点。本章主要介绍Jinja 2模板引擎的基本结构、基本使用方法。
本章主要涉及的知识点有:
- 如何使用Flask渲染模板;
- 在模板中传递一个或多个参数;
- if语句在模板中的使用;
- for语句在模板中的使用。
3.1模板引擎概述及简单使用
随着不同终端(个人PC、平板电脑,手机、移动穿戴设备等)的兴起,开发人员在越来越多地思考:如何写一份功能代码(业务逻辑代码),这份业务逻辑代码能够在响应式或非响应式设备上都能使用。为了提升开发效率,开发人员开始高度重视前后端的分离,后端负责业务逻辑/数据访问,前端负责表现、交互逻辑,同一份业务逻辑代码可应用于多个不同终端的视图渲染。后端实际上实现的功能一般叫做业务逻辑,前端完成的功能一般叫做表现逻辑。如果把业务逻辑和表现逻辑混在一起,势必造成系统耦合度高、代码维护困难的现象,因此分离业务逻辑和表现逻辑,把变现逻辑交给视图引擎,即网页模板,很有必要。
模板实质上是一个静态的包含HTML语法的全部或片段的文本文件,也可包含由变量表示的动态部分。使用真实值替换网页模板中的变量,生成对应数据的HTML片段,这一过程称为渲染。Flask提供了Jinja 2模板引擎来渲染模板,下面逐步介绍其模板渲染机制。
在PyCharm中新建一名称为3-1的工程,在工程中templates的文件夹下新建index.html文件,代码如下:
在工程中templates的文件夹下新建user.html文件,代码如下:
app.py文件的代码如下:
01行表示导入Flask模块;02行表示导入render_template模块;03行表示Flask初始化;04行定义路由;05行定义视图函数;06行使用render_template()方法渲染模板;07行定义路由;08行定义视图函数;09行使用render_template()方法渲染模板;10行表示当模块被直接运行时,代码将被运行,当模块是被导入时,代码不被执行;11行表示开启调试模式。
运行程序,得到如3.1图所示结果。
Flask通过render_template()函数来实现模板的渲染。要使用Jinja 2模板引擎,需要使用from flask import render_template命令导入render_template函数。在视图函数的return方法中,render_template()函数的首个参数声明使用哪一个模板文件。
3.2向模板中传递参数
Flask提供Jinja 2模板引擎来渲染模板的同时,还可以将程序中的参数或变量值传递给指定的模板进行渲染。
在PyCharm中新建一名称为3-2的工程,在工程中的templates文件夹下新建index.html文件,代码如下:
在工程中templates文件夹下新建user.html文件,代码如下:
app.py文件的代码如下:
01行表示导入Flask模块;02行表示导入render_template模块;03行表示Flask初始化;04行表示定义路由;05行表示定义视图函数;06行渲染模板;07行定义路由;08行表示定义视图函数;09行表示渲染模板并向模板传递参数;10行表示当模块被直接运行时,代码将被运行,当模块被导入时,代码不被执行。
运行程序,得到如图3.2所示结果。
render_template()函数第一个参数是指定模板文件的名称,比如这里的index.html和user.html。render_template( )函数的第二个参数为可选项,可以为空。比如index()视图函数中的render_template('index.html'),这里第二个参数为空。第二个参数不为空的话,一般用于向模板中传递变量。这里传递变量,一般是以键值对方式进行的。
01 @app.route('/')
02 def index():
03 title = 'python的键值对'
04 author='tom_jack'
05 return render_template('index.html', var1=title, var2=author)
用上述代码替换index()视图函数代码,在index.html的body块儿区域增加下面的代码:
01 <body>
02 {{ var1 }}<br> {#br表示网页中的回车#}
03 {{ var2 }}
04 </body>
再次运行程序,得到如图3.3所示结果。
模板中接收变量值,需要把变量放在{{ }},比如{{ var1 }}等。模板中如果要写注释,格式为{# #},比如这里的{#br表示网页中的回车#}。
如果视图函数中有多个变量值都需要传递给模板,可以使用**locals()方法,例如:
01 def index(): #定义index函数
02 # return render_template('index.html')
03 title = 'python的键值对' #定义键值
04 author = 'tom_jack' #定义键值
05 return render_template('index.html', **locals()) #渲染模板并传值
实际上是将return render_template('index.html', var1=title, var2=author)这行代码替换为return render_template('index.html', **locals())。将模板文件index.html中的{{ var1 }}
{{ var2 }}替换为{{ title }}
{{ author }}即可。
注意:在render_template()函数中,如果要给模板传递全部的本地变量,可以使用**locals()方法,此时,在模块中可以直接使用{{title}}和{{author}}来直接使用变量。
3.3模板中的控制语句之if语句
在Jinja 2模板引擎中也可以使用if和for循环控制语句,控制模板渲染的方向。模板引擎中,if和for语句中应该放到{% %}中。
本节我们首先看看模板中的if语句如何使用。在前端的Jinja 2语法中,if可以进行判断:是否存在参数,存在的参数是否满足条件,其基本语法如下:
01 {% if condition %} <!-- condition指的是条件-->
02 {% else %} <!-- 条件不满足时-->
03 {% endif %} <!-- 结束if语句-->
在PyCharm中新建一个名称为3-3的工程。在工程中的templates文件夹下新建index.html文件,代码如下:
app.py对应的代码如下:
import random表示导入Python的随机库,rand1=random.randint(0,1)表示产生0~1范围内的整型数。在模板中进行判断,如果产生的数据为1,视为有效,如果产生的数据为0,视为无效数据。运行本项目代码,结果如图3.4所示。可以多次刷新,看看输出结果有何不同。
在PyCharm中新建一名称为3-4的工程。在工程中的templates文件夹下新建index.html文件,代码如下:
app.py文件内容如下:
运行项目代码,运行结果如图3.5所示。
在模板中,尽量少使用多层嵌套的 if…else…语句,往往会因为缩进出现这样或那样的问题。尽量多用if…elif…else…的结构(即多个elif),这一系列条件判断会从上到下依次判断,如果某个判断为True,执行完对应的代码块,后面的条件判断就会直接忽略,不再执行。
3.4模板中的控制语句之for语句
首先,我们回顾一下Python中的for循环语句。for循环语句是Python中的一个循环控制语句,任何有序的序列对象内的元素都可以遍历,比如字符串、列表、元组等可迭代对像。for循环的语法格式如下:
for 目标 in 对象:
循环体
比如,使用for循环一个字符串,输出字符串中每位字符的操作方法如下:
01 #encoding:utf-8 #指定编码
02 str='www.google.com' #定义字符串
03 for str1 in str: #for循环进行遍历
04 print(str1) #打印输出
运行程序,屏幕上可以输出www.google.com中每一个字符。那么模板中的for循环又该如何使用呢?模板中的for语句定义如下:
01 {% for 目标 in对象 %}
02 <p>目标</p>
03 {% endfor %}
Jinja 2中for循环内置常量:
- loop.index:当前迭代的索引(从1开始);
- loop.index0:当前迭代的索引(从0开始);
- loop.first:是否是第一次迭代,返回True或False;
- loop.last是否是最后一次迭代,返回True或False;
- loop.length:返回序列的长度。
下面以视图函数定义一个字典goods,在模板中使用for循环渲染输出。在PyCharm中新建一名称为3-5的工程。在工程中的templates文件夹下新建shop.html文件,代码如下:
上面的代码实现了一个静态页面,代码中定义了一个表格,表格分为5行2列进行显示,其中,第1行用来显示表格“商品名称”及“商品价格”等内容。
app.py文件的内容如下:
在hello_world视图函数中定义一列表goods,其属性主要有name和price,用for语句将其遍历出来。运行程序,运行结果如图3.6所示。
3.5Flask的过滤器
过滤器本质上是一个转换函数,有时候我们不仅需要输出变量的值,还需要把某个变量的值修改后再显示出来,而在模板中不能直接调用Python中的某些方法,这么这就用到了过滤器。
3.5.1常见过滤器
1.与字符串操作相关的过滤器
<p>{{name|default('None',true)}</p>
其中,name为变量名,如果name为空,则用None这个值去替换name。
-
<p>{{'hello'|capitalize}}</p>
将字符串hello转化成Hello,实现首字母大写的目的。
-
<p>{{'HELLO'|lowere}}</p>
将字符HELLO全部转为小写。
-
<p>{{'hello'|replace('h','x')}}</p>
将hello中的字母h替换成x。
2.对列表进行操作相关的过滤器
-
{{[01,80,42,44,77]|first}}
取得列表中的首个元素01。 -
{{[01,80,42,44,77]|last}}
取得列表中的最后一个元素77。 -
{{[01,80,42,44,77]|count}}
取得列表中的元素个素,统计个数为5,count也可以使用length替换。 -
{{[01,80,42,44,77]|sort}}
列表中的元素重新排序,默认按照升序进行排序。 -
{{[01,80,42,44,77]|join(','}}
将列表中的元素合并为字符串,返回1,80,42,44,77。
3.对数值进行操作相关的过滤器
-
{{18.8888|round}}
四舍五入取得整数,返回19.0。 -
{{18.8888|round(2,’floor’)}}
保留小数点后2位,返回结果为18.88。 {{-2|abs}}
求绝对值运算,返回结果为2。
下面以列表中的每间隔2行换颜色为例,详细说明模板中过滤器的使用方法。
在PyCharm中新建一名称为3-6的工程。在工程中的templates文件夹下新建index.html和app.py文件,index.html文件代码如下:
app.py文件的代码如下:
3.5.2自定义过滤器
内置的过滤器不满足需求怎么办?过滤器的实质就是一个转换函数,我们其实完全可以写出属于自己的自定义过滤器。
通过调用应用程序实例的 add_template_filter 方法实现自定义过滤器。该方法第一个参数是函数名,第二个参数是自定义的过滤器名称。
有一个商品列表页,要求每3行输出一条分割线。在PyCharm中新建一名称为3-7的工程。在工程中的templates文件夹下新建index.html和app.py文件,index.html文件代码如下:
上面的代码对传递过来的列表进行遍历,每3行输出一条分割线,分割线的样式由07~13行所对应的代码定义。
app.py文件的代码如下:
02~05行导入相应模块,有非UTF-8编码范围内的字符时就要使用sys.setdefaultencoding()方法予以修正。04行表示Flask初始化;05行表示定义路由;06行定义视图函数;08~12行定义列表goods;13行表示渲染模板,并向模板传递参数;14行定义函数;15、16行表示每间隔3行返回一个line;19行表示使用自定义过滤器添加CSS。
3.6 宏的定义及使用
Jinja 2中的宏功能有些类似于传统程序语言中的函数,它跟Python中的函数类似,可以传递参数,但是不能有返回值,可以将一些经常用到的代码片段放到宏中,然后把一些不固定的值抽取出来作为一个变量。
3.6.1 宏的定义
宏(Macro),有声明和调用两个部分。让我们先声明一个宏:
01 <!--定义宏-->
02 {% macro input(name, type='text', value= ' ') -%}
03 ????<input type="{{ type }}" name="{{ name }}" value="{{ value|e }}">
04 {%- endmacro %}
上面的代码定义了一个宏,定义宏要加macro,宏定义结束要加endmacro标志。宏的名称就是input,它有3个参数,分别是name、type和value,后两个参数有默认值。我们可以使用表达式来调用这个宏:
01 <!--调用宏-->
02 {{ input('username')}}
03 {{ input('password',type='password')}}
在Pycharm中新建一名为3-8的工程。在工程中templates的文件夹下新建index.html文件,index.html代码如下:
执行网页后,生成对应的代码如下:
上面的代码定义了一个宏,这个宏有3个参数,分别是name、type和value,然后用这个宏定义了两个文本输入框,定义了一个提交按钮。
app.py文件中需要引入模板文件,如下:
01行导入Flask和render_template模块;02行表示Flask初始化;03行表示定义路由;04行表示定义视图函数;05行渲染模板;06行表示当模块被直接运行时,代码将被运行,当模块是被导入时,代码不被执行;07行表示开启调试模式。
运行上面的程序,结果如图3.7所示。
3.6.2 宏的导入
一个宏可以被不同的模板使用,所以我们建议将其声明在一个单独的模板文件中。需要使用时导入即可,而导入的方法类似于Python中的import。我们把3.6.1节中的宏定义部分单独放在一个文件中。
在Pycharm中新建一名称为3-9的工程。在工程中的templates文件夹下新建index.html文件和form.html文件。index.html文件代码如下:
form.html文件代码如下:
上面的代码定义了一个宏,定义宏要加macro,宏定义结束要加endmacro标志。宏的名称就是input,它有3个参数,分别是name、type和value,后两个参数有默认值。
app.py文件的代码如下:
01行表示导入Flask及render_template模块;02行表示Flask初始化;03行表示定义路由;04行定义视图函数;05行渲染模板。
3.6.3 include的使用
宏文件中引用其他宏,可以使用include语句。include语句可以把一个模板引入到另外一个模板中,类似于把一个模板的代码复制到另外一个模板的指定位置。下面通过一个实例来说明。
在PyCharm中新建一个名为3-10的工程。在工程中的templates文件夹下新建index.html文件、header.html文件及footer.html文件。index.html文件代码如下:
header.html文件内容如下:
footer文件内容如下:
app.py文件内容如下:
01行表示导入Flask及render_template模块;02行表示Flask初始化;03行定义路由;04行定义视图函数;05行表示渲染模板。
include把一个模板的代码复制到另外一个模板的指定位置,这里的{% include ''header.html'' %}和{% include ''footer.html'' %}把头文件和尾部文件引入到index.html文件中。
3.7 set和with语句的使用
set与with语句都可以在Jinja 2中定义变量并赋予值。set定义的变量在整个模板范围内都有效,with关键字在定义变量并赋值的同时,限制了with定义变量的作用范围。
首先介绍一下set关键字的使用方法:
(1)给变量赋值:
{% set telephone ='1388888888' %}
(2)给列表或数组赋值:
{% set nav = [('index.html', 'index'), ('product.html', 'product)] %}
可以在模板中使用{{ telephone }}和{{ nav }}来引用这些定义的变量。
接下来介绍with关键字的使用方法,例如:
{% with pass = 60 %}
{{ pass }}
{% endwith %}
with定义的变量的作用范围在{% with %}和{% endwith %}代码块内,在模板的其他地方,引用此变量值无效。
在PyCharm中新建一名为3-11的工程。在工程中templates的文件夹下新建index.html文件,index.html文件代码如下:
app.py文件内容如下:
运行上面的工程,结果如图3.8所示。
3.8 静态文件的加载
静态文件的加载一般需要先新建文件夹static,在文件夹下再新建css、js和images文件夹,在这些文件夹中存放css、js、images,同时要需要使用'url_for'函数。
在PyCharm中新建一个名为3-12的工程。找到static文件夹,在此文件夹下再新建css、js和images这3个文件夹,目录结构如图3.9所示。
在templates目录下新建一个名为index.html的文件, 在app.py的视图函数中使用return render_template('index.html')方法来渲染模板。下面分别给出加载JS、图片和CSS的方法。
(1)加载JS文件
在静态文件index.html中,在之前引入jquery-3.3.1.js文件,具体代码如下:
<script src="{{ url_for('static', filename='js/jquery-3.3.1/jquery-3.3.1.js') }}">
</script>
可以使用下面代码测试jquery-3.3.1.js文件是否加载成功。
<script>
if(jQuery) {
alert('jQuery已加载!');
}
else {
alert('jQuery未加载!');
}
</script>
通过测试,可以发现通过上述方法可以正常加载js文件。这里使用到了url_for()函数来实现。事实上,还可以通过下面代码实现:
<script type="text/javascript" src="static/js/jquery-3.3.1/jquery-3.3.1.js">
</script>
不过,一般建议使用url_for()函数形式。
(2)加载图片文件。
加载图片,可以使用下述代码实现:
<img src="{{ url_for('static', filename='images/car.jpg') }}"></img>
(3)加载CSS文件。
加载外部CSS文件,可以使用下述代码实现:
<link rel="stylesheet" href="{{ url_for('static',filename='css/car.css') }}">
car.css的代码如下:
.img{
BORDER-RIGHT: #000 1px solid; BORDER-TOP: #000 1px solid; MARGIN: 10px 0px; BORDER-LEFT: #000 1px solid; BORDER-BOTTOM: #000 1px solid
}
在index.html文件中添加如下代码:
app.py文件的代码如下:
运行上述代码,效果如图3.10所示。
3.9 模板的继承
一个系统网站往往需要统一的结构,这样看起来比较“整洁”。比如,一个页面中都有标题、内容显示、底部等几个部分。如果在每一个网页中都进行这几部分的编写,那么整个网站将会有很多冗余部分,而且编写的网页程序也不美观。这时可以采用模板继承,即将相同的部分提取出来,形成一个base.html,具有这些相同部分的网页通过继承base.html来得到对应的模块。
1.模板的继承语法
模板的继承语法如下:
{% extends “模板名称” %}
2.块的概念
模板继承包含基本模板和子模板。其中,基本模板里包含了网站里基本元素的基本骨架,但是里面有一些空的或不完善的块(block)需要用子模板来填充。
在父模板中:
…
{% block block的名称 %}
{% endblock %}
…
在子模板中:
…
{% block block的名称 %}
子模板中代码
{% endblock %}
…
在PyCharm中新建一个名为3-13的工程。在templates目录中创建index.html、base.html和product.html 3个静态文件。base.html文件作为基类,index.html和product.html文件作为子类,子类去继承基类的基本内容。
base.html文件内容如下:
index.html文件内容如下:
product.html文件的内容:
app.py文件内容如下:
默认情况下,子模板如果实现了父模板定义的block,那么子模板block中的代码就会覆盖父模板中的代码。如果想要在子模板中仍然保持父摸板中的代码,那么可以使用{{super()}}来实现,如index.html中{% block body %}{% endblock %}代码块中使用了{{ super() }}方法,运行结果如图3.11所示:
如果想要在一个block中调用其他block中的代码,可以通过{{self.其他block名称()}}实现。比如product.html文件中的
{{ self.title() }}
方法。运行此代码,结果如图3.12所示。3.10 温 故 知 新
1.学完本章内容后,读者需要回答:
(1)什么是模板?
(2)模板中如何写注释?
(3)模板中如何使用if语句?
(4)模板中如何使用for语句?
2.在下一章中将会学习:
(1)路由函数的使用。
(2)装饰器的基本使用。
(3)蓝图的定义和基本使用。
3.11 习 题
通过下面的习题来检验本章的学习情况,习题答案请参考本书配套资源。
1.使用for语句,新建一个工程,打印出九九乘法表。
2.在视图函数中定义一个字典books,请在模板中遍历出字典的所有属性。