pip install flask-wtf
flask-wtf(https://flask-wtf.readthedocs.io/en/stable/)
默认情况下,Flask-WTF能保护所有表单免受跨站请求伪造(Cross-Site Request Forgery,CSRF)的攻击。恶意网站把请求发送到被攻击者已登录的其他网站时就会引发CSRF攻击。
为了实现CSRF保护,Flask_WTF需要程序设置一个密钥。Flask-WTF使用这个密钥生成加密令牌,再用令牌验证请求中表单数据的真伪。设置密钥的方法如示例4-1所示。
示例4-1 hello.py:设置Flask-WTF
app = Flask(__name__)
app.config['SECRET_KEY'] = 'hard to guess string'
SECRET_KEY配置变量是通用密钥,可在Flask和多个第三方扩展中使用。如其名所示,加密的强度取决于变量值的机密程度。不同的程序要使用不同的密钥,而且要保证其他人不知道以所用的字符串。
使用Flask-WTF时,每个Web表单都由一个继承自Form的类表示。这个类定义表单中的一组字段,每个字段都用对象表示。字段对象可附属一个或多个验证函数。验证函数来验证用户提交的输入值是否符合要求。
示例4-2是一个简单的Web表单,包含一个文本段和一个提交按钮。
示例4-2 hello.py:定义表单类
#coding:utf8
from flask import Flask
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=[Required()])
submit = SubmitField('Submit')
StringField构造函数中的可选参数validators指定一个由验证函数组成的列表,在接受用户提交的数据之前验证数据。验证函数Required()确保提交的字段不为空。
表单字段是可调用的,在模板中调用后会渲染成HTML。假设视图函数把一个NameForm实例通过参数form传入模板,在模板中可以生成一个简单的表单,如下所示:
Flask-Bootstrap提供了一个非常高端的辅助函数,可以使用Bootstrap中预先定义好的表单样式渲染整个Flask-WTF表单,而这些操作只需一次调用即可完成。使用Flask-Bootstrap,上述表单可使用下面的方式渲染:
{% import "bootstrap/wtf.html" as wtf %}
{{ wtf.quick_form(form) }}
示例4-3 templates/index.html:使用Flask-WTF和Flask-Bootstrap渲染表单
{% extends "base.html" %}
{% import "bootstrap/wtf.html" as wtf %}
{% block title %}Flasky{% endblock %}
{% block page_content %}
Hello, {% if name %}{{ name }}{% else %}Stranger{% endif %}!
{{ wtf.quick_form(form) }}
{% endblock %}
示例4-4 hello.py:路由方法
#coding:utf8
from flask import Flask,render_template
from flask_wtf import Form
from wtforms import StringField,SubmitField
from wtforms.validators import Required
from flask_bootstrap import Bootstrap
app = Flask(__name__)
bootstrap = Bootstrap(app)
app.config['SECRET_KEY'] = 'hard to guess string'
class NameForm(Form):
name = StringField('What is your name?',validators=[Required()])
submit = SubmitField('Submit')
@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)
if __name__ == '__main__':
app.run(debug=True)
app.route修饰器中添加的methods参数告诉Flask在URL映射中把这个视图函数注册为GET和POST气你球儿的处理程序。如果没指定methods参数,就只能把视图函数注册为GET请求的处理程序。
把POST加入方法列表很有必要,因为将提交表单作为POST请求进行处理更加便利。表单也可作为GET请求提交,不过GET请求没有主体,提交的数据以查询字符串的形式附加到URL中,可在浏览器的地址栏中看到。基于这个以及其他多个原因,提交表单大都作为POST请求进行处理。
局部变量用来存放表单中输入的有效名字,如果没有输入,其值为None。如上述代码所示,在视图函数中创建一个NameForm类实例用于表示表单。提交表单后,如果数据能被所有验证函数接受,那么validate_on_submit()方法的返回值为True,否则返回False。这个函数的返回值决定是重新渲染表单还是处理表单提交数据。
用户第一次访问程序时,服务器会收到一个没有表单数据的GET请求,所以validate_on_submit()将返回False。if语句的内容将被跳过,通过渲染模板处理请求,并传入表单对象和值为None的name变量作为参数。用户会看到浏览器中显示了一个菜单。
用户提交表单后,服务器收到了一个包含数据的POST请求。validate_on_submit()会调用name字段上附属的Required()验证函数。如果名字不为空,就能通过验证,validate_on_submit()返回True。现在,用户输入的名字可通过字段的data属性获取。在if语句中,把名字赋值给局部变量name,然后再把data属性设为空字符串,从而清空表单字段。最后一行调用render_template()函数渲染模板,但这一次参数name的值为表单中输入的名字,因此会显示一个针对该用户的欢迎消息。
用户在输入名字提交表单,然后点击浏览器的刷新按钮,会看到一个莫名其妙的警告,要求在再次提交表单之前进行确认。之所以出现这种情况,是因为刷新页面时浏览器会重新发送之前已经发送过的最后一个请求。如果这个请求是一个包含表单数据的POST请求,刷新页面后会再次提交表单。大多数情况下,这并不是理想的处理方式。
基于这个原因,最好别让Web程序把POST请求作为浏览器发送的最后一个请求。
这种需求的实现方式是,使用重定向作为POST请求的响应,而不是使用常规响应。重定向是一种特殊的响应,响应内容是URL,而不是包含HTML代码的字符串。浏览器收到这种响应时,会向重定向的URL发起GET请求,显示页面的内容。这个页面的加载可能要多花几微妙,因为要先把第二个请求发送给服务器。除此之外,用户不会察觉到有什么不同。现在,最后一个请求是GET请求,所以刷新命令能像预期的那样正常使用了。这个技巧称为Post/重定向/Get模式。
但这种方法会带来另一个问题。程序处理POST请求时,使用form.name.data获取用户输入的名字,可是一旦这个请求结束,数据也就丢失了。因为这个POST请求使用重定向处理,所以程序需要保存输入的名字,这样重定向后的请求才能获得并使用这个名字,从而构建真正的响应。
程序可以把数据存储在用户会话中,在请求之间"记住"数据。用户会话是一种私有存储,存在于每个连接到服务器的客户端中。
示例4-5是index()视图函数的新版本、实现了重定向和用户会话。
示例4-5 hello.py:重定向和用户会话
#coding:utf8
from flask import Flask,render_template,session,redirect,url_for
from flask_wtf import Form
from wtforms import StringField,SubmitField
from wtforms.validators import Required
from flask_bootstrap import Bootstrap
app = Flask(__name__)
bootstrap = Bootstrap(app)
app.config['SECRET_KEY'] = 'hard to guess string'
class NameForm(Form):
name = StringField('What is your name?',validators=[Required()])
submit = SubmitField('Submit')
@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'))
if __name__ == '__main__':
app.run(debug=True)
一个典型的例子是,用户提交了有一项错误的登录表单后,服务器发回的响应重新渲染了登录表单,并在表单上显示了一个消息,提示用户名或者密码错误。
示例4-6 hello.py:Flash消息
#coding:utf8
from flask import Flask,render_template,session,redirect,url_for,flash
from flask_wtf import Form
from wtforms import StringField,SubmitField
from wtforms.validators import Required
from flask_bootstrap import Bootstrap
app = Flask(__name__)
bootstrap = Bootstrap(app)
app.config['SECRET_KEY'] = 'hard to guess string'
class NameForm(Form):
name = StringField('What is your name?',validators=[Required()])
submit = SubmitField('Submit')
@app.route('/',methods=['GET','POST'])
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 you name!')
session['name'] = form.name.data
return redirect(url_for('index'))
return render_template('index.html',form=form,name=session.get('name'))
if __name__ == '__main__':
app.run(debug=True)
{% block content %}
{% for message in get_flashed_messages() %}
{{ message }}
{% endfor %}
{% block page_content %}{% endblock %}
{% endblock %}