flask成长记(三)

本文介绍了Flask-WTF扩展如何简化Flask中Web表单的处理,包括验证和渲染HTML。同时讲解了CSRF保护的概念和Flask-WTF的默认防护机制。通过示例展示了表单类的创建、字段类型和内置验证函数。最后,讨论了在视图函数中处理表单数据、重定向和用户会话管理,解决POST请求重复提交的问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

flask成长记(三)

web表单

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

尽管 Flask 的请求对象提供的信息足够用于处理 Web 表单,但有些任务很单调,而且要重
复操作。比如,生成表单的 HTML 代码和验证提交的表单数据。

这上面说啥?

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

Flask-WTF 及其依赖可使用 pip 安装:

pip install flask-wtf

跨站请求伪造保护

其实蜗牛刚看到也是黑人问号脸……

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

没错,这个CSRF就是跨站请求伪造。

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

hello.py: 设置 Flask-WTF

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

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

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

表单类

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

下面是一个简单的 Web 表单,包含一个文本字段和一个提交按钮。

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

来来来,我们回味一下。
上边儿说了,每个 Web 表单都由一个继承自 Form 的类表示。

class NameForm(Form):这个就是python中的类继承机制。Form就是那个无私的被继承的类。

那这个类啥时候被实例化呢?

剩下的name和submit是对象。

你还有没有想过,你现在是在python函数中定义了一个class而已啊,里面的按钮啥的都是对象。
但是最后你是要渲染成HTML标签的啊!

那它和HTML怎么对应?
在这个示例中,
NameForm 表单中有一个名为 name 的文本字段和一个名为 submit 的提交按钮。 
StringField 类表示属性为 type="text" 的 <input> 元素。
SubmitField 类表示属性为 type="submit" 的<input> 元素。
字段构造函数的第一个参数是把表单渲染成 HTML 时使用的标号。

其实我看也没简单多少。
虽然原理上好理解了,但是总觉得怪怪的。

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

那这个WTForms还支持什么HTML字段呢?
StringField 文本字段
TextAreaField 多行文本字段
PasswordField 密码文本字段
HiddenField 隐藏文本字段
DateField 文本字段,值为 datetime.date 格式
DateTimeField 文本字段,值为 datetime.datetime 格式
IntegerField 文本字段,值为整数
DecimalField 文本字段,值为 decimal.Decimal
FloatField 文本字段,值为浮点数
BooleanField 复选框,值为 True 和 False
RadioField 一组单选框
SelectField 下拉列表
SelectMultipleField 下拉列表,可选择多个值
FileField 文件上传字段
SubmitField 表单提交按钮
FormField 把表单作为字段嵌入另一个表单
FieldList 一组指定类型的字段

内建函数有哪些呢?

Email 验证电子邮件地址
EqualTo 比较两个字段的值;常用于要求输入两次密码进行确认的情况
IPAddress 验证 IPv4 网络地址
Length 验证输入字符串的长度
NumberRange 验证输入的值在数字范围内
Optional 无输入值时跳过其他验证函数
Required 确保字段中有数据
Regexp 使用正则表达式验证输入值
URL 验证 URL
AnyOf 确保输入值在可选值列表中
NoneOf 确保输入值不在可选值列表中

下面这个就是重要的一点了,把表单渲染成HTML

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

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

注意了啊,是视图函数把一个form传入模板。
上面不是定义了一个表单类么,那就是在视图函数中实例化了一个表单,
之后又传到了前台的模板。恩我就是这么理解的。

在条件允许的情况下最好能使用 Bootstrap 中的表单样式。

Flask-Bootstrap 提供了一个非常高端的辅助函
数,可以使用 Bootstrap 中预先定义好的表单样式渲染整个 Flask-WTF 表单,而这些操作
只需一次调用即可完成。使用 Flask-Bootstrap,上述表单可使用下面的方式渲染:

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

import 指令的使用方法和普通 Python 代码一样,允许导入模板中的元素并用在多个模板
中。导入的 bootstrap/wtf.html 文件中定义了一个使用 Bootstrap 渲染 Falsk-WTF 表单对象
的辅助函数。 wtf.quick_form() 函数的参数为 Flask-WTF 表单对象,使用 Bootstrap 的默认
样式渲染传入的表单。

这个程序的完整如下:

hellopy的程序:

from flask import Flask, render_template
from flask_script import Manager
from flask_bootstrap import Bootstrap
from flask_moment import Moment
from flask_wtf import Form
from wtforms import StringField, SubmitField
from wtforms.validators import Required

app = Flask(__name__)                   这个就是密钥
    app.config['SECRET_KEY'] = 'hard to guess string'

manager = Manager(app)
bootstrap = Bootstrap(app)
moment = Moment(app)


class NameForm(Form):                   这个是新建了一个类,表单类。
      name = StringField('What is your name?', validators=[Required()])
      submit = SubmitField('Submit')


@app.errorhandler(404)
def page_not_found(e):
    return render_template('404.html'), 404


@app.errorhandler(500)
def internal_server_error(e):
    return render_template('500.html'), 500


@app.route('/', methods=['GET', 'POST'])
def index():
    name = None
    form = NameForm()                         上面这个就是新建了一个表单对象
                                              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__':
    manager.run()

模板:

{% 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">
    <div class="page-header">
        <h1>Hello, {{ name }}!</h1>
    </div>
</div>
{% endblock %}


这个是模板。

在视图函数中处理表单

在上边的hello.py中,不仅要渲染表单,还要接受表单的数据。

name = form.name.data
form.name.data = ”

注意这一句:

@app.route(‘/’, methods=[‘GET’, ‘POST’])

app.route 修饰器中添加的 methods 参数告诉 Flask 在 URL 映射中把这个视图函数注册为
GET 和 POST 请求的处理程序。如果没指定 methods 参数,就只把视图函数注册为 GET 请求
的处理程序。

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

用GET方法的时候,你能在URL中看到信息,账户的名字和密码就都泄露了,而且真的很奇怪……

@app.route('/', methods=['GET', 'POST'])
def index():
    name = None
    form = NameForm()             
    if form.validate_on_submit():                             第一开始,没有提交表单数据,
                                                              书上说的是渲染模板,
                                                              提交给用户一个表单。
                                                              之后这个if语句能通过了,于是
                                                              就将值传到了这里。
        name = form.name.data
        form.name.data = ''
    return render_template('index.html', form=form, name=name)
index.html
{% extends "base.html" %}
{% import "bootstrap/wtf.html" as wtf %}

{% block title %}Flasky{% endblock %}

{% block page_content %}
<div class="page-header">
    <h1>Hello, {% if name %}{{ name }}{% else %}Stranger{% endif %}!</h1>
</div>
{{ wtf.quick_form(form) }}
{% endblock %}

这个是index的html文件。原来你是这样的模板!
看到这个,我的理解是,渲染模板其实是先装载到视图函数中的吧?
不然父模板怎么继承?block怎么去替换呢?

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

重定向和用户回话

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

你要是实在不想看到这个POST,那最好别让Web程序吧POST请求作为浏览器发送的最后一个请求。

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

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

程序可以把数据存储在用户会话中,在请求之间“ 记住”数据。用户会话是一种私有存
储,存在于每个连接到服务器的客户端中。 用户会话,它是请求上下文中的变量,
名为 session,像标准的 Python 字典一样操作。

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

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

 翻译过来就时,你点了提交,会新建一个回话,这个比之前多了重定向,重定向会转到那个页面,
 这里又重定向到了index页面,所以是又加载了一遍这个页面,但是这时候应该是判定函数无法通过。
 也就是直接到下面的render去渲染了,这个请求是GET。
 现在页面最后一个请求是GET请求,不会有刷新出现提示的情况。

在程序的前一个版本中,局部变量 name 被用于存储用户在表单中输入的名字。这个变量现
在保存在用户会话中,即 session[‘name’],所以在两次请求之间也能记住输入的值。

现在,包含合法表单数据的请求最后会调用 redirect() 函数。 redirect() 是个辅助函数,
用来生成 HTTP 重定向响应。 redirect() 函数的参数是重定向的 URL,这里使用的重定向
URL 是程序的根地址, 因此重定向响应本可以写得更简单一些,写成 redirect(‘/’),但
却会使用 Flask 提供的 URL 生成函数 url_for()。推荐使用 url_for() 生成 URL,因为这
个函数使用 URL 映射生成 URL,从而保证 URL 和定义的路由兼容,而且修改路由名字后
依然可用。

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

最后一处改动位于 render_function() 函数中,使用 session.get(‘name’) 直接从会话中读
取 name 参数的值。和普通的字典一样,这里使用 get() 获取字典中键对应的值以避免未找
到键的异常情况,因为对于不存在的键, get() 会返回默认值 None。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值