Flask-WTF把一个独立的WTForms集成了在Flask程序中。
1跨站请求伪造保护
默认情况下,Flask-WTF能保护所有表单面授跨站请求伪造的攻击。为了实现这个防护,Flask-WTf需要在程序中设置一个密钥,然后再使用这个密钥生成加密令牌,再用令牌验证请求中表单数据的真伪。
设置密钥的方法如下:
app = Flask(__name__) app.config['SECRET_KEY'] = 'hard to guess string'
为了安全起见,密钥是不应该硬编码到程序中的,而是要保存到环境变量中。到第7章会介绍
2表单类
使用Flask-WTF时,每个Web表单都由一个继承自From的类表示。From类定义表单中的一组字段(属性),每个字段都用对象表示,字段对象可附属一个或多个验证函数。验证函数用来验证用户提交的输入值是否符合要求。
下例是一个简单的Web表单,包含一个文本字段和一个提交按钮:
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')
name是一个文本字段,submit是一个提交按钮,分别相当于HTML中的:
<input type="text"> <input type="submit" value=>
name使用的StringField方法的第一个参数是显示在页面中的提示信息,第二个参数是一个表单验证项,用validators=[]方法实现,该方法接收一个列表,可在列表中放置多个验证函数。 验证函数Required()确保提交的字段不为空。
3把表单渲染成HTML
表单字段是可调用的,可以通过参数例如form传到模板中,然后可以在模板中生成一个简单的表单:
<form method="POST"> {{ form.hidden_tag() }} {{ form.name.label }} {{ form.name() }} {{ form.submit() }} </form>
要想改进表单的外观,可以把参数传入渲染字段的函数,传入的参数会被转换成字段的HTML属性。例如,可以为字段指定id或class属性,然后定义CSS样式,下面代码指定了字段id:
<form method="POST"> {{ form.hidden_tag() }} {{ form.name.label }} {{ form.name(id='my-text-field') }} {{ form.submit() }} </form>
Bootstrap已经为我们提供了一些表单样式,只需调用即可完成。上述表单可使用下面的方式渲染:
{% import "bootstrap/wtf.html" as wtf %} {{ wtf.quick_form(form) }}
完整的表单代码如下:
{% 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 %}
4在视图函数中处理表单
根据之前的例子,视图函数index()已经渲染了表单,我们还要接受表单中的数据,代码如下:
@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)
render_template见3.1渲染模板。
app.route修饰器中添加的methods参数告诉flask在URL映射中个把这个视图函数注册为GET和POST请求的处理程序。如果没有指定methods参数,默认把视图函数注册为GET请求的处理程序。
5重定向和用户会话
上面的hello.py存在一个问题,当用户输入了名字提交了表单知乎,然后点击刷新,会看淡一个警告。原因是刷新页面是浏览器会重新发送之前已经发送过的最后一个请求,如果这个请求是一个包含表单数据的POST请求,刷新页面后会再次提交。
解决方法是使用重定向作为POST请求的响应,而不是使用常规响应。重定向是一种特殊的响应,响应内容是URL,而不是包含HTML代码的字符串。浏览器收到重定向响应时,会向重定向的URL发起GET请求,显示页面的内容。这个页面的加载会把第二个请求发给服务器,但是只有几微秒,用户不会察觉到。这个技巧称为POST/重定向/GET模式。
但还有一个问题,就是当程序处理POST请求结束后,表单中获取到的用户输入数据就丢失了,如果使用重定向,就不能继续使用这些数据。此时,就要使用到用户会话,也就是第2章中提到的session。
from flask import Flask, render_template, session, redirect, url_for @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'))
重定向还有一个作用:比如一个论坛要登陆才能发帖,如果没登录,点击发帖,会跳到登录界面,这里用到了重定向。
from flask import Flask,url_for,redirect import config app = Flask(__name__) @app.route('/') def index(): return u'这是主页' @app.route('/login/') def login(): return u'这是登录页面' @app.route('/page/<is_login>/') def page(is_login): if is_login == '1': return u'已登录' else: return redirect(url_for('login'))#重定向 if __name__ == '__main__': app.run(debug=True)
地址栏输入 http://127.0.0.1:5000/page/1/ 页面提示已登录,如果输入 http://127.0.0.1:5000/page/2/ 会重定向到登录页面
重定向是跳一个网页,render_template()是返回HTML文件
6Flash信息
Flash消息用于显示确认消息、警告或者错误提醒。
来自Flask文档中的Flash消息闪现说明:闪现系统的基本工作方式是:在且只在下一个请求中访问上一个请求结束时记录的消息。也就是说,当前看到的Flash消息,是上一次请求结束后设置的。
from flask import redirect from flask import flash from flask import url_for @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 your name!') session['name'] = form.name.data return redirect(url_for('index')) return render_template('index.html', form=form, name=session.get('name'))
在示例中,每次提交的名字会跟存储在session中的上一次提交的名字做比较,如果不一样,则会调用flash()函数,在发给客户端的下一个响应中显示消息。
仅调用flask()并不能把消息显示出来,还要在模板中渲染这些信息。最好在基模板中渲染falsh信息,flask把get_flashed_messages()函数开放给模板用来获取并渲染信息。
{% block content %} <div class="container"> {% for message in get_flashed_messages() %} <div class="alert alert-warning"> <button type="button" class="close" data-dismiss="alert">×</button> {{ message }} </div> {% endfor %} {% block page_content %}{% endblock %} </div> {% endblock %}