04.flask表单
配套视频教程
Flask-WTF 扩展可以把处理 Web 表单的过程变成一种愉悦的体验。这个扩展对独立的 WTForms 包进行了包装,方便集成到 Flask 应用中。
Flask-WTF 及其依赖可使用 pip 安装:
pip install flask-wtf
配置
与其他多数扩展不同,Flask-WTF 无须在应用层初始化,但是它要求应用配置一个密钥。密钥是一个由随机字符构成的唯一字符串,通过加密或签名以不同的方式提升应用的安全性。Flask 使用这个密钥保护用户会话,以防被篡改。
hello.py
app = Flask(__name__)
app.config['SECRET_KEY'] = 'hard to guess string'
app.config
字典可用于存储 Flask、扩展和应用自身的配置变量。使用标准的字典句法就能把配置添加到 app.config
对象中。这个对象还提供了一些方法,可以从文件或环境中导入配置。
Flask-WTF 之所以要求应用配置一个密钥,是为了防止表单遭到跨站请求伪造(CSRF,cross-site request forgery)攻击。恶意网站把请求发送到被攻击者已登录的其他网站时,就会引发 CSRF 攻击。Flask-WTF 为所有表单生成安全令牌,存储在用户会话中。
表单类
使用 Flask-WTF 时,在服务器端,每个 Web 表单都由一个继承自 FlaskForm
的类表示。这个类定义表单中的一组字段,每个字段都用对象表示。字段对象可附属一个或多个验证函数。验证函数用于验证用户提交的数据是否有效。
示例是一个简单的 Web 表单,包含一个文本字段和一个提交按钮。
hello.py
from flask_wtf import FlaskForm
from wtforms import StringField, SubmitField
from wtforms.validators import DataRequired
class NameForm(FlaskForm):
name = StringField('What is your name?', validators=[DataRequired()])
submit = SubmitField('Submit')
这个表单中的字段都定义为类变量,而各个类变量的值是相应字段类型的对象。在这个示例中,NameForm
表单中有一个名为 name
的文本字段和一个名为 submit
的提交按钮。StringField
类表示属性为 type="text"
的 HTML <input>
元素。SubmitField
类表示属性为 type="submit"
的 HTML <input>
元素。字段构造函数的第一个参数是把表单渲染成 HTML 时使用的标注(label)。
StringField
构造函数中的可选参数 validators
指定一个由验证函数组成的列表,在接受用户提交的数据之前验证数据。验证函数 DataRequired()
确保提交的字段内容不为空。
FlaskForm
基类由 Flask-WTF 扩展定义,所以要从flask_wtf
中导入。然而,字段和验证函数却是直接从 WTForms 包中导入的。
WTForms 支持的 HTML 标准字段如表所示。
表:WTForms支持的HTML标准字段
字段类型 | 说明 |
---|---|
BooleanField |
复选框,值为 True 和 False |
DateField |
文本字段,值为 datetime.date 格式 |
DateTimeField |
文本字段,值为 datetime.datetime 格式 |
DecimalField |
文本字段,值为 decimal.Decimal |
FileField |
文件上传字段 |
HiddenField |
隐藏的文本字段 |
MultipleFileField |
多文件上传字段 |
FieldList |
一组指定类型的字段 |
FloatField |
文本字段,值为浮点数 |
FormField |
把一个表单作为字段嵌入另一个表单 |
IntegerField |
文本字段,值为整数 |
PasswordField |
密码文本字段 |
RadioField |
一组单选按钮 |
SelectField |
下拉列表 |
SelectMultipleField |
下拉列表,可选择多个值 |
SubmitField |
表单提交按钮 |
StringField |
文本字段 |
TextAreaField |
多行文本字段 |
WTForms 内建的验证函数如表所示。
表:WTForms验证函数
验证函数 | 说明 |
---|---|
DataRequired |
确保转换类型后字段中有数据 |
Email |
验证电子邮件地址 |
EqualTo |
比较两个字段的值;常用于要求输入两次密码进行确认的情况 |
InputRequired |
确保转换类型前字段中有数据 |
IPAddress |
验证 IPv4 网络地址 |
Length |
验证输入字符串的长度 |
MacAddress |
验证 MAC 地址 |
NumberRange |
验证输入的值在数字范围之内 |
Optional |
允许字段中没有输入,将跳过其他验证函数 |
Regexp |
使用正则表达式验证输入值 |
URL |
验证 URL |
UUID |
验证 UUID |
AnyOf |
确保输入值在一组可能的值中 |
NoneOf |
确保输入值不在一组可能的值中 |
把表单渲染成HTML
表单字段是可调用的,在模板中调用后会渲染成 HTML。假设视图函数通过 form
参数把一个 NameForm
实例传入模板,在模板中可以生成一个简单的 HTML 表单,如下所示:
<form method="POST">
{{ form.hidden_tag() }}
{{ form.name.label }} {{ form.name() }}
{{ form.submit() }}
</form>
注意,除了 name
和 submit
字段,这个表单还有个 form.hidden_tag()
元素。这个元素生成一个隐藏的字段,供 Flask-WTF 的 CSRF 防护机制使用。
当然,这种方式渲染出的表单还很简陋。调用字段时传入的任何关键字参数都将转换成字段的 HTML 属性。例如,可以为字段指定 id
或 class
属性,然后为其定义 CSS 样式:
<form method="POST">
{{ form.hidden_tag() }}
{{ form.name.label }} {{ form.name(id='my-text-field') }}
{{ form.submit() }}
</form>
即便能指定 HTML 属性,但按照这种方式渲染及美化表单的工作量还是很大,所以在条件允许的情况下,最好使用 Bootstrap 的表单样式。Flask-Bootstrap 扩展提供了一个高层级的辅助函数,可以使用 Bootstrap 预定义的表单样式渲染整个 Flask-WTF 表单,而这些操作只需一次调用即可完成。使用 Flask-Bootstrap,上述表单可以用下面的方式渲染:
{% import "bootstrap/wtf.html" as wtf %}
{{ wtf.quick_form(form) }}
import
指令的使用方法和普通 Python 代码一样,通过它可以导入模板元素,在多个模板中使用。导入的 bootstrap/wtf.html 文件中定义了一个使用 Bootstrap 渲染 Flask-WTF 表单对象的辅助函数。wtf.quick_form()
函数的参数为 Flask-WTF 表单对象,使用 Bootstrap 的默认样式渲染传入的表单。hello.py 的完整模板如示例所示。
示例 templates/index.html:使用 Flask-WTF 和 Flask-Bootstrap 渲染表单
{% extends "base.html" %}
{% import "bootstrap/wtf.html" as wtf %}
{% block title %}Flasky{% endblock %}
{% block page_content %}
<div class="page-header">
<h1>Hello, {% if name %}{{ name }}{% else %}Stranger{% endif %}!</h1>
</div>
{{ wtf.quick_form(form) }}
{% endblock %}
base.html
{% extends "bootstrap/base.html" %}
{% block title %}Flasky{% endblock %}
{% block navbar %}
<div class="navbar navbar-inverse" role="navigation">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle"
data-toggle="collapse" data-target=".navbar-collapse">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="/">Flasky</a>
</div>
<div class="navbar-collapse collapse">
<ul class="nav navbar-nav">
<li><a href="/">Home</a></li>
</ul>
</div>
</div>
</div>
{% endblock %}
{% block content %}
<div class="container">
{% block page_content %}{% endblock %}
</div>
{% endblock %}
在视图函数中处理表单
在新版 hello.py 中,视图函数 index()
有两个任务:一是渲染表单,二是接收用户在表单中填写的数据。以下示例是更新后的 index()
视图函数。
示例 hello.py:使用 GET
和 POST
请求方法处理 Web 表单
@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)
app.route
装饰器中多出的 methods
参数告诉 Flask,在 URL 映射中把这个视图函数注册为 GET
和 POST
请求的处理程序。如果没指定 methods
参数,则只把视图函数注册为 GET
请求的处理程序。
这里有必要把 POST
加入方法列表,因为更常使用 POST
请求处理表单提交。表单也可以通过 GET
请求提交,但是 GET
请求没有主体,提交的数据以查询字符串的形式附加到 URL 中,在浏览器的地址栏中可见。基于这个以及其他多个原因,处理表单提交几乎都使用 POST
请求。
图1 是用户首次访问网站时浏览器显示的表单。用户提交名字后,应用会生成一个针对该用户的欢迎消息。欢迎消息下方还是会显示这个表单,以便用户输入新名字。图 2 显示了此时应用的样子。
图1:Flask-WTF Web 表单
图2:提交后显示的 Web 表单
如果用户提交表单之前没有输入名字,那么 DataRequired()
验证函数会捕获这个错误,如图3 所示。注意这个扩展自动提供了多少功能。这说明,像 Flask-WTF 和 Flask-Bootstrap 这样设计良好的扩展能给应用提供十分强大的功能。
图3:验证失败后显示的 Web 表单