基本处理
之前提到的请求上下文中的request.form
可以获得POST请求中提交表单数据,一个个去处理显然很繁琐,这时可以用Flask-WTF来让一切变得更加简单,它集成了WTForms,安装如下:
pip install flask-wtf
设置密钥
为了防止恶意网站的CSRF攻击,Flask-WTF用一个密钥生成加密令牌,再用令牌验证数据的真伪,我们把这个密钥放在app.config
字典中,这里面存储了一些框架的配置变量,还支持导入配置值(一般密钥是保存在环境变量里的),代码如下:
# demo.py
...
app = Flask(__name__)
app.config['SECRET_KEY'] = 'some secret string'
...
使用表单类
在这个拓展中每个表单都由一个继承Form
的类,里面的每个字段都定义为类变量,还可以附属验证函数,如下:
from flask_wtf import Form
from wtforms import StringField, SubmitField
from wtforms.validators import Required
class NameForm(Form):
name = StringField('你的名字:', validators=[Required()]) # validator为验证函数组成的列表
submit = SubmitField('Go')
WTForms支持的HTML标准字段:
字段类型 | 说 明 |
---|---|
StringField | 文本字段 |
TextAreaField | 多行文本字段 |
PasswordField | 密码文本字段 |
HiddenField | 隐藏文本字段 |
DateField | 文本字段,值为datetime.date格式 |
DateTimeField | 文本字段,值为datetime.datetime格式 |
IntegerField | 文本字段,值为整数 |
DecimalField | 文本字段,值为decimal.Decimal |
FloatFiled | 文本字段,值为浮点数 |
BooleanField | 复选框,值为True和False |
RadioField | 一组单选框 |
SelectField | 下拉列表 |
SelectMultipleField | 下拉列表,可选择多个值 |
FileField | 文件上传字段 |
SubmitField | 表单提交按钮 |
FormField | 把表单作为字段嵌入另一表单 |
FieldList | 一组指定类型的字段 |
WTForms验证函数:
验证函数 | 说 明 |
---|---|
验证电子邮件地址 | |
EqualTo | 比较两个字段的值,常用于要求输入两次密码确认的情况 |
IPAddress | 验证IPv4网络地址 |
Length | 验证输入字符串的长度 |
NumberRange | 验证输入的值在数字范围内 |
Optional | 无输入值时跳过其他验证函数 |
Required | 确保字段中有数据 |
Regexp | 使用正则表达式验证输入值 |
URL | 验证URL |
AngOf | 确保输入值在可选值列表中 |
NoneOf | 确保输入值之不在可选值列表中 |
页面部分:渲染表单
接下来要在页面上显示,我们可以把表单实例通过参数form
传入模板,如下:
<form method="POST">
{{ form.hidden_tag() }}
{{ form.name.label }} {{ form.name() }}
{{ form.submit() }}
</form>
有些麻烦是吧,好在Flask-Bootstrap提供了一个好用的函数quick_form()
,它接收一个表单对象,方便快速的渲染表单:
{% extends "base.html" %}
{% import "bootstrap/wtf.html" as wtf %} <!-- 导入模板中的元素并别名为wtf -->
{% block title %}首屋{% endblock %}
{% block page_content %}
<div class="page-header">
<h1>你来了, {% if name %} {{ name }}{% else %}外乡人{% endif %}!!</h1>
</div>
{{ wtf.quick_form(form) }} <!-- 这里对form对象进行渲染-->
{% endblock %}
运行如图:
后端部分:处理表单
接收到POST请求后要进行处理,由于之前的首页路由只有默认的GET请求方法,我们要添加POST方法,虽然表单也可以用GET提交(以查询字符串形式加到URL中),但它没有主体以及其他安全问题,还是稳稳用POST吧
改写的index路由如下:
@app.route('/', methods=['GET', 'POST']) # methods参数注册请求方法
def index():
name = None
form = NameForm() # 实例化一个表单
if form.validate_on_submit(): # 表单成功提交后进入
name = form.name.data # 通过字段的data属性获取
form.name.data = '' # 获取数据后清空表单
return render_template('index.html', form=form, name=name, cur_time=datetime.utcnow(), time=datetime(1995,7,1))
这个过程:第一次GET获取页面和表单,第二次POST提交表单进入if获得参数进行渲染
提交表单后:
用户会话(session)的使用
它是之前提到过的请求上下文中的变量(另一个是request),session以字典的形式存储请求之间的数据,存于客户端的cookie中并用使SECRET_KEY
加密,为什么要用它,需要了解一下重定向
重定向是一种特殊响应,响应内容是URL,浏览器接收到这种响应时会向重定向的URL发起GET请求来显示页面的内容
因为刷新页面时浏览器会重新发送之前发送的最后一个请求,如果是POST(包含表单数据)那刷新后就会再次提交,显然这是不好的,不要把POST请求作为浏览器的最后一个请求,这时可以用重定向来实现,这种避免POST放在最后而用GET的技巧被称作 Post/重定向/Get模式,但当一个请求结束时数据也会跟着丢失,此时session就派上了用场:
from flask import Flask, render_template, redirect, url_for, session # 记得导入所用的
@app.route('/', methods=['GET', 'POST']) # methods参数注册请求方法
def index():
form = NameForm() # 实例化一个表单
if form.validate_on_submit(): # 判断表单是否成功提交
session['name'] = form.name.data # 把值保存在session中
return redirect(url_for('index')) # 为了兼容
return render_template('index.html', form=form, name=session.get('name'), cur_time=datetime.utcnow(), time=datetime(1995,7,1))
url_for()
用于生成URL,它只有一个参数,即端点名(在这里视图函数名),用这个函数可以防止修改路由后URL不可用的问题;session.get('name')
很灵活,当值未找到时返回None
,因此省去了name=None
提交表单后的请求:
Flash提醒
当请求完成后想有点反馈,告知客户端成功或失败,flask有个flash()
函数就是专门干这事的,先看后端代码:
from flask import flash # 记得导入
@app.route('/', methods=['GET', 'POST']) # methods参数注册请求方法
def index():
form = NameForm()
if form.validate_on_submit():
oldname = session.get('name')
if oldname is not None and oldname != form.name.data: # 提交的名字与之前的名字比较
flash("我们不一样")
session['name'] = form.name.data
return redirect(url_for('index'))
return render_template('index.html', form=form, name=session.get('name'), cur_time=datetime.utcnow(), time=datetime(1995,7,1))
然后在模板中渲染,Flask把get_flashed_messages()
函数开放给了模板,放在content块中:
{% 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 %}
输入两次不同后如下:
在之前的请求循环中每次调用flash()
函数都会生成一个消息,会形式多个消息排队等待的情况,所以用for;get_flashed_messages()
获取的消息在下次调用时不会再次返回,所以Flash消息只显示一次