Web开发-Flask从零开始的学习(三)
4 Web表单
对多数应用来说,需要把用户提供的数据交给服务器来处理。
使用 HTML 可以创建 Web 表单,供用户填写信息。表单数据由 Web 浏览器提交给服务器,这一过程通常使用 POST 请求。对包含表单数据的 POST 请求来说,用户填写的信息通过 request.form
访问
配置 flask-wtf
Flask-WTF 无须在应用层初始化,但是它要求应用配置一个密钥。Flask 使用这个密钥保护用户会话,以防被篡改。
app = Flask(__name__)
app.config['SECRET_KEY']='hard to guess string'
app.config
字典可用于存储 Flask、扩展和应用自身的配置变量。使用标准的字典句法就能把配置添加到 app.config
对象中。
Flask-WTF 之所以要求应用配置一个密钥,是为了防止表单遭到跨站请求伪造(CSRF,cross-site request forgery)攻击。恶意网站把请求发送到被攻击者已登录的其他网站时,就会引发 CSRF 攻击。Flask-WTF 为所有表单生成安全令牌,存储在用户会话中。令牌是一种加密签名,根据密钥生成。
密钥不应该写入源码,而是保存在环境变量中。
表单类
-
使用 Flask-WTF 时,在服务器端,每个 Web 表单都由一个继承自 FlaskForm 的类表示。
-
这个类定义表单中的一组字段,每个字段都用对象表示。
-
字段对象可附属一个或多个验证函数。
-
验证函数用于验证用户提交的数据是否有效。
定义表单类:
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')
- 这个表单中的字段都定义为类变量,而各个类变量的值是相应字段类型的对象。
- StringField 构造函数中的可选参数 validators 指定一个由验证函数组成的列表,在接受用户提交的数据之前验证数据。
- 验证函数 DataRequired() 确保提交的字段内容不为空。
FlaskForm 基类由 Flask-WTF 扩展定义,所以要从 flask_wtf 中导入。然而,字段和验证函数却是直接从 WTForms 包中导入的
page 55
把表单渲染成HTML
表单字段是可调用的,在模板中调用后会渲染成 HTML。
Flask-Bootstrap 扩展提供了一个高层级的辅助函数,可以使用Bootstrap 预定义的表单样式渲染整个 Flask-WTF 表单,而这些操作只需一次调用即可完成。
{% import "bootstrap/wtf.html" as wtf %}
{{ wtf.quick_form(form) }}
import 指令的使用方法和普通 Python 代码一样,通过它可以导入模板元素,在多个模板中使用。
导入的 bootstrap/wtf.html 文件中定义了一个使用 Bootstrap 渲染 Flask-WTF 表单对象的辅助函数。
wtf.quick_form() 函数的参数为 Flask-WTF 表单对象,使用Bootstrap 的默认样式渲染传入的表单。
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 %}
- quick_form() 函数渲染 NameForm 对象form(通过参数传递进入)
在视图函数中处理表单
视图函数 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',current_time=datetime.utcnow(),form=form,name=name)
- methods 参数告诉 Flask,在 URL 映射中把这个视图函数注册为
GET 和 POST 请求的处理程序。如果没有指定methods参数,默认注册为GET请求的处理程序。 - 一般使用POST请求处理表单提交。表单也可以通过GET 请求提交,但是 GET 请求没有主体,提交的数据以查询字符串的形式附加到 URL 中,在浏览器的地址栏中可见。
- 提交表单后,如果数据能被所有验证函数接受,那么validate_on_submit() 方法的返回值为 True
- 用户首次访问应用时,服务器会收到一个没有表单数据的 GET 请求,所以 validate_on_submit() 将返回 False。
- 用户提交表单后,服务器会收到一个包含数据的 POST 请求。validate_on_submit() 会调用名字字段上依附的 DataRequired() 验证函数。
重定向和用户会话
刚刚编写的hello.py存在这种问题:用户输入名字后提交表单,然后点击浏览器的刷新按钮,会看到一个警告,要求在再次提交表单之前进行认。
是因为刷新页面时浏览器会重新发送之前发送过的请求。如果前一个请求是包含表单数据的 POST 请求,刷新页面后会再次提交表单。
最好别让 Web 应用把 POST 请求作为浏览器发送的最后一个请求。
实现方法:
- 使用重定向作为 POST 请求的响应,称为 Post / 重定向 /Get 模式
但这种方法又会引起另一个问题。应用处理 POST 请求时,可以通过 form.name.data 获取用户输入的名字,然而一旦这个请求结束,数据也就不见了
- 应用可以把数据存储在用户会话中,以便在请求之间“记住”数据。用户会话是一种私有存储,每个连接到服务器的客户端都可访问。
Session是在服务端保存的一个数据结构,用来跟踪用户的状态,这个数据可以保存在集群、数据库、文件中;
Cookie是客户端保存用户信息的一种机制,用来记录用户的一些信息,也是实现Session的一种方式。
hello.py:重定向和用户会话
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',current_time=datetime.utcnow(),form=form,name=session.get('name'))
-
url_for() 函数的第一个且唯一必须指定的参数是端点名,即路由的内部名称。默认情况下,路由的端点是相应视图函数的名称。
-
我们使用 session.get('name') 直接从会话中读取 name 参数的值。与普通的字典一样,这里使用 get() 获取字典中键对应的值,可以避免未找到键时抛出异常。如果指定的键不存在,则 get() 方法返回默认值 None
闪现消息
请求完成后,有时需要以信息的形式让用户知道发生了变化。比如错误提示,成功提示。
Flask 本身内置这个功能:
from flask import Flask, render_template, session, redirect, url_for, flash
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',current_time=datetime.utcnow(),form=form,name=session.get('name'))
实现提交名字和用户会话中名字对比,不一致则调用flash函数。
- 仅调用 flash() 函数并不能把消息显示出来,应用的模板必须渲染这些消息。
- 最好在基模板中渲染闪现消息,因为这样所有页面都能显示需要显示的消息。
- Flask 把 get_flashed_messages() 函数开放给模板,用于获取并渲染闪现消息。
{% block content %}
{% for message in get_flashed_messages() %}
{{ message }}
{% endfor %}
{% block page_content %}{% endblock %}
{% endblock %}
-
这个示例使用 Bootstrap 提供的 CSS alert 样式渲染警告消息
-
使用了循环,因为在之前的请求循环中每次调用 flash() 函数时都会生成一个消息,所以可能有多个消息在排队等待显示
-
get_flashed_messages() 函数获取的消息在下次调用时不会再次返回