Flask 的表单验证器
在 Web 页面中,表单是一种常见的元素,表单包含有多个字段,通常字段的取值需要在一定的范围内,例如:QQ 注册时,名称不可以为空,密码的长度至少是 8 个字符,如下图所示:
将表单提交给服务端处理时,服务端需要验证表单中的字段的取值是否符合要求。本节学习 Flask 中提供表单验证的功能,学习如何对表单中的字段的取值进行有效性检查。
1. 表单验证器
1.1 WTForms 和 Flask-WTF
在 Python 的 Web 开发中,WTForms 是一个灵活的表单验证和表单渲染的库,它的主要功能:
- 验证表单中的字段的取值是否符合要求;
- 渲染输出表单的 HTML 代码。
WTForms 可以与任意的 Web 框架和模板引擎一起使用。 在 Flask 框架或者 Django 框架中,都可以使用 WTForms。
Flask-WTF 在 WTForms 的基础上提供了一些扩展,可以方便的在 Flask 框架中生成表单。
1.2 表单字段
WTForms 支持如下类型的表单字段:
字段类型 | 说明 |
---|---|
StringField | 文本字段 |
TextAreaField | 多行文本字段 |
PasswordField | 密码文本字段 |
HiddenField | 隐藏文本字段 |
DateField | 文本字段,值为datetime.date格式 |
IntegerField | 文本字段,值为整数 |
DecimalField | 文本字段, 值为decimal.Decimal |
FloatField | 文本字段,值为浮点数 |
BooleanField | 复选框, 值为True 和 False |
RadioField | 一组单选框 |
SelectField | 下拉列表 |
FileField | 文件上传字段 |
SubmitField | 表单提交按钮 |
1.3 验证器
WTForms 支持如下类型的表单验证:
验证类型 | 说明 |
---|---|
验证电子邮件地址 | |
EqualTo | 比较两个字段的值;常用于要求输入两次秘钥进行确认的情况 |
Length | 验证输入字符串的长度 |
NumberRange | 验证输入的值在数字范围内 |
DateRequired | 确保字段中有数据 |
2. 程序功能和结构
2.1 程序功能
在下面的小节,我们将使用 WTForms 实现一个登录程序,表单中包含有如下字段:
表单字段 | 说明 |
---|---|
邮件地址 | 要求符合邮件地址的格式,如 user@qq.com |
密码 | 要求密码至少包括 6 个字符 |
提交 | 将表单提交给服务器 |
用户通过浏览器进行登录,界面如下所示:
用户输入邮件地址为 ‘tom’、密码为 ‘123’,点击登录按钮将表单提交给服务器,服务器进行表单验证:
- 验证邮件地址是否规范,并显示验证失败的信息——“请输入正确的邮箱” ;
- 验证密码的长度,并显示验证失败的信息——“密码至少包括 6 个字符”。
2.2 程序结构
例子包括 2 个源文件,如下表所示:
程序 | 说明 |
---|---|
app.py | Flask 后端程序,实现表单验证 |
templates/login.html | 登录界面的模板 |
2.3 源程序下载
3. Flask 程序 app.py
3.1 安装库
使用如下命令安装相关的库:
$ pip3 install wtforms
$ pip3 install flask-wtf
$ pip3 install email-validator
3.2 引入库
#!/usr/bin/python3
from flask import Flask, render_template, request
from flask_wtf import FlaskForm
from wtforms import StringField, SubmitField, PasswordField
from wtforms.validators import DataRequired, Length, Email, ValidationError
app = Flask(__name__)
app.config['SECRET_KEY'] = 'hard to guess string'
在这个程序中,引入如下类:
类 | 来自于模块 | 功能 |
---|---|---|
FlaskForm | flask_wtf | 登录表单的基类 |
StringField | wtforms | 文本字段 |
PasswordField | wtforms | 密码文本字段 |
SubmitField | wtforms | 表单提交按钮 |
DateRequired | wtforms | 确保字段中有数据 |
Length | wtforms | 验证输入字符串的长度 |
wtforms | 验证电子邮件地址的格式是否正确 | |
ValidationError | wtforms | 验证失败时,抛出此异常 |
在第 8 行,配置 Flask 应用的选项 ‘SECRET_KEY’ 用于防范 CSRF 攻击,请参考 CSRF 攻击的相关词条。
3.3 定义登录表单
class LoginForm(FlaskForm):
email = StringField(
label = '邮箱',
validators = [
DataRequired(message = '邮箱不能为空'),
Email(message = '请输入正确的邮箱')
]
)
password = PasswordField(
label = '密码',
validators =[
DataRequired(message = '密码不能为空'),
Length(min = 6, message = '密码至少包括 6 个字符')
]
)
submit = SubmitField('登录')
定义类 LoginForm,它继承于 FlaskForm,用于描述登录界面,登录界面是一个表单,包含有 3 个字段:
- email,显示 label 为 ‘邮箱’,包括 2 个验证器:DataRequired 和 Email,message 参数为验证失败的提示信息;
- password,显示 label 为 ‘密码’,包括 2 个验证器:DataRequired 和 Length,message 参数为验证失败的提示信息,min = 6 表示密码的最小长度;
- submit,提交按钮,提交表单给服务端。
3.4 进行表单验证
@app.route('/', methods=['GET', 'POST'])
def login():
form = LoginForm()
print('form.validate_on_submit() =', form.validate_on_submit())
print('form.email.label =', form.email.label)
print('form.email() = ', form.email)
print('form.email.errors =', form.email.errors)
return render_template('login.html', form=form)
app.run(debug=True)
在第 1 行,设置以 GET 方法或者 POST 方法访问路径 / 时,使用函数 login() 进行处理;在第 3 行,创建一个实例 form,表示用户登录的表单;在第 8 行,调用 render_template 渲染 login.html。
form 对象提供了如下方法和属性:
属性 | 说明 |
---|---|
form.validate_on_submit() | 表单验证函数,返回值表示验证是否正确 |
form.email() | 显示 email 字段对应的 HTML 代码 |
form.email.label | email 字段的 label |
form.email.errors | 验证 email 字段的失败提示信息 |
在程序中打印 form 的属性,当用户提交表单时,在控制台中显示如下信息:
form.validate_on_submit() = False
form.email.label = <label for="email">邮箱</label>
form.email() = <input id="email" name="email" required type="text" value="tom">
form.email.errors = ['请输入正确的邮箱']
当表单验证失败时,form.validate_on_submit() 返回为 False。form.email.errors 是一个列表,记录了所有可能的错误信息。
4. 模板文件 login.html
<html>
<meta charset='UTF-8'>
<h1>登录</h1>
<form class="form" method="POST">
<div>
{{ form.email.label }}
{{ form.email() }}
<b>{{ form.email.errors[0] }}</b>
</div>
<div>
{{ form.password.label }}
{{ form.password() }}
<b>{{ form.password.errors[0] }}</b>
</div>
<div>
{{ form.submit() }}
</div>
{{ form.hidden_tag() }}
</form>
</html>
login.html 是用于描述登录界面的模板,根据 form 中字段 email 和 password 的属性,它被渲染为如下的 HTML 文件:
<html>
<meta charset='UTF-8'>
<title>登录</title>
<h1>登录</h1>
<form class="form" method="POST">
<div>
<label for="email">邮箱</label>
<input id="email" name="email" required type="text" value="tom">
<b>请输入正确的邮箱</b>
</div>
<div>
<label for="password">密码</label>
<input id="password" name="password" required type="password" value="">
<b>密码至少包括 6 个字符</b>
</div>
<div>
<input id="submit" name="submit" type="submit" value="登录">
</div>
<input id="csrf_token" name="csrf_token" type="hidden" value="ImY2Y2NhMWNjZDRlYWE1ZDE2ODRiZDFlYzY5ZGNhNDIzZWJmNWQ0OTQi.X1Fo_w.13ad614Bw80RgPhc9RFdjZw7-q0">
</form>
</html>
这里注意两点:
- form.email.errors 和 form.password.errors 是一个错误信息列表,errors[0] 表示第一条错误信息;
- form.hidden_tag() 用于防范 CSRF 攻击,生成 <input id=“csrf_token”/> 标签,请参考相关词条。
5. 自定义验证器
5.1 简介
在 Flask 中,可以自定义验证器实现特定的验证需求,例如:在验证密码字段时,要求密码不能全部都是数字。
Flask 包含有两种类型的验证器:行内验证器和全局验证器。下面通过具体的例子说明如何实现自定义验证 “密码是否全部是数字”。
5.2 行内验证器
对前面小节的例子程序进行如下的局部修改,修改类 LoginForm,增加一个成员函数 validate_password,如下:
class LoginForm(FlaskForm):
def validate_password(self, field):
for char in field.data:
print('!', char)
if '0123456789'.find(char) < 0:
return
raise ValidationError('密码不能全部是数字')
在第 2 行,定义成员函数 validate_password,验证函数的形式为 validate_字段名,在验证字段数据时会调用这个方法来验证对应的字段,在这里调用函数 validate_password 验证字段 password。
在第 3 行到第 6 行,遍历密码字段 field 的每个字符,如果发现存在一个非数字的字符,则正常返回;如果所有的字符都是数字,则抛出异常 ValidationError。
当用户输入的密码全部都是数字时,表单验证失败,提示错误信息为:‘密码不能全部是数字’。
5.3 全局验证器
如果想要创建一个可重用的通用验证器,可以通过定义一个全局函数来实现。
对前面小节的例子程序进行如下的局部修改,增加一个全局函数 validate_password,如下:
def can_not_be_all_digits(form, field):
for char in field.data:
print('!', char)
if '0123456789'.find(char) < 0:
return
raise ValidationError('密码不能全部是数字')
函数 can_not_be_all_digits 验证字段 field 是否全部是数字,代码与上一个小节中的函数 validate_password 完全相同。
同时修改类 LoginForm 的 PasswordField,如下:
class LoginForm(FlaskForm):
password = PasswordField(
label = '密码',
validators =[
DataRequired(message = '密码不能为空'),
Length(min = 6, message = '密码至少包括 6 个字符'),
can_not_be_all_digits
]
)
在第 7 行,validatros 中增加一个新的 validator——can_not_be_all_digits。当用户输入的密码全部都是数字时,表单验证失败,提示错误信息为:‘密码不能全部是数字’。
5.4 源程序下载
6. 小结
本小节讲解了 Flask 的表单验证的概念和使用,总结如下:
使用 Flask 的表单验证功能,可以方便的验证表单中的字段的有效性,并渲染输出表单的 HTML 代码。