✅【Flask笔记】第三节:模版
旧的笔记,以前存放的笔记软件停止运营了,重新迁移到博客上,删除和修改一些过时的内容。
0x00 模版基础用法
1. 创建模版
Jinja2常见的三种定界符:
语句:比如if
判断,for
循环等。
1 | {%...%} |
表达式:比如字符串,变量,函数调用等。
1 | {{...}} |
注释。
1 | {#...#} |
2. 模版语法
利用Jinja2这样等模版引擎,可以将一部分程序逻辑放在模版中,但是Jinja2并不支持所有的Python语法,我们应该适度使用模版,仅把和输出控制相关的逻辑操作放到模版中。
Jinja2允许在模版中使用大量Python对象,比如字符串,列表,字典,元组,整型,浮点型,布尔值。它支持基本的运算符号(+、-、*、/等)、比较符号(比如==、!=等)、逻辑符号(and、or、not和括号)以及in、is、None和布尔值(Ture、False)。
Jinja2提供了多种控制结构,其中for和if是最常用的两种:
1 | {% if user.bio %} |
另外在模版中,Jinja2支持使用.
获取变量属性。比如user字典中的username可以通过.
获取,等同于user['username']
在for
循环内,Jinja2提供了多个特殊变量,常用的Jinja2循环变量如下:
变量名 | 说明 |
---|---|
loop.index | 当前迭代数(从1开始计算) |
loop.index0 | 当前迭代数(从0开始计算) |
loop.revindex | 当前反向迭代数(从1开始计算) |
loop.revindex0 | 当前反向迭代数(从0开始计算) |
loop.first | 如果是第一个元素,则为True |
loop.last | 如果是最后一个元素,则为True |
loop.previtem | 上一个迭代条目 |
loop.nextitem | 下一个迭代条目 |
loop.length | 序列包含的元素数量 |
3. 渲染模版
渲染模版就是执行模版中代码,渲染后的结果就是我们要返回给客户的的HTML响应。在视图函数中渲染模版并不知节使用jinja2提供的函数,而是使用Flash中的render_template()
。在render_template()
函数中,Flask会在程序的根目录下的templates
文件夹里寻找模版文件。同时还以关键字参数的形式传入模版中使用的变量值。
1 | from flask import Flask,render_template |
除了
render_template()
函数,Flask还提供了一个render_template_string()
函数用来渲染模版字符串
0x01 模版辅助工具
1. 上下文
在模版的上下文中包含很多变量,其中包括渲染模版时候render_template()
函数中传递的变量以及Flask默认传入的变量。
模版中也可以定义变量:
1 | {% set navigation = [('/','Home'),('/about','About')]%} |
也可以将一部分模版数据定义为变量,使用set
和endset
标签声明开始和结束:
1 | {% set navigation%} |
1.1 内置上下文变量
Flask在模版上下文中提供了一些内置变量,可以在模版中直接使用。
变量 | 说明 |
---|---|
config | 当前配置对象 |
request | 当前请求对象,在已激活的请求环境下使用 |
session | 当前会话对象,在已激活的请求环境下使用 |
g | 与请求绑定的全局变量,在已激活的请求环境下使用 |
1.2 自定义上下文
如果多个模版都需要使用同一个变量,那么比起在多个视图函数中重复传入,更好的方法是能够设置一个模版全局变量。使用@app.context_processor
注册模版上下文处理函数,它可以帮助我们完成统一传入变量的工作:
1 |
|
当我们调用render_template()
函数渲染任意一个模版时,所有使用@app.context_processor
装饰器的模版上下文处理函数都会被执行,这些函数的返回值会被添加到模版中。因此我们可以在模版中直接使用foo
变量。
除了使用@app.context_processor
装饰器,也可以直接将其作为方法调用,传入模版上下文处理函数:
1 | def inject_foo(): |
使用lambda简化:
1 | app.context_processor(lambda:dict(foo='I am foo.')) |
2. 全局对象
全局对象是指在所有模版中都可以直接使用的对象。
2.1 内置全局函数
Jinja2常用的内置模版全局函数:
函数 | 说明 |
---|---|
range([start,]stop[,step]) | 和Python中的range()用法相同 |
lipsum(n=5,html=True,min=20,max=100) | 生成随机文本(lorem ipsum)可以在测试时用来填充页面,默认生成5段HTML文本,每段包含20~100个单词 |
dict(**items) | 和Python中dict()用法相同 |
Flask内置模版全局函数:
函数 | 说明 |
---|---|
url_for() | 用于生成URL的函数 |
get_flashed_messages() | 用于获取flash消息的函数 |
Flask除了把g,session,config,request对象注册为上下文变量,也将它们设为全局变量,因此可以全局使用url_for()。
2.2 自定义全局函数
除了使用@app.context_processor
注册模版上下文处理函数来传入函数,也可以使用app.template_global
装饰器可以直接将函数注册为模版全局函数。默认使用函数的原名称传入模版,在app.template_global()
装饰器中使用name
参数可以指定一个自定义名称,app.template_global
仅能用于注册全局函数。
1 |
|
你可以直接使用
app.add_template_global()
方法注册自定义全局函数,传入函数对象和可选的自定义名称(name)
,比如app.add_template_global(your_global_function)
3. 过滤器
在Jinja2中,过滤器是一些可以修改和过滤变量的特殊函数。
两种过滤器的写法:
1 | {{ name|title }} #将name变量的值标题化。相当于Python中的name.title() |
1 | #使用filter和endfilter标签声明开始和结束,upper过滤器将一段文字转换为大写。 |
3.1 内置过滤器
Jinja2常用的内置过滤器:
过滤器 | 说明 |
---|---|
default(value,default_value = ‘’,boolean=False) | 设置默认值,默认值作为参数传入,别名为d |
escape(s) | 转义HTML文本,别名为e |
first(seq) | 返回序列的第一个元素 |
last(seq) | 返回序列的最后一个元素 |
length(object) | 返回变量的长度 |
random(seq) | 返回序列中的随机元素 |
safe(value) | 将变量标记为安全,避免转义 |
trim(value) | 清楚变量值前后的空格 |
max(value,case_sensitive=False,attribute=None) | 返回序列中最大的值 |
min(value,case_sensitive=False,attribute=None) | 返回序列中最小的值 |
unique(value,case_sensitive=False,attribute=None) | 返回序列中不重复的值 |
striptags(value) | 清楚变量值内的HTML标签 |
urlize(value,trim_url_limit=None,nofollow=False,target=None,rel=None) | 将URL文本转换成可单击的HTML链接 |
wordcount(s) | 计算单词数量 |
tojson(value,indent=None) | 将变量值转换为JSON格式 |
truncate(s,length=255,killwords=False,end=’…’,leeway=None) | 截断字符串,常用于显示文章摘要length参数设置截断的长度,killwords参数设置是否截断单词,end参数设置结尾符号 |
在使用过滤器时,列表中过滤器函数的第一个参数表示被过滤的变量值(Value)
或字符串(s)
,即竖线符号左侧的值,其他参数可以通过添加括号传入。
过滤器可以叠加使用:
1 | <h1>Hello,{{name|default('陌生人')|title}}</h1> |
XSS相关
jinja2会自动对模版中的变量进行转义,所以不需要手动使用escape过滤器或调用escape()函数对变量进行转义。默认情况下,仅对.html
、.htm
、.xml
、.xhtml
后缀名进行转义。
如果像避免转义可以使用safe过滤器{{ sanitized_text|safe }}
,另一种将文本标记安全的方法是在渲染前将变量转换为Markup
对象:
1 | from flask import Markup |
这时在模版中可以直接使用{{ text }}
3.2 自定义过滤器
使用app.template_filter()
装饰器可以注册自定义的过滤器:
1 | from flask import Markup |
和注册全局函数类型,你可以在app.template_filter()
中使用name
关键字设置过滤器的名称,默认会使用函数名称。
1 | {{ name|musical}} |
你可以直接使用
app.add_template_filter()
方法注册自定义过滤器,传入函数对象和可选的自定义名称(name)
,比如app.add_template_filter()(your_filter_function)
4. 测试器
在Jinja2中,测试器是一些用来测试变量或表达式,返回布尔值的特殊函数。
1 | {% if age is number %} |
4.1 内置测试器
常用的内置测试器:
测试器 | 说明 |
---|---|
callable(object) | 判断变量是否可被调用 |
defined(value) | 判断变量是否已定义 |
undefined(value) | 判断变量是否为定义 |
none(value) | 判断变量是否是否为None |
number(value) | 判断变量是否为数字 |
string(value) | 判断变量是否为字符串 |
sequence(value) | 判断变量是否为序列,比如字符串,列表,元组 |
iterable(value) | 判断变量是否可迭代 |
mapping(value) | 判断变量是否匹配对象,比如字典 |
sameas(value,other) | 判断变量与other是否指向相同的内存地址 |
在使用测试器时,is
左侧是测试器函数的第一个参数(value)
,其他参数可以添加括号传入,也可以在右侧使用空格链接:
1 | {% if foo is sameas(bar) %} |
等同于:
1 | {% if foo is sameas bar %} |
4.2 自定义测试器
可以使用Flask提供的app.template_test()
装饰器来注册一个自定义测试器:
1 | @app.template_test() |
测试器的名称默认为函数名称,你可以在app.template_test()
中使用name
关键字指定自定义名称。测试器函数需要接受被测试的值作为输入,返回布尔值。
你可以直接使用
app.add_template_test()
方法注册自定义测试器,传入函数对象和可选的自定义名称(name)
,比如app.add_template_test()(your_test_function)
5. 模版环境对象
在Jinja2中渲染行为由jinja2.Environment类控制,所有配置选项,上下文变量,全局函数,过滤器,测试器都存储在Environment的实例上。当与Flask结合后,不再单独创建Environment对象。而是使用Flask创建的Environment对象。它存储在app.jinja_env属性上。
例如,可以自定义jinja2的变量定界符:
1 | app = Flask(__name) |
实际开发中没必要修改。
模版环境中的全局函数,过滤器,测试器分别存储在Environment对象的globals、filters和tests属性中,这三个属性都是字典对象,除了使用Flask装饰器和方法自定义函数,也可以直接操作这三个字典来添加对应的函数或变量。
注意:以下内容选修,实际开发过程中基本不用用到。
5.1 添加自定义全局对象
直接操作globals字典允许我们传入任意python对象。下面代码使用app.jinja_env.globals
添加全局函数和全局变量
1 | def bar(): |
5.2 添加自定义过滤器
使用app.jinja_env.filters
添加自定义过滤器。
1 | def smiling(s): |
5.3 添加自定义测试器
使用app.jinja_env.tests
添加自定义测试器。
1 | def baz(n): |
0x02 模版组织结构
除了使用函数,过滤器等工具控制模版输出外,Jinja2还提供了一些工具来宏观上组织模版内容。
1. 局部模版
当多个独立模版中都会使用同一块HTML代码时,我们可以把这部分代码抽离出来,存储到局部模版中,达到一个复用代码等效果。我们用include
标签插入一个局部模版,这会把局部模版的全部内容插在使用include
标签的位置。
1 | {% include '_banner.html' %} |
为了和普通模版区分开,局部模版命名通常以一个下划线开始。
当程序中某个视图用来处理Ajax请求时,返回数据不需要包含完整的HTML结构,这时可以返回渲染后的局部模版。
2. 宏(macro)
宏类似Python中的函数,用宏可以把一部分模版封装到宏里,使用传递参数来构建内容。为了方便管理,我们可以把宏存储在单独的文件中,这个文件通常命名为macros.html
或_macros.html
。
1 | {% macros qux(amount=1) %} |
使用时需要像从Python模块中导入函数一样使用import
语句导入它,然后作为函数调用,传入必要参数。
1 | {% from 'macros.html' import qux %} |
出于性能的考虑,include
一个局部模版会传递当前上下文,但是import
却不会。
例如,当使用render_template()函数渲染一个foo.html模版时候,foo.html的模版上下文包含下列对象:
- Flask使用内置模版上下文处理函数提供的g,session,config,request。
- 扩展使用内置的模版上下文处理函数提供的变量。
- 自定义模版上下文处理器传入的变量。
- 使用render_template()函数传入的变量。
- Jinja2和Flask内置及自定义的全局对象。
- Jinja2内置和自定义的过滤器。
- Jinja2内置和自定义的测试器。
使用include标签插入的局部模版同样地可以使用上述上下文中的变量和函数,而import的模版(例如宏),仅包含下列这些对象:
- Jinja2和Flask内置及自定义的全局对象。
- Jinja2内置和自定义的过滤器。
- Jinja2内置和自定义的测试器。
如果我们想使用2,3,4项,就需要在导入的时候显式地使用with context
声明,传入当前模版上下文:
1 | {% from 'macros.html' import qux with context %} |
在
app.jinja_env.globals
中将g、session、config和request设置为了全局变量,所以仍然可以不用显式声明,而在宏中直接使用它们。
3. 模版继承
在jinja2中还可以使用继承方式,减少重复的代码。例如可以把导航,页脚这些通用内容放在基模版中,而每一个继承的子模版在被渲染时候都会包含这些。
3.1 编写基模版
基模版存储了程序页面固定部分,通常被命名为base.html
或layout.html
1 | <!DOCTYPE html> |
为了避免块的混乱,块结束的标签可以指明块名:
1 | {% block body %} |
3.2 编写子模版
1 | {% extends 'base.html' %} |
使用extends标签声明继承自base.html模版,并且extends必须是子模版的第一个标签。
在子模版中可以对父模版中的块执行两种操作:
覆盖内容
在子模版中创建同名的块,可以覆盖父模版中同名的块。
追加内容
使用super()函数可以追加内容。
1 | {% block styles %} |
0x03 模版进阶
1. 空白控制
在实际输出的HTML文件中,模版中的jinja2语句会被渲染为空行,仍然占有位置,例如:
1 | <div> |
实际输出:
1 | <div> |
如果想要在去掉这些空行,可以在定界符内添加减号,比如{%- endfor%}
会移除该语句的前的空白。
1 | <div> |
添加减号后输出如下:
1 | <div> |
另一种方法是使用模版环境对象:
1 | app.jinja_env.trim_blocks = True |
宏内空白不受这两个属性控制。
实际上我们没必要严格控制HTML输出,在部署的时候也可以使用工具来去除。
2. 静态文件
在Flask中默认需要将静态文件存储在与主脚本同级目录的static文件夹中。
1 | < img src="{{ url_for('static', filename='avatar.jpg') }}" width="50"> |
在URL中图片的访问路径是http://localhost:5000/static/avatar.jpg
另外也可以通过实例化Flask类时候,指定static_folder参数来自定义存储目录,URL路径会跟随文件夹的名称变化。也可以独立指定static_url_path参数,来自定义URL路径。
例如:
1 | app = Flask(__name__, static_folder='assets') |
存储的文件夹是assets,URL访问路径是:http://localhost:5000/static/avatar.jpg
1 | app = Flask(__name__, static_folder='assets', static_url_path='/static') |
同时修改static_folder参数和static_url_path参数,存储的文件夹是assets,URL访问路径是:http://localhost:5000/static/avatar.jpg
3. 消息闪现
Flask提供一个非常有用的函数flash()函数,比如当用户登录成功后,显示“欢迎回来!”。flash()函数发送的消息会存储在session中,我们需要在模版中使用全局函数get_flashed_message()获取消息并将其显示出来。
通过flash()发送的消息存储在session中。需要对程序设置密钥,可以通过app.secert_key属性或配置变量SECERT_KEY设置。
1 | from flask import Flask, render_template, flash |
Flask中使用get_flashed_messages()函数在模版中获取消息,因为程序中每一个页面都有可能需要显示消息,所以我们把显示消息的代码放在基版本中,因为同一个页面可能包含多条要显示的消息,所以这里使用for循环迭代get_flashed_messages()返回的消息列表。
1 | <main> |
4. 自定义错误页面
可以通过注册错误处理函数来自定义错误页面,错误处理函数和视图函数很类似,返回值将会作为响应主体。
先创建错误页面的模版文件:
1 | {% extends 'base.html' %} |
错误处理函数需要附加app.errorhandler()装饰器,并传入错误状态码作为参数。错误处理函数本身需要接收异常类作为参数,并在返回值中注明对应的HTTP状态码。
1 | from flask import Flask, render_template |
另外也可以使用app.errorhandler()装饰器为其他异常注册处理函数,并返回自定义响应,只需要在app.errorhandler()传入对应的异常类即可。
错误处理函数接收异常对象e
作为参数,内置的异常对象提供了以下常用的属性:
属性 | 说明 |
---|---|
code | 状态码 |
name | 原因短语 |
description | 错误描述,另外使用get_description()方法还可以获取HTML格式的错误描述代码 |
5. JS和CSS中的Jinja2
有时候我们会需要在JS和CSS中使用Jinja2提供的变量值,甚至是控制语句。比如通过传入模版的theme_color来控制页面主题色彩,或是根据用户是否登录来决定是否执行某个JS。
首先要知道只有使用render_template()传入的模版文件才会被渲染,如果你把jinja2代码写在单独的JS或CSS文件中,尽管你在HTML中引用了,但是其中的jinja2代码永远不会执行。
5.1 定义JS/CSS变量
想要在JS中获取数据,可以通过HTML元素的data-*
属性存储,你可以自定义横线后面的名称,作为自定义数据。
1 | <span data-id="{{ user.id }}" data-username="{{ user.username }}">{{ user.username }}</span> |
在JS中可以使用DOM元素的dataset属性获取data-*
的值,例如:element.dataset.username
,或使用getAttribute()方法:element.getAttribute('data-username')
;使用JQuery时,可以直接对JQuery对象调用data方法,比如$element.data('username')
。
如果是需要全局使用的数据,可以在页面中嵌入JS定义变量。
1 | <script type="text/javascript"> |
CSS同理:
1 | <style> |
在CSS文件中使用var()函数传入变量名即可获取对应的值:
1 | #foo { |