Flask Web开发--狗书笔记004--第四章 Web表单

请求对象包含客户端发出的所有请求信息,其中,request.form能获取POST请求中提交的表单数据。

笔者向我们介绍了一个Flask-WTF扩展,老惯例,首先是安装:

pip install flask-wtf

跨站请求伪造保护

flask-wtf能保护所有表单免收跨站请求伪造的攻击。
至于什么是CSRF,emmm,这里暂时不做详细介绍了….

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

hello.py:设置Flask-WTF

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

app.config

app.config字典可以用来存储框架、扩展和程序本身的配置变量。使用标准的字典句法就能把配置值添加到app.config对象中。这个对象还提供了一些方法,可以从文件或者是环境中导入配置值。
SECRET_KEY是配置变量是通用密钥,可以在Flask和多个第三方扩展中使用。
不同的程序要使用不同的密钥,而且要保证其他人不知道你所用的字符串。
为了增强安全性,密钥不应该直接写入代码,而要保存在环境变量里。

表单类

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

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

有的用法已经过时,现在的用法是:

    name = StringField('What is your name?',validators=[DataRequired()])

至于WTForms支持的HTML标准字段和
验证函数在用的时候我们可以百度……

把表单渲染成HTML

假设视图函数把一个NameForm实例通过参数form传入模板,在模板中就可以生成一个简单的表单。如下所示:

<form action="" method="POST">
    {{ form.hidden_tag() }}
    {{ form.name.label }}{{ form.name() }}
    {{ form.submit() }}
</form>

还可以把参数传入渲染字段的函数,传入的参数会被转化为字段的HTML属性。

<form action="" method="POST">
    {{ form.hidden_tag() }}
    {{ form.name.label }}{{ form.name(id='my-text-field') }}
    {{ form.submit() }}
</form>

然后本书引入了使用Bootstrap中预先定义好的表单表单样式渲染整个表单的方法….这里忽略了这一部分。还是因为跟这个前端框架不熟…哈哈…

在视图函数中处理表单

到了这时候,我们在视图 函数index()中不仅要渲染表单,还要接收表单中的数据。
更新之后的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',form=form,name=name)

表单是可以用get请求提交的,但是get请求没有主体,提交的数据以查询字符串的形式附加到URL中,可在浏览器的地址栏中看到。
基于这个还有其他多个原因,提交表单大都作为post请求提交。

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

重定向和用户会话

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

我觉得笔者在这里给我们讲到了一个细节问题,很多用户往往不能理解浏览器发出的这个警告。基于这个原因,我们最好不要让Web程序把POST请求作为浏览器发送的最后一个请求。

解决方案:我们可以使用重定向作为post请求的响应。
浏览器收到重定向的响应时,会向重定向的URL发起GET请求,显示页面的内容。
这个页面的加载可能需要多花几微妙,因为要先把第二个请求发给服务器,除此之外,用户不会察觉到有什么不同。
现在,最后一个请求是get,刷新命令就可以像预期的那样正常工作了。
这个技巧称为POST/重定向/GET模式

但是这种方法带来了另外一个问题,我们可以思考一下….
程序使用POST请求的时候,使用form.name.data获取用户输入的名字,可是一旦这个请求结束,数据也就丢失了。

我们需要在重定向的同时设法保存输入的名字,这样重定向之后的请求才可以获取并且使用这个名字。

程序可以把数据存储在用户会话中,在请求之间记住数据。

用户会话是一种私有存储,存在于每个连接到服务器的客户端中。
session会话可以像标准的字典一样操作。

书中说,默认情况下,这个用户会话会保存在客户端的cookie中,使用设置的SECRET_KEY进行加密签名。如果篡改了cookie中的内容,会话也会随之失效。

那么现在这个变量现在保存在用户会话中,即session[‘name’],所以在两次请求之间也能记住输入的值。
使用session.get(‘name’)直接从会话中读取name参数的值。和普通的字典一样,这里使用get()获取字典中键对应的值以避免未找到键的异常情况,因为对于不存在的键,get()会返回默认值None

Flash消息

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

这是一个典型的栗子,用户提交了一项错误的登陆信息表单后,服务器发回的响应重新渲染了登陆表单,并在表单上显示一个消息,提示用户。

当然,我们仅仅调用flash()函数并不能把消息显示出来,程序使用的模板要渲染这些消息,最好在基模板中渲染Flash消息。
Flask把get_flashed_messages()函数开放给模板,用来获取并且渲染消息。

{% block content %}
<div class="container">
    {% for message in get_flashed_messages() %}
    <div class="alert alert-warning">
        <button type="button" class="close" data-dismiss="alert">&times;</button>
        {{ message }}
    </div>
    {% endfor %}
    {% block page_content %}{% endblock %}
</div>
{% endblock %}

在模板中使用循环是因为在之前的请求循环中每次调用flash()函数都会生成一个消息,所以可能会有多个消息在排队等待显示。
get_flashed_messages()函数获取的消息在下次调用时不会再次返回,因此Flash消息只显示一次,然后就消失了。

第四章完结撒花。五一假期就要过去了,假期计划没有完成,心慌慌……

猜你喜欢

转载自blog.csdn.net/enjolras_fuu/article/details/80153066
今日推荐