Web表单

pip install flask-wtf

flask-wtf(https://flask-wtf.readthedocs.io/en/stable/)

4.1 跨站请求伪造保护

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

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

示例4-1 hello.py:设置Flask-WTF

app = Flask(__name__)
app.config['SECRET_KEY'] = 'hard to guess string'

    app.config字典可用来存储框架、扩展和程序本身的配置变量。使用标准的字典句法就能把配置值添加到app.config对象中。这个对象还提供了一些方法,可以从文件或环境中导入配置值。

    SECRET_KEY配置变量是通用密钥,可在Flask和多个第三方扩展中使用。如其名所示,加密的强度取决于变量值的机密程度。不同的程序要使用不同的密钥,而且要保证其他人不知道以所用的字符串。

4.2 表单类

    使用Flask-WTF时,每个Web表单都由一个继承自Form的类表示。这个类定义表单中的一组字段,每个字段都用对象表示。字段对象可附属一个或多个验证函数。验证函数来验证用户提交的输入值是否符合要求。

示例4-2是一个简单的Web表单,包含一个文本段和一个提交按钮。

示例4-2 hello.py:定义表单类

#coding:utf8
from flask import Flask
from flask_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的文本字段和一个名为submit的提交按钮。StringField类表示属性为type="text"的元素。SubmitField类表示属性为type="submit"的元素。字段构造函数的第一个参数是把表单渲染成HTML时使用的标号。

    StringField构造函数中的可选参数validators指定一个由验证函数组成的列表,在接受用户提交的数据之前验证数据。验证函数Required()确保提交的字段不为空。

Web表单_第1张图片

Web表单_第2张图片

4.3 把表单渲染成HTML

    表单字段是可调用的,在模板中调用后会渲染成HTML。假设视图函数把一个NameForm实例通过参数form传入模板,在模板中可以生成一个简单的表单,如下所示:

 

{{ form.hidden_tag() }} {{ form.name.label }} {{ form.name() }} {{ form.submit() }}

    当然,这个表单还很简陋。要想改进表单的外观,可以把参数传入渲染字段的函数,传入的参数会被转换成字段的HTML属性。例如,可以为字段指定id或class属性,然后定义CSS样式:

{{ form.hidden_tag() }} {{ form.name.label }} {{ form.name(id='my-text-field') }} {{ form.submit() }}
    Flask-Bootstrap提供了一个非常高端的辅助函数,可以使用Bootstrap中预先定义好的表单样式渲染整个Flask-WTF表单,而这些操作只需一次调用即可完成。使用Flask-Bootstrap,上述表单可使用下面的方式渲染:

{% import "bootstrap/wtf.html" as wtf %}
{{ wtf.quick_form(form) }}

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

示例4-3 templates/index.html:使用Flask-WTF和Flask-Bootstrap渲染表单

{% extends "base.html" %}
{% import "bootstrap/wtf.html" as wtf %}
{% block title %}Flasky{% endblock %}
{% block page_content %}

{{ wtf.quick_form(form) }}
{% endblock %}

    如果没有定义模板变量name,则会渲染字符串"Hello,Stranger!"。内容区的第二部分使用wtf.quick_form()函数渲染NameForm对象。

4.4 在视图函数中处理表单

示例4-4 hello.py:路由方法

#coding:utf8
from flask import Flask,render_template
from flask_wtf import Form
from wtforms import StringField,SubmitField
from wtforms.validators import Required
from flask_bootstrap import Bootstrap
app = Flask(__name__)
bootstrap = Bootstrap(app)
app.config['SECRET_KEY'] = 'hard to guess string'

class NameForm(Form):
  name = StringField('What is your name?',validators=[Required()])
  submit = SubmitField('Submit')

@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)

if __name__ == '__main__':
  app.run(debug=True)
    app.route修饰器中添加的methods参数告诉Flask在URL映射中把这个视图函数注册为GET和POST气你球儿的处理程序。如果没指定methods参数,就只能把视图函数注册为GET请求的处理程序。

    把POST加入方法列表很有必要,因为将提交表单作为POST请求进行处理更加便利。表单也可作为GET请求提交,不过GET请求没有主体,提交的数据以查询字符串的形式附加到URL中,可在浏览器的地址栏中看到。基于这个以及其他多个原因,提交表单大都作为POST请求进行处理。

    局部变量用来存放表单中输入的有效名字,如果没有输入,其值为None。如上述代码所示,在视图函数中创建一个NameForm类实例用于表示表单。提交表单后,如果数据能被所有验证函数接受,那么validate_on_submit()方法的返回值为True,否则返回False。这个函数的返回值决定是重新渲染表单还是处理表单提交数据。

    用户第一次访问程序时,服务器会收到一个没有表单数据的GET请求,所以validate_on_submit()将返回False。if语句的内容将被跳过,通过渲染模板处理请求,并传入表单对象和值为None的name变量作为参数。用户会看到浏览器中显示了一个菜单。

    用户提交表单后,服务器收到了一个包含数据的POST请求。validate_on_submit()会调用name字段上附属的Required()验证函数。如果名字不为空,就能通过验证,validate_on_submit()返回True。现在,用户输入的名字可通过字段的data属性获取。在if语句中,把名字赋值给局部变量name,然后再把data属性设为空字符串,从而清空表单字段。最后一行调用render_template()函数渲染模板,但这一次参数name的值为表单中输入的名字,因此会显示一个针对该用户的欢迎消息。

Web表单_第3张图片

Web表单_第4张图片

4.5 重定向和用户会话

    用户在输入名字提交表单,然后点击浏览器的刷新按钮,会看到一个莫名其妙的警告,要求在再次提交表单之前进行确认。之所以出现这种情况,是因为刷新页面时浏览器会重新发送之前已经发送过的最后一个请求。如果这个请求是一个包含表单数据的POST请求,刷新页面后会再次提交表单。大多数情况下,这并不是理想的处理方式。

    基于这个原因,最好别让Web程序把POST请求作为浏览器发送的最后一个请求。

    这种需求的实现方式是,使用重定向作为POST请求的响应,而不是使用常规响应。重定向是一种特殊的响应,响应内容是URL,而不是包含HTML代码的字符串。浏览器收到这种响应时,会向重定向的URL发起GET请求,显示页面的内容。这个页面的加载可能要多花几微妙,因为要先把第二个请求发送给服务器。除此之外,用户不会察觉到有什么不同。现在,最后一个请求是GET请求,所以刷新命令能像预期的那样正常使用了。这个技巧称为Post/重定向/Get模式。

    但这种方法会带来另一个问题。程序处理POST请求时,使用form.name.data获取用户输入的名字,可是一旦这个请求结束,数据也就丢失了。因为这个POST请求使用重定向处理,所以程序需要保存输入的名字,这样重定向后的请求才能获得并使用这个名字,从而构建真正的响应。

    程序可以把数据存储在用户会话中,在请求之间"记住"数据。用户会话是一种私有存储,存在于每个连接到服务器的客户端中。

示例4-5是index()视图函数的新版本、实现了重定向和用户会话。

示例4-5 hello.py:重定向和用户会话

 

#coding:utf8
from flask import Flask,render_template,session,redirect,url_for
from flask_wtf import Form
from wtforms import StringField,SubmitField
from wtforms.validators import Required
from flask_bootstrap import Bootstrap
app = Flask(__name__)
bootstrap = Bootstrap(app)
app.config['SECRET_KEY'] = 'hard to guess string'

class NameForm(Form):
  name = StringField('What is your name?',validators=[Required()])
  submit = SubmitField('Submit')

@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'))

if __name__ == '__main__':
  app.run(debug=True)

    url_for()函数的第一个且唯一必须指定的参数是端点名,即路由的内部名字。默认情况下,路由的端点是相应视图函数的名字。在这个示例中,处理跟地址的视图函数是index(),因此传给url_for()函数的名字是index。

4.6 Flash消息

    一个典型的例子是,用户提交了有一项错误的登录表单后,服务器发回的响应重新渲染了登录表单,并在表单上显示了一个消息,提示用户名或者密码错误。

示例4-6 hello.py:Flash消息

#coding:utf8
from flask import Flask,render_template,session,redirect,url_for,flash
from flask_wtf import Form
from wtforms import StringField,SubmitField
from wtforms.validators import Required
from flask_bootstrap import Bootstrap
app = Flask(__name__)
bootstrap = Bootstrap(app)
app.config['SECRET_KEY'] = 'hard to guess string'

class NameForm(Form):
  name = StringField('What is your name?',validators=[Required()])
  submit = SubmitField('Submit')

@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 you name!')
    session['name'] = form.name.data
    return redirect(url_for('index'))
  return render_template('index.html',form=form,name=session.get('name'))

if __name__ == '__main__':
  app.run(debug=True)

示例4-7 templates/base.html:渲染Flash消息

{% block content %}
{% for message in get_flashed_messages() %}
{{ message }}
{% endfor %} {% block page_content %}{% endblock %}
{% endblock %}

Web表单_第5张图片























你可能感兴趣的:(Flask)