Flask表单
表单
在web程序中,表单是与用户交互的最常见的方式之一。用户注册、登录、撰写文章。不过,表单的处理却并不简单。你不仅要创建表单,验证用户输入的内容,向用户显示错误提示,还要获取并保存数据。幸运的是,强大的WTForms可以帮我们解决这些问题。WTForms是一个使用Python编写的表单库,它使得表单的定义、验证(服务器端)和处理变得非常轻松。这一章我们会介绍在Web程序中处理表单的方法和技巧。
4.1HTML表单
<form method="post"> # 表单
<label for="username">Username</label><br>
<input type="text" name="username" placeholder="Héctor Rivera"><br> # 输入字段
<label for="password">Password</label><br>
<input type="password" name="password" placeholder="19001130"><br>
<input id="remember" name="remember" type="checkbox" checked>
<label for="remember"><small>Remember me</small></label><br>
<input type="submit" name="submit" value="Log in"> # 提交
</form>
<label>
标签则用来定义字段的标签文字。我们可以在<form>
和<input>
标签中使用各种属性来对表单进行设置。上面的表单被浏览器解析后会生成两个输入框,一个勾选框和一个提交按钮。
WTForms支持在Python中使用类定义表单,然后直接通过类定义生成对应的HTML代码,这种方式更加方便,而且使表单更易于重用。因此,除非是非常简单的程序,或者是你想让表单的定义更加灵活,否则我们一般不会在模板中直接使用HTML编写表单,
4.2使用Flask-WTF处理表单
扩展Flask-WTF集成了WTForms,使用它可以在Flask中更方便地使用WTFroms。Flask-WTF将表单数据解析、CSRF保护、文件上传等功能与Flask集成,另外还附加reCAPTCHA支持(google验证码服务)。
Flask-WTF默认为每个表单启用CSRF保护,它会为我们自动生成和验证CSRF令牌。默认情况下,Flask-WTF使用程序密钥来对CSRF令牌进行签名,所以我们需要为程序设置密钥:app.secret_key='secret string'
4.2.1定义WTForms表单类
表单由Python类表示,这个类继承从WTForms导入的Form基类。from wtforms import Form, StringField, PasswordField, BooleanField, SubmitField
。字段分别用表单类的类属性来表示。
随意定一个类:
class LoginForm(Form):
... username = StringField('Username', validators=[DataRequired()])
... password = PasswordField('Password', validators=[DataRequired(), Length(8, 128)])
... remember = BooleanField('Remember me')
... submit = SubmitField('Log in')
这里的LoginForm表单类中定义了四个字段:文本字段StringField、密码字段Password Field、勾选框字段BooleanField和提交按钮字段SubmitField。字段类从wtforms包导入。
常用字段:
通过实例化字段类时传入的参数,我们可以对字段进行设置,字段类构造方法接收的常用参数:
WTForms中,验证器(validator)是一系列用于验证字段数据的类,我们在实例化字段类时使用validators关键字来指定附加的验证器列表。验证器从wtforms.validators模块中导入,常用的验证器:
4.2.2输出HTML代码
类定义的表单如何输出HTML代码的。
>>> form = LoginForm()
>>> form.username()
u'<input id="username" name="username" type="text" value="">'
>>> form.submit()
u'<input id="submit" name="submit" type="submit" value="Submit">'
>>> form.username.label()
u'<label for="username">Username</label>'
>>> form.submit.label()
u'<label for="submit">Submit</label>'
4.2.3在模板中渲染表单
使用render_temple()
函数中使用关键字参数form将表单实例传入模板。
from forms import LoginForm
@app.route('/basic')
def basic():
form = LoginForm()
return render_template('basic.html', form=form)
在模板中,只需要调用表单类的属性即可获取字段对应的HTML代码,如果需要传入参数,也可以添加括号,如:
<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>
form.csrf_token字段包含了自动生成的CSRF令牌值,在提交表单后会自动被验证,为了确保表单通过验证,我们必须在表单中手动渲染这个字段(Flask-WTF为表单类实例提供了一个form.hidden_tag()方法,这个方法会依次渲染表单中所有的隐藏字段。因为csrf_token字段也是隐藏字段,所以当这个方法被调用时也会渲染csrf_token字段。
渲染后实际效果:
<form method="post">
<input id="csrf_token" name="csrf_token" type="hidden" value="IjVmMDE1ZmFjM2VjYmZjY...i.DY1QSg.IWc1WEWxr3TvmAWCTHRMGjIcDOQ">
<label for="username">Username</label><br>
<input id="username" name="username" type="text" value=""><br>
<label for="password">Password</label><br>
<input id="password" name="password" type="password" value=""><br>
<input id="remember" name="remember" type="checkbox" value="y"><label for="remember">Remember me</label><br>
<input id="submit" name="submit" type="submit" value="Log in"><br>
</form>
4.3表单处理
表单数据的处理涉及很多内容,除去表单提交不说,从获取数据到保存数据大致会经历以下步骤:
1)解析请求,获取表单数据。
2)对数据进行必要的转换,比如将勾选框的值转换成Python的布尔值。
3)验证数据是否符合要求,同时验证CSRF令牌。
4)如果验证未通过则需要生成错误消息,并在模板中显示错误消息。
5)如果通过验证,就把数据保存到数据库或做进一步处理。
除非是简单的程序,否则手动处理不太现实,使用Flask-WTF和WTForms可以极大地简化这些步骤。
4.3.1提交表单
提交表单时,就会创建一个表单的HTTP请求,请求中包含表单各个字段的数据。表单的提交行为主要由三个属性控制:
form标签的action属性用来指定表单被提交的目标URL,默认为当前URL,也就是渲染该模板的路由所在URL。如果你要把表单数据发送到其他URL,默认为当前URL,也就是渲染该模板的路由所在URL。如果你要把表单数据发送到其他URL,可以自定义这个属性值。
当使用GET方法提交表单数据时,表单的数据会以查询字符串的形式附加在请求的URL里。http://localhost:5000/basic?username=greyli&password=12345
GET方式仅适用于长度不超过2000个字符,且不包含敏感信息的表单。因为这种方式会直接将用户提交的表单数据暴露在URL中,容易被攻击者截获,示例中的情况明显是危险的。因此,出于安全的考虑,我们一般使用POST方法提交表单。
4.3.2验证表单数据
验证表单数据有两种方式:
1.客户端验证
HTML5验证属性、除了使用HTML5提供的属性实现基本的客户端验证,我们通常会使用JavaScript实现完善的验证机制。如果你不想手动编写JavaScript代码实现客户端验证,可以考虑使用各种JavaScript表单验证库,比如jQuery Validation Plugin(https://jqueryvalidation.org/)、Parsley.js(http://parsleyjs.org/)以及可与Bootstrap集成的Bootstrap Validator(http://1000hz.github.io/bootstrap-validator/,目前仅支持Bootstrap3版本)等。
2.服务器验证
把用户输入的数据提交到服务器端,在服务器端对数据进行验证。Flask用WTForms验证机制:
WTForms验证表单字段的方式是在实例化表单类时传入表单数据,然后对表单实例调用validate()方法。这会逐个对字段调用字段实例化时定义的验证器,返回表示验证结果的布尔值。如果验证失败,就把错误消息存储到表单实例的errors属性对应的字典中,验证的过程如下所示:
>>> from wtforms import Form, StringField, PasswordField, BooleanField
>>> from wtforms.validators import DataRequired, Length
>>> class LoginForm(Form):
... username = StringField('Username', validators=[DataRequired()])
... password = PasswordField('Password', validators=[DataRequired()
, Length(8, 128)])
>>> form = LoginForm(username='', password='123')
>>> form.data # 表单数据字典
{'username': '', 'password': '123'}
>>> form.validate()
False
>>> form.errors # 错误消息字典
{'username': [u'This field is required.'], 'password': [u'Field must be
at least 8 characters long.']}
>>> form2 = LoginForm(username='greyli', password='12345678')
>>> form2.data
{'username': 'greyli', 'password': '12345678'}
>>> form2.validate()
True
>>> form2.errors
{}
因为我们的表单使用POST方法提交,如果单纯使用WTForms,我们在实例化表单类时需要首先把request.form传入表单类,而使用Flask-WTF时,表单类继承的FlaskForm基类默认会从request.form获取表单数据,所以不需要手动传入。
注释:使用POST方法提交的表单,其数据会被Flask解析为一个字典,可以通过请求对象的form属性获取(request.form);使用GET方法提交的表单的数据同样会被解析为字典,不过要通过请求对象的args属性获取(request.args)。
if form.validate_on_submit():
等价于 if request.method=='post' and form.validate():
4.3.3在模板中渲染错误信息
错误信息会存在表单类的errors属性中,这是一个匹配作为表单字段的类属性到对应的错误消息列表的字典。
<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>
至此,我们已经介绍了在Python中处理HTML表单的所有基本内容。完整的表单处理过程的流程图如图所示。