本次主要介绍下 Web 表单。

尽管 Flask 的请求对象提供的对象足够用于处理 Web 表单(如:request.form 能获取 POST 请求中提交的表单数据),但有些任务很单调,而且要重复操作。比如生成表单的 HTML 代码和验证提交的表单数据。

Flask-WTF (http://pythonhosted.org/Flask-WTF)扩展可以把处理 Web 表单的过程编程一种愉快的体验。这个扩展对独立的 WTForms(http://wtforms.simplecodes.com)包进行了包装,方便集成到 Flask 程序中。

安装 Flask-WTF

1
pip install flask-wtf

跨站请求伪造保护

默认情况下,Flask-WTF 能保护所有表单面免受跨站请求伪造(Cross-Site Request Forgery,CSRF)的攻击。恶意网站把请求发送到被攻击者已登录的其他网站是就会引发 CSRF 攻击。

为了实现 CSRF 保护,Flask-WTF 需要程序设置一个密钥。Flask-WTF 使用这个密钥生成加密令牌,再用令牌验证请求中表单数据的真伪。

设置方式

1
2
app = Flask(__name__)
app.config['SECRET_KEY'] = '这里设置密钥'

注意:为了增强安全性,密钥不应该直接写入代码,而要保存在环境变量中。

表单类

index.html 文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<html>
<head>
<title>首页</title>
</head>
<body>
{% if name %}
<h1>Hello,{{ name }}!</h1>
{% else %}
<h1>Hello Stranger!</h1>
{% endif %}
<form method="POST">
{{ form.hidden_tag() }}
{{ form.name.label }} {{ form.name() }}
{{ form.submit() }}
</form>
</body>
</html>

解释下:

0.如果有 name 则输出 Hello,xxx! ,没有 name 则输出 Hello,Stranger!

1.form.hidden_tag() 是一个隐藏字段的标识字符串

2.form.name()是下面 app.py 中 NameForm 类的一个 name 字段(输入文本框),form.name.label则是name字段中的第一个参数值字符串

3.同理

```是提交按钮
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
app.py 文件:
``` PYTHON
from flask import Flask,render_template
from flask.ext.wtf import Form
from flask.ext.bootstrap import Bootstrap
from wtforms import StringField,SubmitField
from wtforms.validators import Required
class NameForm(Form):
name = StringField('你的名字?',validators=[Required()])
submit = SubmitField('提交')
app = Flask(__name__)
app.config['SECRET_KEY'] = 'ni cai'
bootstrap = Bootstrap(app)
@app.route('/',methods=['GET','POST'])
def index():
name = None
nameForm = NameForm()
if nameForm.validate_on_submit():
name = nameForm.name.data
nameForm.name.data = ''
return render_template('index.html',form=nameForm,name=name)
if __name__ == '__main__':
app.run(debug=True)

解释下:

0.NameForm 类,声明了一个输入姓名文本框和提交按钮。字段构造函数第一个参数吧表单渲染成 HTML 时使用的标号。其中输入姓名的文本框需要必须填写。

1.app.route 修饰器中添加的 methods 参数告诉 Flask 在 URL 映射中把这个视图函数注册为 GET 和 POST 请求的处理程序,如果没有指定 methods 参数,就只把视图函数注册为 GET 请求处理程序。因为Web表单提交为 POST 方式,故需要显示标明。

2.nameForm.validate_on_submit() 方法,提交表单后,如果数据鞥被所有验证函数接受,那么 nameForm.validate_on_submit() 方法返回 True,否则返回 False

到这里就可以看见下图:

flask_04_01.png

在文本框中输入Tom看见下图:

flask_04_02.png

优化

显示的界面以及功能都比较粗糙,主要存在以下几个问题:

  • 界面很简陋,没有优美的 CSS 样式
  • 提交完数据后,按快捷键刷新浏览器会出现弹框提示:是否重新提交表单数据
  • 添加提示指示

界面显示优化

前面介绍了 Flask-Bootstrap 这个优秀的前端显示方案,完全可以拿过来用,并且 Flask-Bootstrap 还提供了一个非常高端的辅助函数,可以使用 Bootstrap 中预先定义好的表单演示渲染整个 Flask-WTF 表单,而这些操作只需一次调用即可完成。

前面介绍了 base.html 这里就不提及了。

index.html 文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
{% extends "base.html" %}
{% import "bootstrap/wtf.html" as wtf %}
{% block title %}首页 {% endblock %}
{% block page_content %}
<div class="page-header">
{% if name %}
<h1> Hello,{{ name }}! </h1>
{% else %}
<h1>Hello,Stranger!</h1>
{% endif %}
</div>
{{ wtf.quick_form(form) }}
{% endblock %}

解释下:

wtf.quick_form() 函数的参数为 Flask-WTF 表单对象,使用 Bootstrap 样式渲染传入的表单。

运行效果如下图:

flask_04_03.png

解决重复提交出现弹框问题

之所以出现这种情况,是因为刷新页面时浏览器会重新发送之前已经发送过的最后一个请求。

解决方案:不让 Web 程序把 POST 请求作为浏览器发送的最后一个请求——重定向。

修改 app.py 文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
from flask import Flask,render_template,session,url_for,redirect
from flask.ext.wtf import Form
from flask.ext.bootstrap import Bootstrap
from wtforms import StringField,SubmitField
from wtforms.validators import Required
class NameForm(Form):
name = StringField('你的名字?',validators=[Required()])
submit = SubmitField('提交')
app = Flask(__name__)
app.config['SECRET_KEY'] = 'ni cai'
bootstrap = Bootstrap(app)
@app.route('/',methods=['GET','POST'])
def index():
name = None
nameForm = NameForm()
if nameForm.validate_on_submit():
session['name'] = nameForm.name.data
nameForm.name.data = ''
return redirect(url_for('index'))
return render_template('index.html',form=nameForm,name=session.get('name'))
if __name__ == '__main__':
app.run(debug=True)

解释下:

0.session['name'] 存储了同一个会话的中文本框中的内容

1.url_for() ,当然 redirect(url_for('index')) 也可以写成redirect('/') ,但是推荐使用 url_for() 生成 URL,因为这个函数使用 URL映射生成 URL,从而保证 URL 和定义的路由兼容,而且修改路由名字后依然后依然可用。url_for()函数的第一个且唯一必须指定的参数是端点名,即路由的内部名字,默认情况下,路由的端点是相应视图函数的名字,在这个示例中,处理跟地址的视图函数是 index(),因此传给 url_for() 函数的名字是 index

2.session.get('name') 直接从会话中中读出 name 参数的值,也可以使用session['name']读取,但是推荐使用 session.get('name') ,使用get() 获取字典中键对应的值以避免未找到键的异常情况,因为对于不存在的键,get()会返回默认值 None

添加提示

请求完成后,有时需要让用户知道状态发生了变化。

这种提示功能是 Flask 的核心特性,flash() 函数可实现这种效果。

app.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
from flask import Flask,render_template,session,url_for,redirect,flash
from flask.ext.wtf import Form
from flask.ext.bootstrap import Bootstrap
from wtforms import StringField,SubmitField
from wtforms.validators import Required
class NameForm(Form):
name = StringField('你的名字?',validators=[Required()])
submit = SubmitField('提交')
app = Flask(__name__)
app.config['SECRET_KEY'] = 'ni cai'
bootstrap = Bootstrap(app)
@app.route('/',methods=['GET','POST'])
def index():
name = None
nameForm = NameForm()
if nameForm.validate_on_submit():
newName = nameForm.name.data
if newName == session.get('name'):
session['name'] = nameForm.name.data
nameForm.name.data = ''
return redirect(url_for('index'))
else:
flash('名字输入错误!')
return render_template('index.html',form=nameForm,name=session.get('name'))
if __name__ == '__main__':
app.run(debug=True)

base.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
{% extends "bootstrap/base.html" %}
{% block title %}Flasky {% endblock %}
{% block navbar %}
<div class="navbar navbar-inverse" role="navigation">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="/">Flasky</a>
</div>
<div class="navbar-collapse collapse">
<ul class="nav navbar-nav">
<li><a href="/">Home</a></li>
</ul>
</div>
</div>
</div>
{% endblock %}
{% 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 %}

实现功能:如果用户输入的名字,不是第一次输入表单中的名字,则会出现名字输入错误提示。

解释下:

get_flashed_messages() 函数用来获取并渲染的消息。