Flask表单详解
- 概述
- 跨站请求伪造保护
- 表单类
- 把表单渲染成HTML
- 在视图函数中处理表单
- 重定向和用户会话
- Flash消息
概述
尽管 Flask 的请求对象提供的信息足够用于处理 Web 表单,但有些任务很单调,而且要重复操作。比如,生成表单的 HTML 代码和验证提交的表单数据
Flask-WTF(http://pythonhosted.org/Flask-WTF/)扩展可以把处理 Web 表单的过程变成一种愉悦的体验。这个扩展对独立的 WTForms(http://wtforms.simplecodes.com)包进行了包装,方便集成到 Flask 程序中
Flask-WTF 及其依赖可使用 pip 安装:
pip install flask-wtf
跨站请求伪造保护
默认情况下,Flask-WTF 能保护所有表单免受跨站请求伪造(CSRF)的攻击
为了实现 CSRF 保护,Flask-WTF 需要程序设置一个密钥。Flask-WTF 使用这个密钥生成加密令牌,再用令牌验证请求中表单数据的真伪
app = Flask(__name__)
app.config['SECRET_KEY'] = 'hard to guess string'
表单类
使用 Flask-WTF 时,每个 Web 表单都由一个继承自 Form 的类表示。这个类定义表单中的一组字段,每个字段都用对象表示。字段对象可附属一个或多个验证函数。验证函数用来验证用户提交的输入值是否符合要求
from flask_wtf import Form
from wtforms import StringField, SubmitField
from wtforms.validators import Required
class NameForm(Form):
name = StringField('What is your name?', validators=[DataRequired()])
submit = SubmitField('Submit')
NameForm 表单中有一个名为 name 的文本字段和一个名为 submit 的提交按钮。StringField类表示属性为 type="text"
的 <input>
元素。SubmitField 类表示属性为type="submit"
的 <input>
元素
StringField 构造函数中的可选参数 validators 指定一个由验证函数组成的列表,在接受用户提交的数据之前验证数据。验证函数 DataRequired()
确保提交的字段不为空
WTForms 支持的 HTML 标准字段如下:
StringField
: 用于接收文本输入的字段PasswordField
: 用于接收密码输入的字段TextAreaField
: 多行文本输入字段IntegerField
: 用于接收整数输入的字段FloatField
: 用于接收浮点数输入的字段BooleanField
: 用于接收布尔值输入的字段,通常表示复选框DateField
: 用于接收日期输入的字段DateTimeField
: 用于接收日期时间输入的字段TimeField
: 用于接收时间输入的字段FileField
: 用于上传文件的字段SubmitField
: 提交按钮字段,用于提交表单SelectField
: 下拉菜单字段,用于提供选项供用户选择RadioField
: 单选按钮字段,用于提供多个选项中的一个选项供用户选择MultipleSelectField
: 多选下拉菜单字段,用于提供多个选项供用户选择
支持的验证函数如下:
Email
: 确保字段中输入的数据是合法的电子邮件地址EqualTo
: 确保字段中输入的数据与另一个字段中的数据相同IPAddress
: 确保字段中输入的数据是合法的 IP 地址Length
: 确保字段中输入的数据的长度在指定的范围内NumberRange
: 确保字段中输入的数据在指定的数字范围内Optional
: 表示字段可以为空,不是必需的DataRequired
: 确保字段中输入的数据不为空,是必需的(老的版本为Required
)Regexp
: 根据正则表达式来验证字段中输入的数据URL
: 确保字段中输入的数据是合法的 URLAnyOf
: 确保字段中输入的数据是给定列表中的一项NoneOf
: 确保字段中输入的数据不是给定列表中的任何一项
把表单渲染成HTML
表单字段是可调用的,在模板中调用后会渲染成 HTML
1、视图函数把一个 NameForm 实例通过参数 form 传入模板
from flask_wtf import FlaskForm
@app.route('/index')
def index():
form = NameForm()
return render_template('index.html', form=form)
2、在模板中可以生成一个简单的表单,如下所示
{% extends "base.html" %}
{% block title %}
Index
{% endblock %}
{% block head %}
{{ super() }}
{% endblock %}
{% block body %}
<form method="POST">
{{ form.hidden_tag() }}
{{ form.name.label }}
{{ form.name() }}
{{ form.submit() }}
</form>
{% endblock %}
在这个模板中,form.hidden_tag()
用于生成包含 CSRF 令牌的隐藏字段,以防止跨站请求伪造攻击。form.name.label
用于生成名为 name 字段的标签,用于显示在表单中。form.name()
用于生成名为 name 的输入字段,允许用户输入数据。form.submit()
用于生成提交按钮,用户点击此按钮可以提交表单
3、渲染的效果
在视图函数中处理表单
有时候我们不仅要渲染表单,还要接收表单中的数据
@app.route('/', methods=['GET', 'POST'])
def index():
name = None
form = NameForm()
if form.validate_on_submit():
name = form.name.data
form.name.data = '' # 清空表单中的输入
return render_template('index.html', form=form, name=name)
用户第一次访问程序时,服务器会收到一个没有表单数据的 GET 请求,所以 validate_on_submit()
将返回 False。if 语句的内容将被跳过,通过渲染模板处理请求,并传入表单对象和值为 None 的 name 变量作为参数。用户会看到浏览器中显示了一个表单:
用户提交表单后,服务器收到一个包含数据的 POST 请求。validate_on_submit()
会调用 name 字段上附属的 DataRequired()
验证函数。如果名字不为空,就能通过验证,validate_on_submit()
返回 True。现在,用户输入的名字可通过字段的 data 属性获取。在 if 语句中,把名字赋值给局部变量 name,然后再把 data 属性设为空字符串,从而清空表单字段。最后一行调用 render_template()
函数渲染模板,但这一次参数name 的值为表单中输入的名字,因此会显示一个针对该用户的欢迎消息:
重定向和用户会话
上述最新版的代码存在一个可用性问题。用户输入名字后提交表单,然后点击浏览器的刷新按钮,会看到一个莫名其妙的警告,要求在再次提交表单之前进行确认。之所以出现这种情况,是因为刷新页面时浏览器会重新发送之前已经发送过的最后一个请求。如果这个请求是一个包含表单数据的 POST 请求,刷新页面后会再次提交表单。大多数情况下,这并不是理想的处理方式
基于这个原因,最好别让 Web 程序把POST 请求作为浏览器发送的最后一个请求。这种需求的实现方式是,使用重定向作为 POST 请求的响应,而不是使用常规响应。重定向是一种特殊的响应,响应内容是 URL,而不是包含 HTML 代码的字符串。浏览器收到这种响应时,会向重定向的 URL 发起 GET 请求,显示页面的内容。这个页面的加载可能要多花几微秒,因为要先把第二个请求发给服务器。除此之外,用户不会察觉到有什么不同
但这种方法会带来另一个问题。程序处理 POST 请求时,使用 form.name.data
获取用户输入的名字,可是一旦这个请求结束,数据也就丢失了。因为这个 POST 请求使用重定向处理,所以程序需要保存输入的名字,这样重定向后的请求才能获得并使用这个名字,从而构建真正的响应
程序可以把数据存储在用户会话中,在请求之间“记住”数据。用户会话是一种私有存储,存在于每个连接到服务器的客户端中
@app.route('/', methods=['GET', 'POST'])
def index():
form = NameForm()
if form.validate_on_submit():
session['name'] = form.name.data
return redirect(url_for('index'))
return render_template('index.html', form=form, name=session.get('name'))
Flash消息
请求完成后,有时需要让用户知道状态发生了变化。这里可以使用确认消息、警告或者错误提醒。一个典型例子是,用户提交了有一项错误的登录表单后,服务器发回的响应重新渲染了登录表单,并在表单上面显示一个消息,提示用户用户名或密码错误
flash()
函数可实现这种效果
def index():
form = NameForm()
if form.validate_on_submit():
old_name = session.get('name')
if old_name is not None and old_name != form.name.data:
flash('Looks like you have changed your name!')
session['name'] = form.name.data
return redirect(url_for('index'))
return render_template('index.html', form=form, name=session.get('name'))
每次提交的名字都会和存储在用户会话中的名字进行比较,而会话中存储的名字是前一次在这个表单中提交的数据。如果两个名字不一样,就会调用 flash()
函数,在发给客户端的下一个响应中显示一个消息
仅调用 flash()
函数并不能把消息显示出来,程序使用的模板要渲染这些消息。最好在基模板中渲染 Flash 消息,因为这样所有页面都能使用这些消息
{% for message in get_flashed_messages() %}
{{ message }}
{% endfor %}