中国工程建设招标网官方网站企业网站大全
第二章介绍的request对象,使用了客户端请求的所有信息。特别地,request.form提供了对POST请求提交的表单数据的访问。尽管Flask请求对象的支持足于处理网页单,但是还有很多作务很繁锁和重复。两个很好的例子是产生HTML表单代码和验证表单数据。
Flask-WTF扩展处理表单的体验更让人愉快。这个扩展是WTForms的 Flask集成。Flask-WTF和它的依赖可以通过pip安装:
(venv) $ pip install flask-wtf
Cross-Site Request Forgery (CSRF) 保护
黙认情况下, Flask-WTF保护所有的表单免受Cross-Site Request Forgery (CSRF)攻击。当恶意网站发送请求到攻击者登入的不同的网站时会出现CSRF攻击。
要实施 CSRF保护,Flask-WTF要求应用配置密钥。Flask-WTF用这个密钥产生加密标签来证实表单数据请求是授权的。
Example 4-1展示如何配置密钥
Example 4-1. hello.py: Flask-WTF configuration
app = Flask(__name__)
app.config['SECRET_KEY'] = 'hard to guess string'
app.config字典是框架、扩展或应用本身存贮配置信息的通用地方。配置值可以通过标准的字典符号添加到app.config。configuration对象也有函数从环境或文件中导入配置值。
SECRET_KEY配置变量被Flask和其它扩展用作通用密钥。如它的名字所示,加密的强度取决于变量的值。在不同的应用中使用不同的密钥并确保这个字符串不被别人知道。为了增强安全性,密钥应贮存于环境变量中而不是被嵌入到代码中。这个技术描述于第7章。
Form类
Flask-WTF时,每一个网页表单由一个类来呈现它继承自Form类。这个类定义了表单中字段的列表,每个字段呈现为一个对象。每个字段对象可以附着一个或多个验证器。验证器是检查用户输入的函数。
Example 4-2 展示一个简单的表单,它有一个文本字段和一个提交字段。
Example 4-2. hello.py: 表单类定义
from flask.ext.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')
表单中的字段以类的变量定义,每个类的变量赋一个相关的对象具有一个类型。前面的例子里,NameForm表单有一个文本字段称为name以及一个提交按钮。StringField类呈现为<input>元素具有type="text"的属性。SubmitField类呈现<input>元素具有 type="submit"属性。field构造函数的第一个参数是宣染表单时的label。
StringField 构造器中包含的可选的验证器参数定义了检查器的列表,它们在接受前作用于用户提交的数据。Required()验证器确保提效的字段非空。表单的基类被Flask-WTF 扩展定义,所以它导入自flask_wtf。但是字段和验证器自接从WTForms包导入。
WTForms支持的标准的HTML字段列表见Table 4-1.见:
http://www.aluoyun.cn/details.php?article_id=194
WTForms内在验证器的列表展示于Table 4-2. 见:
http://www.aluoyun.cn/details.php?article_id=195
表单的HTML宣染
表单字段是可调用的,即从模板宣染它们到HTML。假如view函数传递一个NameForm实例到模板作为参数,模板会产生简单的HTML表单:
<form method="POST">
{{ form.name.label }} {{ form.name() }}
{{ form.submit() }}
</form>
当然结果是很粗的。要改进表单的外观,宣染字段的传入参数被转换为字段的HTML属性。
作为例子,你可以给出字段id或class属性然后定义CSS格式:
<form method="POST">
{{ form.name.label }} {{ form.name(id='my-text-field') }}
{{ form.submit() }}
</form>
但是即使具有HTML属性,用这种方法宣染表单的工作也很多,所以最好是尽可能使用Bootstrap的表单风格。
Flask-Bootstrap提供了非常高级的帮助函数来宣染Flask WTF表单,它使用Bootstrap预定义的表单式样,每个式样都有一个调用。使用Flask Bootstrap,前面的表单可以如下宣染:
{% import "bootstrap/wtf.html" as wtf %}
{{ wtf.quick_form(form) }}
import指令与正常的Python脚本的的作用一样并允许模板元素被导入并在许多模板中使用。 导入的bootstrap/wtf.html 文件定义帮助函数来宣染Flask-WTF表单使用Bootstrap。
wtf.quick_form()函数取Flask-WTF表单对象并宣染它用黙认的Bootstrap式样。hello.py完整的模板见Example 4-3。
Example 4-3. templates/index.html: Using Flask-WTF and Flask-Bootstrap to render a 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 %}
现在模板的content区域有两个节。第一个节是网页的header展示欢迎。这里使用了模板条件。Jinja2的条件的格式为{% if variable %}...{% else %}...{% endif %}。如果条件为真,则if 与else之间的内容被宣染到模板。如果条件为假,则else和endif之间的内容被宣染。
示例的模板宣染 “Hello, Stranger!” 当name模板参数不提供时。内容的第二节宣染NameForm对象使用wtf.quick_form()函数。
View函数中处理表单
在新版的hello.py文件里,view函数index()将宣染表单并接收数据。Example 4-4展示更新的view函数index()。
Example 4-4. hello.py: Route methods
@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装饰器中添加的方法参数告诉Flask注册view函数作为URL map中 GET 和 POST 请求的处理器。当不指定方法时,view函数只处理GET请求。
在方法列表中增加POST是必要的因为提交的表单用 POST请求更方便。也有可能用GET请求提交表单,但是GET请求没有主体,数据以查询字符串的方式附加在URL里并在浏览器的地址栏内是可见的。出于这个和其它原因,表单提交几乎都用POST请求。用局部name变量贮存接收自表单的name。当 name未知时,变量初始化为None。 view函数创建NameForm类的实例来呈现表单。
validate_on_submit()方法返回真当表单提交时,并且数据被字段验证器接收。在其它情况下,validate_on_submit()返回假。
方法的返回值确定表单是否被宣染或被处理。当用户第一次导航到应用时,服务器接收不带表单数据的GET请求,所以validate_on_submit() 返回False。if语句的主体将跳过,请求被处理通过宣染模板,它获得表单对象并将name变量设为None作为参数。用户现在可以看到浏览器中的表单。当用户提交表单时,服务器接收 POST请求的数据。validate_on_submit()调用附着于name字段的Required()验证器。如果name不是空的,则验证器接受它validate_on_submit()返回真。现在用户输入的name可以作为字段的data属性访问。在if语句的主体内,name被赋给局部变量并且 form字段被清除,通过设置数据属性为空字符串。最后一行调用的render_template()宣染模板,但是这次name参数包含来自表单的name,所以欢迎被个性化。
当用户提交name时,应用用个性化欢迎进行响应。表单还在下面,所以用户还可以提交新的name ,如果想要的话。
如果用户用空的name提交表单,Required()验证器抛出错误。注意功能是自动的。这是设计良好的Flask-WTF和Flask-Bootstrap扩展给你的应用强大的例子。
重定向和用户会话
上一版本的hello.py有个使用问题。如果你输入你的name并提交它,然后点击浏览器的刷新按钮,你会得到警告要求确认再重新提交表单。这是因为浏览器重复上一次的请求当它们请求刷新网页时。当最后一次发送POST请求使用表单数据时,刷新会导致单表的重复提交,这不是期望的行为。许多用户不理解浏览器的警告。因此,它被认为是网页应用的良好实践,不漏掉浏览器发送的最后一次POST请求。这个实践可以通过重定向响应POST请求而不是正常的请求来获得。重定向是一种特殊的响应,它有URL而不是HTML代码的字串。当浏览器接受这个响应时,它发送GET请求重定向的URL,那就是显示的页面。页面会取更长的时间来加载因为第二个请求要发送到服务器,除此之外用户不会看到别的不同。现在最后一个请求是GET,所以刷新命令如期执行。这种技巧称为Post/ Redirect/Get模式。但是这种方法有另一个问题。当应用处理POST请求时,它访问存贮于form.name.data的用户输入的name,但是请求结束时表单数据消失。因为表单数据用重定向处理,应用需要存贮 name以使重定向请求可以得到它并用它构建实际的响应。应用可以记住一个请求的东西到下一次请求通过将它们存贮在用户会话里,用户会话是私有的存贮或以被每个连接的客户端使用。
用户会话在第二章作为与请求上下文相关的变量介绍。
它调用会话并像标准的python字典一样访问。
Example 4-5 展示新版的index() view函数,它实施重定向和用户会话。
Example 4-5. hello.py: Redirects and user sessions
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'))
在上一个版本的应用中,局部变量name用于贮存用户在表单中输入的name。那个变量现在放在用户会话中,作为session['name'],以便它在请求外被记住。现在有有效表单数据的请求以调用redirect()结束, redirect()是一个产生HTTP重定向请求的帮助函数。redirect()取URL作为重定向参数。本例中的重定向URL是根URL,所以响应与redirect('/')更一致,但是实际上使用lask的URL生成函数url_for()。鼓劢使用url_for()产生URLs因为这个函数使用URL map产生URLs,所以URLs确保兼容定义的路由和任何路由名的变更可以自动获得当用这个函数时。 url_for()函数第一个和唯一要求的参数是endpoint名,每个路由拥有的内部名。黙认情况,路由的endpoint是 view函数名。本例中,处理根URL的view函数是index(),所以提供给url_for()的name参数是 index。最后的改变在 render_template()函数里,它现在从会话中直接获得name参数,使用session.get('name')。与使用正常的字典一样,使用get() 请求字典的key避免意外,对于未找到的keys,因为get()返回黙认的值None对于缺失的key。对于这个版本的应用,你可以看到刷新会得到你想要的结果。
消息推出
有时在请求完成后给用户一个状态更新是有用的。这可以是一个确认信息,警告,或错误。一个典型的例子是当你提交错误的登录表单时,服务器重新宣染登录表单并在其上面出现消息告诉你用户名或密码无效。
Flask包含了这种功能作为核心特征。Example 4-6 展示如何使用flash()函数来实现这个目的。
Example 4-6. hello.py: Flashed messages
from flask import Flask, render_template, session, redirect, url_for, flash
@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
form.name.data = ''
return redirect(url_for('index'))
return render_template('index.html',
form = form, name = session.get('name'))
本例中,每次提交name,都与存贮在用户会话中的name比较。如果两个names不同, flash()函数被调用并显示于下一个返回客户端的响应中。
调用flash()并不足于使消息显示;应用使用的模板需要宣染这些消息。宣染推出的消息的最好的地方是基模板,因这它会在所有网页中使能这些消息。Flask有一个get_flashed_messages() 函数供模板使用以追踪消息和宣染他们,如Example 4-7所示。
Example 4-7. templates/base.html: Flash message rendering
{% 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 %}
本例中,消息被宣染,使用Bootstrap的CSS。
使用一个循环,因为有多个消息要显示,每次调用 flash() 都要显示。从get_flashed_messages()得到的消息不会在这个函数下一次调用时返回。所以推出的消息只显示一次然后消失。 能从网页表单接受用户数据是许多应用要求的特征,永久存贮数据也是许多应用要求的特征。下一章的主题是使用Flask的数据支持。