学习目标
熟悉Flask处理表单的方式,能够归纳在Flask程序中如何处理表单
掌握Flask-WTF扩展包的安装,能够借助pip工具安装Flask-WTF扩展包
掌握使用Flask-WTF创建表单的方式,能够独立使用Flask-WTF创建表单
掌握在模板中渲染表单的方式,能够在模板文件中渲染使用Python类定义的表单
掌握Flask-WTF验证表单的方式,能够通过validate_on_submit()方法验证表单,并在模板文件中输出错误提示信息
掌握类视图的定义方式,能够定义标准类视图和基于请求方法的类视图
掌握蓝图的使用,能够利用蓝图对Flask程序进行模块化拆分
在Web程序中,表单是与用户进行交互的方式之一,常见于用户注册、用户登录、编辑设置等页面。不过处理表单是比较麻烦的,涉及创建表单、验证表单数据、获取和保存表单数据、反馈错误提示等诸多操作,为了降低开发人员处理表单的难度,Flask提供了专门负责处理表单的扩展包——Flask-WTF。另外,Flask还提供了类视图和蓝图。接下来,本章主要针对表单与类视图的相关内容进行讲解。
表单是在网页中搜集用户信息的各种表单控件的集合区域,表单控件包括文本框、单选框、复选框、提交按钮等,用于实现客户端和服务器端之间的数据交互。通过表单搜集的用户输入的敏感信息,比如用户名、密码等,一般会通过POST请求的方式提交给服务器进行处理,安全性相对更高。
在Flask程序中,我们可以利用Flask内置的部分功能对表单进行简单的处理,具体的处理过程:首先在模板文件中通过HTML代码创建表单,然后通过请求上下文中的request.form对象获取以及验证表单数据,最后通过消息闪现给用户反馈正确或错误提示。
通过用户注册的案例分步骤为大家演示如何使用Flask内置的功能处理表单,具体步骤如下。
(1)创建一个Flask项目Chapter04,在Chapter04项目中新建templates文件夹,在该文件夹下创建模板文件register.html,在该模板文件中使用form标签创建表单。
注册页面
{#给用户展示不同的消息闪现#}
{% macro print_error(type) %}
{% for message in get_flashed_messages(category_filter = (type)) %}
{{ message }}
{% endfor %}
{% endmacro %}
(2)在app.py文件中定义视图函数register(),该视图函数用于展示注册页面以及验证用户输入的注册数据是否符合要求。
from flask import Flask, render_template, request, flash
app = Flask(__name__)
app.secret_key = 'Your_seccret_key&^52@!'
@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')
# 验证数据的完整性
if not all([username, password, password2]):
flash('请填入完整信息', category='message')
# 验证输入的数据是否符合要求
elif len(username) < 3 and len(username) > 0 or len(username) > 15:
flash('用户名长度大于3且小于15', category='info')
# 验证两次输入的密码是否一致
elif password != password2:
flash('密码不一致', category='error')
else:
return '注册成功'
return render_template('register.html')
if __name__ == '__main__':
app.run()
(3)运行代码,通过浏览器访问http://127.0.0.1:5000/register后展示了注册页面。
单击“注册”按钮后,注册页面上用户名输入框的后面展示了提示信息“请填入完整信息”。
密码为“123”,确认密码为“123”,单击“注册”按钮后,注册页面上用户名输入框的后面展示了提示信息“用户名长度大于3且小于15”。
依次填写用户名为“666666”,密码为“123”,确认密码为“123456”,单击“注册”按钮后,注册页面上确认密码输入框的后面展示了提示信息“密码不一致”。
Flask-WTF是Flask中专门用于处理表单的扩展包,该扩展包内部对Flask和WTForms进行了一个简单的集成,可以让开发者便捷地使用WTForms来处理表单。 WTForms其实是一个灵活的表单验证和渲染库,可以与Flask、Django等多个Web框架结合使用,支持表单数据验证、CSRF保护、国际化等功能。 要想在Flask程序中使用Flask-WTF扩展包,需要提前在虚拟环境中进行安装。我们可以通过pip命令安装Flask-WTF扩展包。
(flask_env) E:\env_space>pip install flask-wtf
Flask-WTF的表单其实是一个继承FlaskForm的类,表单类中可以根据需要包含若干个属性,每个属性的值又是一个表单字段类的对象,不同字段类的对象会映射为表单中的不同控件。
WTForms库的Field类派生了许多表单字段类,常用字段类与表单控件的映射关系。
字段类 | 表单控件 | 说明 |
---|---|---|
BooleanField | input type="checkbox" | 复选框,值为True或False,默认值为Flase |
DataField | input type="text" | 文本字段,值为datetime.date对象 |
DataTimeField | input type="text" | 文本字段,值为datetime.datetime对象 |
DecimalField | input type="text" | 文本字段,值为decimal.Decimal |
FileField | input type="file" | 文件上传字段 |
FloatField | input type="text" | 浮点数字段,值为浮点型 |
IntegerField | input type="text" | 整数字段,值为整型 |
字段类 | 表单控件 | 说明 |
---|---|---|
RadioField | input type="radio" | 一组单选按钮 |
SelectField | select option /option /select | 下拉列表 |
SubmitField | input type="submit" | 提交按钮 |
StringField | input type="text" | 文本字段 |
PasswordField | input type="password" | 密码文本字段 |
TextAreaField | textarea /textarea | 多行文本字段 |
HiddenField | input type="hidden" | 隐藏文本字段 |
常用的字段类都继承自WTForms库的Field类,所以我们可以通过Field类的构造方法实例化所有字段类,虽然有的字段类内部已经重写了Filed类的构造方法,但这些字段类的构造方法中会包含一些相同的参数。
参数 | 说明 |
---|---|
label | 字段标签 |
render_kw | 字典类型,用于设置控件的属性 |
validators | 列表类型,包含一系列的验证器,在提交表单数据时,会被列表中的验证器逐一验证 |
default | 字符串或可调用对象,为表单字段设置默认值 |
参数render_kw的值是一个字典,用于为表单控件设置一些属性,包括提示信息(placeholder)、高度(height)、宽度(width )、是否获得焦点(autofocus )等。
参数validators的值是一个列表,该列表中包含了一系列用于验证表单数据是否有效的验证器,只有当表单数据满足验证器的规则时,填写的表单数据才能成功提交到服务器。 在WTForms库中,验证器是一些用于字段数据的Python类,这些类都封装在wtforms.validators模块中,因此我们在使用验证器之前需要先从wtforms.validators模块中导入相应的类。
验证器 | 说明 |
---|---|
DataRequired(message=None) | 验证数据是否有效,空字符串是为无效数据 |
Email(message=None) | 验证数据是否为电子邮件地址 |
EqualTo(fieldname,message=None) | 验证两个字段值是否相同 |
IPAddress(ipv4=True, ipv6=False, message=None) | 验证数据是否为有效IP地址 |
Length(min=-1,max=-1,message=None) | 验证输入值的长度是否在给定的范围内 |
NumberRange(min=None,max=None,message=None) | 验证输入的数字是否在给定的范围内 |
验证器 | 说明 |
---|---|
Optional(strip_whitespace=True) | 无输入值时跳过其他验证 |
Regexp(regex,flags=0,message=None) | 使用正则表达式验证输入值 |
URL(require_tld=True,message=None) | 验证URL |
AnyOf(values, message=None,values_formatter=None) | 确保输入值在可选值列表中 |
NoneOf(values, message=None,values_formatter=None) | 确保输入值不在可选值列表中 |
from flask import Flask, render_template
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, SubmitField
from wtforms.validators import DataRequired, Length, EqualTo
app = Flask(__name__)
class RegisterForm(FlaskForm):
username = StringField(label='用户名:',
validators=[DataRequired(message='用户名不能为空'),
Length(3, 15, message='长度应该为3~15')])
password = PasswordField('密码:',
validators=[DataRequired(message='密码不能为空')])
password2 = PasswordField('确认密码:',
validators=[DataRequired(message='密码不能为空'),
EqualTo('password', message='两次密码不一致')])
submit = SubmitField('注册')
使用Flask-WTF创建了注册表单,但此时在模板文件中还无法渲染创建的表单。如果希望在模板文件中渲染通过Flask-WTF创建的表单,首先需要在视图函数中将表单类的对象传递到模板文件中,然后在模板文件中获取表单字段,将表单字段渲染到HTML页面进行呈现。
通过一个案例分步骤演示如何通过Flask-WTF在模板文件中渲染表单,具体步骤如下。 (1)在Chapter04项目的app.py文件中定义用于传递表单类对象的视图函数。
app.secret_key = '34sdfji9453#$@'
@app.route('/register', methods=['GET', 'POST'])
def register():
form = RegisterForm()
return render_template('register_wtf.html', form=form)
if __name__ == '__main__':
app.run()
需要注意的是,默认情况下Flask-WTF为每个表单启用CSRF保护,因此我们需要在程序中设置密钥,这样可以让Flask-WTF通过该密钥生成CSRF令牌,以便用CSRF令牌验证请求中表单数据的真伪。
(2)在templates文件夹中创建模板文件register_wtf.html,并在该模板文件中获取表单字段。
注册页面
(3)运行代码,通过浏览器访问http://127.0.0.1:5000/register后页面中展示了通过Flask-WTF创建的注册表单。
多学一招:开启CSRF保护
CSRF指跨站请求伪造(Cross-site request forgery),是一种挟持用户在当前已登录的Web应用程序上执行非本意操作的攻击方法,它利用的是网站对用户浏览器的信任。 若网站中未开启CSRF保护,那么攻击者可通过一些非法技术手段盗用用户的身份信息,然后以用户的名义发送请求来进行一些恶意操作,例如,盗取用户账号进行转账、购买商品、发送信息等,造成个人隐私泄露和财产损失。
Flask-WTF提供了一套完善的CSRF保护体系,对于开发人员来说,使用非常起来简单。Flask-WTF中的CSRF保护体系由flask_wtf模块中的CSRFProtect类提供,该模块不仅能为包含表单的视图提供CSRF保护,还可以为不包含表单的视图提供CSRF保护(通过AJAX 发送POST请求可不用表单)。 在Flask程序中开启CSRF保护需要分别在后端和前端模板文件中进行设置。在Flask程序后端开启CSRF保护。
from flask_wtf.csrf import CsrfProtect
app = Flask(__name__)
CsrfProtect(app)
需要注意的是,开启CSRF保护需要密钥对令牌进行安全签名。默认情况下,使用Flask应用程序的密钥secret_key,也可以通过设置WTF_CSRF_SECRET_KEY使用其他的密钥。 在前端模板文件的form标签中需要调用表单对象的csrf_token()方法。
当CSRF验证失败时,默认会返回400的错误响应。
验证表单数据是网站对用户提交的表单数据的正确性进行校验。表单数据的验证通常分为两种形式,分别是客户端验证和服务器端验证。
客户端验证是指客户端(比如浏览器)对用户提交的数据进行校验。客户端验证一般可以通过多种方式进行验证,包括使用HTML5内置的验证属性、JavaScript表单验证库等。客户端验证可以实时动态提示用户输入是否正确,只有用户输入正确后才会将表单数据发送给服务器。
服务器端验证是指用户把表单数据提交到服务器端,由服务器端对表单数据进行校验。在服务器端校验时,若出现错误,则会将错误信息加入到响应进行返回,待用户修改后再次提交表单数据,直至通过校验为止。
Flask-WTF的FlaskForm类中提供了用于验证表单数据的validate_on_submit()方法,该方法内部会调用表单验证器对表单数据进行验证。
validate_on_submit()方法的返回值是一个布尔值,若返回值为True,则表示用户提交的表单数据符合验证器定义的规则,说明通过验证;若返回值为False,则用户提交的表单数据不符合验证器定义的规则,说明未通过验证。
针对未通过验证的情况,FlaskForm会将错误消息添加到表单类的errors属性中,errors属性的值是一个匹配表单字段类属性到错误信息列表的字典。若需要获取具体的错误信息列表,则可以在模板文件中通过“form.字段名. errors”进行获取。
通过一个案例分步骤演示如何通过Flask-WTF实现表单数据验证的功能,具体步骤如下。 (1)在Chapter04项目的app.py文件中,使用Flask-WTF扩展包创建表单,具体代码如下所示。
from flask import Flask, render_template
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, SubmitField
from wtforms.validators import DataRequired, Length, EqualTo
class RegisterForm(FlaskForm):
username = StringField(label='用户名:', render_kw={'required': False},
validators=[DataRequired(message='用户名不能为空'),
Length(3, 15, message='长度应该为3~15')])
password = PasswordField('密码:', render_kw={'required': False},
validators=[DataRequired(message='密码不能为空')])
password2 = PasswordField('确认密码:', render_kw={'required': False},
validators=[DataRequired(message='密码不能为空'),
EqualTo('password', message='两次密码不一致')])
submit = SubmitField('注册')
(2)定义视图函数register(),对用户提交的表单数据进行验证。
app = Flask(__name__)
app.secret_key = '34sdfji9453#$@'
@app.route('/register', methods=['GET', 'POST'])
def register():
form = RegisterForm()
if form.validate_on_submit():
return '注册成功!'
return render_template('register_wtf.html', form=form)
if __name__ == '__main__':
app.run()
(3)在Chatper04项目的templates目录下新建模板文件register_verification.html,该模板文件中获取表单字段的错误消息
注册页面
(4)重启开发服务器,通过浏览器访问http://127.0.0.1:5000/register后页面中展示了注册表单,在注册表单中不输入任何内容,单击“注册”按钮会在每个输入框后面提示错误信息。
在Flask中,标准类视图是继承flask.views模块中基类View的子类,该子类中必须重写View类中的dispatch_request()方法。除dispatch_request()方法外,我们也可以根据需要向View子类中添加其他方法或属性。
methods属性:设置当前类视图可以处理的请求方式列表。 decorators属性:为类视图指定装饰器列表,该列表中可以放置一个或多个装饰器。 dispatch_request()方法:用于实现处理不同HTTP请求的具体逻辑,该方法可以通过关键字参数接收URL传递的参数。 as_view()方法:用于将类转换为可与路由系统一起使用的实际视图函数。as_view()方法必须传入一个name参数,用于指定动态生成视图函数的名称,也可以根据需要传入一些位置参数和关键字参数,这些参数都会转发给类的构造方法,以创建类的实例,并调用类内部的dispatch_request()方法。
如果希望类视图能够对浏览器发送的HTTP请求进行处理,那么需要将类视图与URL建立映射关系。我们需要通过add_url_rule()方法将类视图与URL进行映射,不过该方法的view_func参数不能直接传入类视图的名称,而是需要传入通过as_view()方法将类视图转换后的视图函数。
from flask import Flask, request
from flask.views import View
class MyView(View): # 定义类视图
def dispatch_request(self, name): # 重写dispatch_request()方法
return f'hello {name}'
app = Flask(__name__)
# 将类视图与URL规则进行映射
app.add_url_rule('/hello/', view_func=MyView.as_view('myview'))
if __name__ == '__main__':
app.run()
运行代码,通过浏览器访问http://127.0.0.1:5000/hello/flask后页面中展示了类视图中dispatch_request()方法返回的内容。
在类视图中通过methods属性设置当前类视图可以处理的请求方式。例如,在上述示例的MyView类中,设置当前类视图可以处理GET请求和POST请求,并在dispatch_request()方法中添加处理GET请求的具体逻辑。
from flask import Flask, request
from flask.views import View
# class MyView(View): # 定义类视图
# def dispatch_request(self, name): # 重写dispatch_request()方法
# return f'hello {name}'
class MyView(View): # 定义类视图
methods = ['GET', 'POST'] # 指定请求方式
def dispatch_request(self, name): # 重写dispatch_request()方法
if request.method == 'GET':
return f'hello {name}'
app = Flask(__name__)
# 将类视图与URL规则进行映射
app.add_url_rule('/hello/', view_func=MyView.as_view('myview'))
if __name__ == '__main__':
app.run()
在Flask中,基于方法的类视图需要继承flask.views模块中的MethodView类,而MethodView类继承View类,由于MethodView类中已经重写了dispatch_request()方法,所以定义基于请求方法的类视图时不需要重写dispatch_request()方法。
在基于方法的类视图中,并非通过类属性methods来指定当前视图可以处理的请求方式,而是通过定义与请求方式同名的方法来处理不同的请求。例如,定义一个基于方法的类视图LoginView,之后在该类中添加处理GET请求和POST请求的方法。
from flask.views import MethodView
class LoginView(MethodView):
def get(self): # 处理GET请求
return '我负责处理GET请求'
def post(self): # 处理POST请求
return '我负责处理POST请求'
基于方法的类视图同样需要使用add_url_rule()方法将类视图与URL规则进行映射,并将通过as_view()方法将类视图转换后的视图函数传入view_func参数。
通过一个用户登录案例分步骤演示如何定义与使用基于方法的类视图,具体步骤如下所示。 (1)在templates文件夹中添加一个用于展示用户登录页面的模板文件login.html。
(2)在app.py文件中定义与使用基于方法的类视图。
from flask.views import MethodView
from flask import Flask, render_template, request
class LoginView(MethodView):
def get(self): # 处理GET请求
return render_template('login.html')
def post(self): # 处理POST请求
username = request.form.get('username') # 获取输入的用户名
password = request.form.get('password') # 获取输入的密码
# 判断用户名和密码是否为123
if username =='flask' and password == '123':
return f'用户:{username}登录成功。'
else:
return '用户名或密码错误,请重新登录。'
app = Flask(__name__)
app.add_url_rule('/login', view_func=LoginView.as_view('login'))
if __name__ == '__main__':
app.run()
(3)运行代码,通过浏览器访问http://127.0.0.1:5000/login后页面中展示了用户登录表单。
依次在用户输入框和密码输入框中输入正确的用户名和密码,单击“登录”按钮后页面中展示了登录成功的提示信息。
Flask推出了蓝图的概念,蓝图提供了模块化管理的功能,简化了大型Web应用程序的开发难度。 蓝图是一种制作应用程序组件的方式,可以在应用程序内部或跨越多个项目使用。当分配请求时,Flask会将蓝图和视图函数关联起来,并生成两个端点之前的URL。
蓝图适用于以下场景:
将一个应用程序分解成一组子模块。这是大型应用程序的理想选择,即项目实例化一个应用实例,初始化一些扩展,以及注册一组蓝图。 以一个URL前缀或子域在应用程序中注册蓝图。URL前缀或子域的参数成为该蓝图中所有视图函数的通用视图参数(具有默认值)。 在一个应用程序中用不同的URL规则多次注册一个蓝图。 通过蓝图提供模板过滤器、静态文件、模板和其他实用程序。蓝图不必实现应用程序或视图的功能。 在初始化Flask 扩展时在应用程序中注册一个蓝图。
若想在Flask程序中使用蓝图,首先需要创建蓝图,然后再对蓝图进行注册,其中创建蓝图需要通过Blueprint类实现;注册蓝图需要通过register_blueprint()方法实现。
假设Flask程序包含4个视图函数,分别属于普通用户和管理员两个子模块。如果在该程序中使用蓝图,那么使用蓝图前后的程序结构分别如下图。
1.创建蓝图
使用Blueprint类的构造方法可以创建蓝图,蓝图中也可以定义路由,定义方式与在Flask实例中定义路由的方式相同。
flask.Blueprint(name, import_name, static_folder=None, static_url_path=None, template_folder=None, url_prefix=None, subdomain=None, url_defaults=None, root_path=None, cli_group=
name:必选参数,表示蓝图的名称。 import_name:必选参数,表示蓝图包的名称,通常为name。 static_folder:可选参数,表示静态文件夹的路径。 static_url_path:可选参数,表示静态文件的URL。 template_folder:可选参数,表示模板文件夹路径。 url_prefix:可选参数,表示附加到所有蓝图URL的路径,用于与Flask应用程序的其他URL区分。
在Chapter04项目的根目录下创建两个py文件,分别是user.py和admin.py,在这两个文件中分别创建user蓝图和admin蓝图。
2.注册蓝图
register_blueprint()方法用于将蓝图注册到Flask程序中。
register_blueprint(blueprint, url_prefix, subdomain, url_defaults,**options)
blueprint:必选参数,表示要注册的蓝图。 url_prefix:可选参数,表示附加到所有蓝图URL的路径,若在Blueprint类设置参url_prefix,则会被该参数值覆盖。
在Chapter04项目的app.py文件中使用register_blueprint()方法注册user蓝图和admin蓝图。
from admin import admin
from user import user
from flask import Flask
app = Flask(__name__)
app.register_blueprint(admin, url_prefix='/admin') # 将蓝图admin进行注册
app.register_blueprint(user, url_prefix='/user') # 将蓝图user进行注册
if __name__ == '__main__':
app.run()
运行代码,通过浏览器分别访问http://127.0.0.1:5000/user/login或http://127.0.0.1:5000/admin/login后页面中显示的效果如下所示。
本章首先介绍了通过Flask处理表单;然后介绍了通过Flask-WTF处理表单,包括安装Flask-WTF扩展包、使用Flask-WTF创建表单、在模板中渲染表单、通过Flask-WTF验证表单;接着介绍了类视图,包括标准类视图和基于方法的类视图;最后介绍了Flask中的蓝图。通过本章的学习希望读者能够掌握Flask中表单与类视图的使用,为后续的学习奠定扎实的基础。
一,填空题
1.Flask中标准类视图需要继承flask.views模块中的()类。
2.定义标准类视图时需要重写()方法。
3.Flask中基于方法的类视图需要继承flask.views模块中的()类。
4.在Flask程序中,注册蓝图需要使用()方法。
5.在模板文件中可通过()获取CSRF令牌。
二,判断题
1.Flask-WTF是Flask内置的扩展包,无须安装即可使用。()
2.默认情况下,Flask-WTF为每个表单启用CSRF保护。()
3.Flask中定义的类视图需要重写dispatch_request()方法。()
4.Flask程序使用表单时需要设置密钥。()
5.WTForms是一个使用灵活的表单验证和渲染库,可以与Flask,Django等多个web框架结合使用。()
三,选择题
1.下列选项中,标识复选框的字段类是()。
A.DataField
B.BooleanField
C.DataTimeField
D.FileField
2.下列选项中,关于Flask-WTF的描述说法错误的是()。
A.Flask-WTF通过Python代码创建表单
B.Flask-WTF支持表单数据验证,CSRF保护等功能
C.虚拟环境中无法使用Flask-WTF扩展包
D.在安装Flask-WTF扩展包时会将WTForms一同安装
3.下列选项中,用于验证两个字段值是否相等的验证器是()。
A.DataRequired
B.Email
C.EqualTo
D.Length
4.下列选项中,关于类视图的描述说法错误的是()。
A.类视图中可以使用类属性methods设置请求方式
B.类视图定义完成之后就可以处理接收的请求
C.基于方法的类视图中,通过定义与请求方式同名的类方法处理请求
D.使用add_url_rule()方法可以将类视图与URL进行映射
5.下列选项中,关于蓝图的描述说法错误的是()。
A.通过蓝图可以将Flask程序分解成不同模块
B.使用Blueprint类可以创建蓝图对象
C.蓝图创建成功后,需要注册到Flask程序中
D.蓝图中不能定义路由