在前面的示例中,视图函数的主要作用是生成请求的响应,这是最简单的请求。实际上,视图函数有两个作用:处理业务逻辑和返回响应内容。在大型应用中,把业务逻辑和表现内容放在一起,会增加代码的复杂度和维护成本。本节学到的模板,它的作用即是承担视图函数的另一个作用,即返回响应内容。 模板其实是一个包含响应文本的文件,其中用占位符(变量)表示动态部分,告诉模板引擎其具体值需要从使用的数据中获取。使用真实值替换变量,再返回最终得到的字符串,这个过程称为“渲染”。Flask使用Jinja2这个模板引擎来渲染模板。Jinja2能识别所有类型的变量,包括{}。 Jinja2模板引擎,Flask提供的render_template函数封装了该模板引擎,render_template函数的第一个参数是要渲染的模板的文件名,后面的参数可以是键值对,也可以是**kwargs,均能将变量对应的真实值传递给模板。。
我们先来认识下模板的基本语法:
{% if user %} {{ user }} {% else %} hello! <ul> {% for index in indexs %} <li> {{ index }} </li> {% endfor %} </ul>
一、变量
Jinja2 模版中的变量代码块可以是任意 Python 类型或者对象,只要它能够被 Python 的 str() 方法转换为一个字符串就可以。
视图代码
from flask import Flask, render_template app = Flask(__name__) @app.route("/") def index(): """ "index.html"表示要渲染的模板 两种传递参数值的方式: 1、键值对 2、不定长字段参数**kwargs """ data = { "name2": "李四", "age2": 19 } my_dict = { "name": "王五", "age": 21 } my_list = [1, 2, 3, 4, 5, 6, 7] list_index = 1 return render_template("index.html", # 键值对 name1="张三", age1=18, # 不定长参数 **data, # 参数值除了可以是string和int类型外,还可以是dict,list等类型,只要这个类型能被 str() 方法转换为一个字符串就可以。 # dict类型 mydict=my_dict, # list类型 mylist=my_list, index=list_index ) if __name__ == '__main__': app.run()
模板代码
模板放在templates目录下,名字为index.html。使用 {{ 参数名 }} 的方式进行插值操作。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <!-- string 和 int取值方式 --> <div>{{ name1 }}:{{ age1 }}</div> <div>{{ name2 }}:{{ age2 }}</div> <!-- dict取值方式 --> <div>{{ mydict }}</div> <div>{{ mydict["name"]}}:{{ mydict.age }}</div> <!-- list取值方式 --> <div>{{ mylist }}</div> <div>{{ mylist[0] }}</div> <div>{{ mylist[index] }}</div> </body> </html>
渲染效果
二、过滤器
过滤器的本质就是函数。有时候我们不仅仅只是需要输出变量的值,我们还需要修改变量的显示,甚至格式化、运算等等,而在模板中是不能直接调用 Python 中的某些方法,那么这就用到了过滤器。
过滤器的使用方式为:变量名 | 过滤器。 过滤器名写在变量名后面,中间用 | 分隔。如:{{variable | capitalize}},这个过滤器的作用:把变量variable的值的首字母转换为大写,其他字母转换为小写。
字符串过滤器
safe:禁用转义html标签 <p>{{ '<em>hello</em>' | safe }}</p> capitalize:把变量值的首字母转成大写,其余字母转小写 <p>{{ 'hello' | capitalize }}</p> lower:把值转成小写 <p>{{ 'HELLO' | lower }}</p> upper:把值转成大写 <p>{{ 'hello' | upper }}</p> title:把值中的每个单词的首字母都转成大写 <p>{{ 'hello' | title }}</p> reverse:字符串反转 <p>{{ 'olleh' | reverse }}</p> format:格式化输出 <p>{{ '%s is %d' | format('name',17) }}</p> striptags:渲染之前把值中所有的HTML标签都删掉 <p>{{ '<em>hello</em>' | striptags }}</p> truncate: 字符串截断 <p>{{ 'hello every one' | truncate(9)}}</p>
列表过滤器
irst:取第一个元素 <p>{{ [1,2,3,4,5,6] | first }}</p> last:取最后一个元素 <p>{{ [1,2,3,4,5,6] | last }}</p> length:获取列表长度 <p>{{ [1,2,3,4,5,6] | length }}</p> sum:列表求和 <p>{{ [1,2,3,4,5,6] | sum }}</p> sort:列表排序 <p>{{ [6,2,3,1,5,4] | sort }}</p>
语句块过滤(不常用)
{% filter upper %}
this is a Flask Jinja2 introduction
{% endfilter %}
自定义过滤器
过滤器的本质是函数。当模板内置的过滤器不能满足需求,可以自定义过滤器。
自定义过滤器有两种实现方式:
- 使用Flask应用对象的add_template_filter方法
- 使用Flask应用对象的template_filter装饰器
注意,自定义的过滤器名称如果和内置的过滤器重名,会覆盖内置的过滤器。
实现方式一:通过调用应用程序实例的add_template_filter方法实现自定义过滤器。该方法第一个参数是函数名,第二个参数是自定义的过滤器名称。
# 间隔截取列表 def filter_double_sort(ls): return ls[::2] # 第一个参数为过滤器函数, # 第二个参数为模板中使用的过滤器名字) app.add_template_filter(filter_double_sort, 'double_2')
实现方式二:使用Flask应用对象的template_filter装饰器实现自定义过滤器。装饰器传入的参数是自定义的过滤器名称。
# 装饰器中的参数为模板中使用的过滤器名字 @app.template_filter('db2') def filter_double_sort(ls): return ls[::2]
使用自定义过滤器
<p>add_template_filter函数:{{ mylist | double_2 }}</p> <p>template_filter装饰器:{{ mylist | db2}}</p>
三、Flask-WTF表单扩展
在介绍Flask-WTF表单扩展前,我们先不使用Flask-WTF简单实现一个注册表单:
模板文件如下:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <!-- form标签不使用action属性时,发送的请求被当前视图函数接收 --> <form method='post'> <div><input type="text" name="username" placeholder='用户名'></div> <div><input type="password" name="password" placeholder='密码'></div> <div><input type="password" name="password2" placeholder='确认密码'></div> <input type="submit"> </form> </body> </html>
视图函数如下:
from flask import Flask, render_template, request, session, redirect, url_for app = Flask(__name__) app.config["SECRET_KEY"] = "asd" @app.route("/login/") def login(): return F"{session.get('user')} 注册成功" @app.route("/register/", methods=["get", "post"]) def register(): if request.method == "POST": username = request.form.get("username") password = request.form.get("password") password2 = request.form.get("password2") session["user"] = username return redirect(url_for("login")) else: return render_template("register.html") if __name__ == '__main__': app.run()
测试效果:
上图中,即使密码与确认密码不一致,但仍能注册成功,这是因为我们没有在视图中验证表单的数据,为了保证参数的合规性,我们需要对每个使用if来进行校验,这是很繁琐的。
而Flask-WTF扩展不仅可以帮助我们在视图中验证表单的数据,还可以使用该扩展进行CSRF验证,帮助我们快速定义表单模板。
使用Flask-WTF表单扩展,需要自行安装,安装命令如下:
pip install flask-wtf
并且还需要配置参数SECRET_KEY,当CSRF(跨站请求伪造)保护激活的时候,CSRF_ENABLED设置会根据设置的密匙生成加密令牌。
接下来,我们使用Flask-WTF实现表单。
模板文件如下:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <form method="post"> <!-- 设置csrf_token --> {{ form.csrf_token }} {{ form.user_name.label }} <p>{{form.user_name}}</p> {% for msg in form.user_name.errors %} <p>{{msg}}</p> {% endfor %} {{ form.password.label }} <p>{{form.password}}</p> {% for msg in form.password.errors %} <p>{{msg}}</p> {% endfor %} {{ form.password2.label }} <p>{{form.password2}}</p> {% for msg in form.password2.errors %} <p>{{msg}}</p> {% endfor %} {{form.submit}} </form> </body> </html>
视图函数如下:
from flask import Flask,render_template, redirect,url_for,session,request,flash # 导入wtf扩展的表单类 from flask_wtf import FlaskForm # 导入自定义表单需要的字段 from wtforms import SubmitField, StringField, PasswordField # 导入wtf扩展提供的表单验证器的验证函数 from wtforms.validators import DataRequired, EqualTo app = Flask(__name__) app.config['SECRET_KEY'] = 'asd' @app.route("/login/") def login(): return F"{session.get('user')} 注册成功" # 定义注册表单类,该类继承FlaskForm class RegisterForm(FlaskForm): # 字段类型: # StringField 对应 type="text" # PasswordField 对应 type="password" # SubmitField 对应 type="submit" # 字段参数说明: # label:字段的名称 # validators:验证器 # DataRequired(message):验证数据不能为空。message为校验不通过的提示 # EqualTo(fieldname, message):验证与指定字段fieldname的值相等,message为校验不通过的提示。 user_name = StringField(label=u"用户名", validators=[DataRequired(u"用户名不能为空")]) password = PasswordField(label=u"密码", validators=[DataRequired(u"密码不能为空")]) password2 = PasswordField(label=u"确认密码", validators=[DataRequired(u"确认密码不能为空"), EqualTo("password", u"两次密码不一致")]) submit = SubmitField(label=u"提交") # 定义根路由视图函数,生成表单对象,获取表单数据,进行表单数据验证 @app.route('/register/', methods=['GET', 'POST']) def register(): # 创建表单对象, 如果是post请求,前端发送了数据,flask会把数据在构造form对象的时候,存放到对象中 form = RegisterForm() # 判断form中的数据是否合理 # 如果form中的数据完全满足所有的验证器,则返回True,否则返回False if form.validate_on_submit(): # 表示验证合格 # 提取数据 uname = form.user_name.data pwd = form.password.data pwd2 = form.password2.data print(uname, pwd, pwd2) session["user"] = uname return redirect(url_for("login")) # 如果校验为False,则返回模板数据 return render_template("register.html", form=form) if __name__ == '__main__': app.run(debug=True)
测试效果:
WTForms支持的HTML标准字段
字段对象 | 说明 |
---|---|
StringField | 文本字段 |
TextAreaField | 多行文本字段 |
PasswordField | 密码文本字段 |
HiddenField | 隐藏文本字段 |
DateField | 文本字段,值为datetime.date格式 |
DateTimeField | 文本字段,值为datetime.datetime格式 |
IntegerField | 文本字段,值为整数 |
DecimalField | 文本字段,值为decimal.Decimal |
FloatField | 文本字段,值为浮点数 |
BooleanField | 复选框,值为True和False |
RadioField | 一组单选框 |
SelectField | 下拉列表 |
SelectMultipleField | 下拉列表,可选择多个值 |
FileField | 文本上传字段 |
SubmitField | 表单提交按钮 |
FormField | 把表单作为字段嵌入另一个表单 |
FieldList | 一组指定类型的字段 |
WTForms常用验证函数
验证函数 | 说明 |
---|---|
DataRequired | 确保字段中有数据 |
EqualTo | 比较两个字段的值,常用于比较两次密码输入 |
Length | 验证输入的字符串长度 |
NumberRange | 验证输入的值在数字范围内 |
URL | 验证URL |
AnyOf | 验证输入值在可选列表中 |
NoneOf | 验证输入值不在可选列表中 |
四、控制语句
模板中的if控制语句
@app.route('/user') def user(): user = 'flsk' return render_template('user.html',user=user)
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> {% if user %} <h1> hello {{user}} </h1> {% else %} <h1> welcome to flask </h1> {% endif %} </body> </html>
模板中的for循环语句
@app.route('/loop') def loop(): fruit = ['apple','orange','pear','grape'] return render_template('loop.html',fruit=fruit)
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <ul> {% for index in fruit %} <li>{{ index }}</li> {% endfor %} </ul> </body> </html>
五、宏、继承、包含
5.1 宏
类似于python中的函数,宏的作用就是在模板中重复利用代码,避免代码冗余。
Jinja2支持宏,还可以导入宏,需要在多处重复使用的模板代码片段可以写入单独的文件,再包含在所有模板中,以避免重复。
定义宏
{% macro input() %} <input type="text" name="username" value="" size="30"/> {% endmacro %}
调用宏
{{ input() }}
定义带参数的宏
{% macro input(name,value='',type='text',size=20) %} <input type="{{ type }}" name="{{ name }}" value="{{ value }}" size="{{ size }}"/> {% endmacro %}
调用宏,并传递参数
{{ input(value='name',type='password',size=40)}}
把宏单独抽取出来,封装成html文件,其它模板中导入使用
文件名可以自定义,比如:定义macro.html,文件内代码如下
{% macro function() %} <input type="text" name="username" placeholde="Username"> <input type="password" name="password" placeholde="Password"> <input type="submit"> {% endmacro %}
在其它模板文件中先导入,再调用
{% import 'macro.html' as func %}
{% func.function() %}
5.2 继承
模板继承是为了重用模板中的公共内容。一般Web开发中,继承主要使用在网站的顶部菜单、底部。这些内容可以定义在父模板中,子模板直接继承,而不需要重复书写。
{% block top %}``{% endblock %}
标签定义的内容,相当于在父模板中挖个坑,当子模板继承父模板时,可以进行填充。
子模板使用extends指令声明这个模板继承自哪?父模板中定义的块在子模板中被重新定义,在子模板中调用父模板的内容可以使用super()。
父模板:base.html
{% block top %}
顶部菜单
{% endblock top %}
{% block content %}
{% endblock content %}
{% block bottom %}
底部
{% endblock bottom %}
子模板:
{% extends 'base.html' %}
{% block content %}
需要填充的内容
{% endblock content %}
模板继承使用时注意点:
- 不支持多继承。
- 为了便于阅读,在子模板中使用extends时,尽量写在模板的第一行。
- 不能在一个模板文件中定义多个相同名字的block标签。
- 当在页面中使用多个block标签时,建议给结束标签起个名字,当多个block嵌套时,阅读性更好。
5.3 包含(Include)
Jinja2模板中,除了宏和继承,还支持一种代码重用的功能,叫包含(Include)。它的功能是将另一个模板整个加载到当前模板中,并直接渲染。
示例:include的使用
{\% include 'hello.html' %}
包含在使用时,如果包含的模板文件不存在时,程序会抛出TemplateNotFound异常,可以加上ignore missing关键字。如果包含的模板文件不存在,会忽略这条include语句。
示例:include的使用加上关键字ignore missing
{\% include 'hello.html' ignore missing %}
5.4 宏、继承、包含
- 宏(Macro)、继承(Block)、包含(include)均能实现代码的复用。
- 继承(Block)的本质是代码替换,一般用来实现多个页面中重复不变的区域。
- 宏(Macro)的功能类似函数,可以传入参数,需要定义、调用。
- 包含(include)是直接将目标模板文件整个渲染出来。