【Flask笔记】第四节:表单

img

旧的笔记,以前存放的笔记软件停止运营了,重新迁移到博客上,删除和修改一些过时的内容。

0x00 使用Flask-WTF处理表单

Flask-WTF将表单数据解析、CSRF保护、文件上传等功能与Flask集成,另外还附加了reCAPTCHA支持。

安装Flask-WTF:

1
pip install flask-wtf

Flask-WTF默认为每个表单启用CSRF保护,它会为我们自动生成和验证CSRF令牌,默认情况下,Flask-WTF使用程序密钥来对CSRF令牌进行签名,所以我们需要为程序设置密钥:

1
app.secret_key = 'secret string'

1. 定义WTForms表单类

1
2
3
4
5
6
7
8
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, BooleanField, SubmitField
from wtforms.validators import DataRequired, Length
class LoginForm(FlaskForm):
username = StringField('Username', validators=[DataRequired()])
password = PasswordField('Password', validators=[DataRequired(), Length(8, 128)])
remember = BooleanField('Remember me')
submit = SubmitField('Log in')

字段属性的名称将作为对应HTMLinput元素的name属性及id属性值。字段属性名称大小写敏感,不能以下划线或validate开头。

字段属性名称大小写敏感,不能以下划线或validate开头。

常用的WTForms字段:

字段类 说明 对应HTML
BooleanField 复选框,值会被处理为True或False <input type="checkbox">
DateField 文本字段,值会被处理为datetime.date对象 <input type="text">
DateTimeField 文本字段,值会被处理为datetime.datetime对象 <input type="text">
FileField 文件上传字段 <input type="file">
FloatField 浮点数字段,值会被处理为浮点数 <input type="text">
IntegerField 整数字段,值会被处理为整数 <input type="text">
RadioField 一组单选按钮 <input type="radio">
SelectField 下拉列表 <select><option></option></select>
SelectMultipleField 多选下拉列表 <select multiple><option></option></select>
SubmitField 提交按钮 <input type="submit">
StringField 文本字段 <input type="text">
HiddenField 隐藏文本字段 <input type="hidden">
PasswordField 密码文本字段 <input type="password">
TextAreaFiled 多行文本字段 <textarea></textarea>

实例化字段类构造方法常用的参数:

参数 说明
label 字段标签<label>的值,也就是渲染后显示在输入字段前面的文字
render_kw 一个字典,用来设置对应HTML标签的属性,比如传入{'placeholder':'Your Name'},渲染后的HTML代码会把<input>标签的placeholder属性设置为Your Name
validators 一个列表,包含一系列验证器,会在表单提交后逐一调用验证表单的数据
default 字符串或可调用的对象,用来为表单字段设置默认值

验证器用于验证字段数据的类,在对字段类实例化时候使用validators来指定附件的验证器列表。

验证器 说明
DataRequired(message=None) 验证数据是否有效
Email(message=None) 验证Email地址
EqualTo(fielname,message=None) 验证两个字段值是否相同
InputRequired(message=None) 验证是否有数据
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) 确保输入值不在可选的列表中

在实例化验证类的时候,messgae参数用来传递自定义错误消息。

2. 输出HTML代码

默认情况下,WTForms输出的字段HTML代码只会包含id和name属性,属性值均为表单类中对应的字段属性名称。如果要添加额外的属性,通常有两种方法。

2.1 使用render_kw属性

1
username = StringField('Username', render_kw={'placeholder': 'Your Username'})

2.2 在调用字段时传入

通过表单实例调用字段属性,通过关键词的方式传入。

1
>>> form.username(style='width: 200px;', class_='bar')

class是Python的保留关键字,这里使用class_代替class,在模版中则可以直接使用class

3. 在模版中渲染表单

在视图函数中实例化表单类,然后在render_template()函数中使用关键字参数form将表单实例传入模版。

1
2
3
4
5
from forms import LoginForm
@app.route('/basic')
def basic():
form = LoginForm()
return render_template('basic.html', form=form)

在模版中,只需要调用表单类的属性即可获取字段对应的HTML代码:

1
2
3
4
5
6
7
<form method="post">
{{ form.csrf_token }} <!-- 渲染CSRF令牌隐藏字段 -->
{{ form.username.label }}<br>{{ form.username }}<br>
{{ form.password.label }}<br>{{ form.password }}<br>
{{ form.remember }}{{ form.remember.label }}<br>
{{ form.submit }}<br>
</form>

0x01 处理表单数据

1. 提交表单

在HTML中,当<form>标签声明的表单类型为submit的提交数据被单击,就会创建一个提交表单的HTTP请求,表单的提交行为主要由三个属性控制:

属性 默认值 说明
action 当前URL,即页面的对应的URL 表单提交时发送的请求的目标URL
method get 提交表单的HTTP请求方法,目前仅支持使用GET和POST
enctype application/x-www-form-urlencodeed 表单数据编码类型,当表单中包含文件上传字段时,需要设为multipart/form-data,还可以设为纯文本类型text/plain

2. 验证表单数据

表单的验证分为客户端验证和服务端验证。

2.1 客户端验证

使用HTML5内置的验证属性,即可实现基本的客户端验证(type,required,min,max,accept等),我们可以在定义表单时候的通过render_kw传入。或是渲染表单时候传入。

除了HTML5提供的属性,还可以使用JS或事第三方的JS验证库,JQuery Validation Plugin,Parsley.js,Bootstrap Validator等。

2.2 服务端验证

现在的视图函数同时接收两种类型的请求,GET和POST,我们需要根据请求方法不同,执行不同的代码,具体来说,就是如果是GET请求,就渲染模版,如果是POST请求,就调用validate()方法验证表单数据。

1
2
3
4
5
6
7
8
from flask import request
...
@app.route('/basic', methods=['GET', 'POST'])
def basic():
form = LoginForm() # GET + POST
if request.method == 'POST' and form.validate():
... # 处理POST请求
return render_template('basic.html', form=form) # 处理GET请求

另外Flask-WTF提供了validate_on_submit()合并了判断的操作,上述代码可以简化为:

1
2
3
4
5
6
@app.route('/basic', methods=['GET', 'POST'])
def basic():
form = LoginForm()
if form.validate_on_submit():
...
return render_template('basic.html', form=form)

除了POST方法,PUT,PATCH和DELETE方法,form.validate_on_submit()也会验证表单数据。

2.3 模版中渲染错误消息

如果form.validate_on_submit()返回False,那么说明验证没有通过。对于验证未通过的字段会直接通过字段名来获取对应字段的错误消息列表,即form.字段名.errors。比如,form.name.errors返回name字段的错误消息列表。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<form method="post">
{{ form.csrf_token }}
{{ form.username.label }}<br>
{{ form.username }}<br>
{% for message in form.username.errors %}
<small class="error">{{ message }}</small><br>
{% endfor %}
{{ form.password.label }}<br>
{{ form.password }}<br>
{% for message in form.password.errors %}
<small class="error">{{ message }}</small><br>
{% endfor %}
{{ form.remember }}{{ form.remember.label }}<br>
{{ form.submit }}<br>
</form>

0x02 表单进阶

1. 设置错误消息语言

1
2
3
4
5
6
7
8
9
from flask_wtf import FlaskForm
app = Flask(__name__)
app.config['WTF_I18N_ENABLED'] = False
class MyBaseForm(FlaskForm):
class Meta:
locales = ['zh']
class HelloForm(MyBaseForm):
name = StringField('Name', validators=[DataRequired()])
submit = SubmitField()

2. 使用宏渲染表单

1
2
3
4
5
6
7
8
9
{% macro form_field(field) %}
{{ field.label }}<br>
{{ field(**kwargs) }}<br>
{% if field.errors %}
{% for error in field.errors %}
<small class="error">{{ error }}</small><br>
{% endfor %}
{% endif %}
{% endmacro %}

这个form_field()宏接收表单类实例的字段属性和附加的关键字参数作为输入,返回包含<label>标签、表单字段、错误消息列表的HTML表单字段代码。使用这个宏渲染表单的示例如下所示:

1
2
3
4
5
6
7
8
{% from 'macros.html' import form_field %}
...
<form method="post">
{{ form.csrf_token }}
{{ form_field(form.username)}}<br>
{{ form_field(form.password) }}<br>
...
</form>

3. 自定义验证器

3.1 行内验证器

1
2
3
4
5
6
7
8
from wtforms import IntegerField, SubmitField
from wtforms.validators import ValidationError
class FortyTwoForm(FlaskForm):
answer = IntegerField('The Number')
submit = SubmitField()
def validate_answer(form, field):
if field.data != 42:
raise ValidationError('Must be 42.')

当表单类中包含以“validate_字段属性名”形式命名的方法时,在验证字段数据时会同时调用这个方法来验证对应的字段,这也是为什么表单类的字段属性名不能以validate开头。

3.2 全局验证器

不需要传入参数的验证器:

1
2
3
4
5
6
7
from wtforms.validators import ValidationError
def is_42(form, field):
if field.data != 42:
raise ValidationError('Must be 42')
class FortyTwoForm(FlaskForm):
answer = IntegerField('The Number', validators=[is_42])
submit = SubmitField()

需要传入参数,使用工厂函数形式的验证器:

1
2
3
4
5
6
7
8
9
10
11
from wtforms.validators import ValidationError
def is_42(message=None):
if message is None:
message = 'Must be 42.'
def _is_42(form, field):
if field.data != 42:
raise ValidationError(message)
return _is_42
class FortyTwoForm(FlaskForm):
answer = IntegerField('The Number', validators=[is_42()])
submit = SubmitField()

4. 文件上传

5. 富文本编辑器