赵走x博客
网站访问量:151916
首页
书籍
软件
工具
古诗词
搜索
登录
Flask 实战:41、电子邮件进阶实践
Flask 实战:40、使用事务邮件服务SendGird
Flask 实战:39、使用Flask-Mail发送电子邮件
Flask 实战:38、数据库进阶实践
Flask 实战:37、更新数据库表
Flask 实战:36、定义关系
Flask 实战:35、在视图函数里操作数据库
Flask 实战:34、数据库操作:CRUD
Flask 实战:33、使用Flask-SQLAlchemy管理数据库
Flask 实战:32、ORM魔法
Flask 实战:31、数据库的分类
Flask 实战:30、单个页面多个表单
Flask 实战:29、单个表单多个提交按钮
Flask 实战:28、使用Flask-CKEditor集成富文本编辑器
Flask 实战:27、多文件上传
Flask 实战:26、文件上传
Flask 实战:25、自定义验证器
Flask 实战:24、使用宏渲染表单
Flask 实战:23、设置错误消息语言
Flask 实战:22、处理表单数据
Flask 实战:21、使用Flask-WTF处理表单
Flask 实战:20、HTML表单
Flask 实战:19、模板进阶实践
Flask 实战:18、模板结构组织
Flask 实战:17、模板辅助工具
Flask 实战:16、模板基本用法
Flask 实战:15、HTTP进阶实践
Flask 实战:14、Flask上下文
Flask 实战:13、HTTP响应
Flask 实战:12、HTTP请求
Flask 实战:11、请求响应循环
Flask 实战:10、Flask与MVC架构
Flask 实战:9、模板与静态文件
Flask 实战:8、Flask命令
Flask 实战:7、URL与端点
Flask 实战:6、项目配置
Flask 实战:5、Flask扩展
Flask 实战:4、Python Shell
Flask 实战:3、启动开发服务器
Flask 实战:2、Hello,Flask!
Flask 实战:1、初识Flask
Flask 实战:22、处理表单数据
资源编号:75869
Python Web
Flask Web开发实战:入门、进阶与原理解析
热度:89
表单数据的处理涉及很多内容,除去表单提交不说,从获取数据到保存数据大致会经历以下步骤: * 1)解析请求,获取表单数据。 * 2)对数据进行必要的转换,比如将勾选框的值转换成Python的布尔值。 * 3)验证数据是否符合要求,同时验证CSRF令牌。 * 4)如果验证未通过则需要生成错误消息,并在模板中显示错误消息。 * 5)如果通过验证,就把数据保存到数据库或做进一步处理。 除非是简单的程序,否则手动处理不太现实,使用Flask-WTF和WTForms可以极大地简化这些步骤。
表单数据的处理涉及很多内容,除去表单提交不说,从获取数据到保存数据大致会经历以下步骤: * 1)解析请求,获取表单数据。 * 2)对数据进行必要的转换,比如将勾选框的值转换成Python的布尔值。 * 3)验证数据是否符合要求,同时验证CSRF令牌。 * 4)如果验证未通过则需要生成错误消息,并在模板中显示错误消息。 * 5)如果通过验证,就把数据保存到数据库或做进一步处理。 除非是简单的程序,否则手动处理不太现实,使用Flask-WTF和WTForms可以极大地简化这些步骤。 # 1、提交表单 在HTML中,当`
`标签声明的表单中类型为submit的提交字段被单击时,就会创建一个提交表单的HTTP请求,请求中包含表单各个字段的数据。表单的提交行为主要由三个属性控制,如表4-4所示。  表4-4 HTML表单中控制提交行为的属性 form标签的action属性用来指定表单被提交的目标URL,默认为当前URL,也就是渲染该模板的路由所在的URL。如果你要把表单数据发送到其他URL,可以自定义这个属性值。 当使用GET方法提交表单数据时,表单的数据会以查询字符串的形式附加在请求的URL里,比如: ``` http://localhost:5000/basic?username=greyli&password=12345 ``` GET方式仅适用于长度不超过3000个字符,且不包含敏感信息的表单。因为这种方式会直接将用户提交的表单数据暴露在URL中,容易被攻击者截获,示例中的情况明显是危险的。因此,出于安全的考虑,我们一般使用POST方法提交表单。使用POST方法时,按照默认的编码类型,表单数据会被存储在请求主体中,比如: ``` POST /basic HTTP/1.0 ... Content-Type: application/x-www-form-urlencoded Content-Length: 30 username=greyli&password=12345 ``` 在第2章我们介绍过,Flask为路由设置默认监听的HTTP方法为GET。为了支持接收表单提交发送的POST请求,我们必须在app.route()装饰器里使用methods关键字为路由指定HTTP方法,如代码所示: 代码清单4-5 form/app.py:设置监听POST方法 ``` @app.route('/', methods=['GET', 'POST']) def basic(): form = LoginForm() return render_template('basic.html', form=form) ``` # 2、验证表单数据 表单数据的验证是Web表单中最重要的主题之一,这一节我们会学习如何使用Flask-WTF验证并获取表单数据。 ### 1.客户端验证和服务器端验证 表单的验证通常分为以下两种形式: #### (1)客户端验证 客户端验证(client side validation)是指在客户端(比如Web浏览器)对用户的输入值进行验证。比如,使用HTML5内置的验证属性即可实现基本的客户端验证(type、required、min、max、accept等)。比如,下面的username字段添加了required标志: ```
``` 如果用户没有输入内容而按下提交按钮,会弹出浏览器内置的错误提示,如图所示。  和其他附加HTML属性相同,我们可以在定义表单时通过render_kw传入这些属性,或是在渲染表单时传入。像required这类布尔值属性,值可以为空或是任意ASCII字符,比如: ``` {{ form.username(required='') }} ``` 除了使用HTML5提供的属性实现基本的客户端验证,我们通常会使用JavaScript实现完善的验证机制。如果你不想手动编写JavaScript代码实现客户端验证,可以考虑使用各种JavaScript表单验证库,比如jQuery Validation Plugin([https://jqueryvalidation.org/](https://jqueryvalidation.org/) )、Parsley.js([http://parsleyjs.org/](http://parsleyjs.org/) )以及可与Bootstrap集成的Bootstrap Validator([http://1000hz.github.io/bootstrap-validator/](http://1000hz.github.io/bootstrap-validator/) ,目前仅支持Bootstrap3版本)等。 客户端方式可以实时动态提示用户输入是否正确,只有用户输入正确后才会将表单数据发送到服务器。客户端验证可以增强用户体验,降低服务器负载。 #### (2)服务器端验证 服务器端验证(server side validation)是指用户把输入的数据提交到服务器端,在服务器端对数据进行验证。如果验证出错,就在返回的响应中加入错误信息。用户修改后再次提交表单,直到通过验证。我们在Flask程序中使用WTForms实现的就是服务器端验证。 在这里我们不必纠结使用何种形式,因为无论你是否使用客户端验证,服务器端验证都是必不可少的,因为用户可以通过各种方式绕过客户端验证,比如在客户端设置禁用JavaScript。对于玩具程序来说,你可以不用考虑那么多,但对于真实项目来说,绝对不能忽视任何安全问题。因为客户端验证超出了本书介绍的范围,这里仅介绍服务器端验证的实现。 ### 2.WTForms验证机制 WTForms验证表单字段的方式是在实例化表单类时传入表单数据,然后对表单实例调用validate()方法。这会逐个对字段调用字段实例化时定义的验证器,返回表示验证结果的布尔值。如果验证失败,就把错误消息存储到表单实例的errors属性对应的字典中,验证的过程如下所示: ``` >>> from wtforms import Form, StringField, PasswordField, BooleanField >>> from wtforms.validators import DataRequired, Length >>> class LoginForm(Form): ... username = StringField('Username', validators=[DataRequired()]) ... password = PasswordField('Password', validators=[DataRequired() , Length(8, 128)]) >>> form = LoginForm(username='', password='123') >>> form.data # 表单数据字典 {'username': '', 'password': '123'} >>> form.validate() False >>> form.errors # 错误消息字典 {'username': [u'This field is required.'], 'password': [u'Field must be at least 6 characters long.']} >>> form2 = LoginForm(username='greyli', password='123456') >>> form2.data {'username': 'greyli', 'password': '123456'} >>> form2.validate() True >>> form2.errors {} ``` 因为我们的表单使用POST方法提交,如果单纯使用WTForms,我们在实例化表单类时需要首先把request.form传入表单类,而使用Flask-WTF时,表单类继承的FlaskForm基类默认会从request.form获取表单数据,所以不需要手动传入。 > 提示 使用POST方法提交的表单,其数据会被Flask解析为一个字典,可以通过请求对象的form属性获取(request.form);使用GET方法提交的表单的数据同样会被解析为字典,不过要通过请求对象的args属性获取(request.args)。 ### 3.在视图函数中验证表单 因为现在的basic_form视图同时接收两种类型的请求:GET请求和POST请求。所以我们要根据请求方法的不同执行不同的代码。具体来说:首先是实例化表单,如果是GET请求,那么就渲染模板;如果是POST请求,就调用validate()方法验证表单数据。 请求的HTTP方法可以通过request.method属性获取,我们可以使用下面的方式来组织视图函数: ``` from flask import request ... @app.route('/basic', methods=['GET', 'POST']) def basic(): form = LoginForm() # GET + POST if request.method == 'POST' and form.validate(): ... # 处理POST请求 return render_template('forms/basic.html', form=form) # 处理GET请求 ``` 其中的if语句等价于: ``` if 用户提交表单 and 数据通过验证: 获取表单数据并保存 ``` 当请求方法是GET时,会跳过这个if语句,渲染basic.html模板;当请求的方法是POST时(说明用户提交了表单),则验证表单数据。这会逐个字段(包括CSRF令牌字段)调用附加的验证器进行验证。 因为WTForms会自动对CSRF令牌字段进行验证,如果没有渲染该字段会导致验证出错,错误消息为“CSRF token is missing”。 Flask-WTF提供的validate_on_submit()方法合并了这两个操作,因此上面的代码可以简化为: ``` @app.route('/basic', methods=['GET', 'POST']) def basic(): form = LoginForm() if form.validate_on_submit(): ... return render_template('basic.html', form=form) ``` 除了POST方法,如果请求的方法是PUT、PATCH和DELETE方法,form.validate_on_submit()也会验证表单数据。 如果form.validate_on_submit()返回True,则表示用户提交了表单,且表单通过验证,那么我们就可以在这个if语句内获取表单数据,如代码清单4-6所示。 代码清单4-6 form/app.py:表单验证与获取数据 ``` from flask import Flask, render_template, redirect, url_for, flash ... @app.route('/basic', methods=['GET', 'POST']) def basic(): form = LoginForm() if form.validate_on_submit(): username = form.username.data flash('Welcome home, %s!' % username) return redirect(url_for('index')) return render_template('basic.html', form=form) ``` 表单类的data属性是一个匹配所有字段与对应数据的字典,我们一般直接通过“form.字段属性名.data”的形式来获取对应字段的数据。例如,form.username.data返回username字段的值。在代码清单4-6中,当表单验证成功后,我们获取了username字段的数据,然后用来发送一条flash消息,最后将程序重定向到index视图。 > 提示 表单的数据一般会存储到数据库中,这是我们下一章要学习的内容。这里仅仅将数据填充到flash()函数里。 在这个if语句内,如果不使用重定向的话,当if语句执行完毕后会继续执行最后的render_template()函数渲染模板,最后像往常一样返回一个常规的200响应,但这会造成一个问题: 在浏览器中,当单击F5刷新/重载时的默认行为是发送上一个请求。如果上一个请求是POST请求,那么就会弹出一个确认窗口,询问用户是否再次提交表单。为了避免出现这个容易让人产生困惑的提示,我们尽量不要让提交表单的POST请求作为最后一个请求。这就是为什么我们在处理表单后返回一个重定向响应,这会让浏览器重新发送一个新的GET请求到重定向的目标URL。最终,最后一个请求就变成了GET请求。这种用来防止重复提交表单的技术称为PRG(Post/Redirect/Get)模式,即通过对提交表单的POST请求返回重定向响应将最后一个请求转换为GET请求。 # 3、在模板中渲染错误消息 如果form.validate_on_submit()返回False,那么说明验证没有通过。对于验证未通过的字段,WTForms会把错误消息添加到表单类的errors属性中,这是一个匹配作为表单字段的类属性到对应的错误消息列表的字典。我们一般会直接通过字段名来获取对应字段的错误消息列表,即“form.字段名.errors”。比如,form.name.errors返回name字段的错误消息列表。 像第2章渲染flash()消息一样,我们可以在模板里使用for循环迭代错误消息列表,如代码清单4-7所示。 代码清单4-7 form/templates/basic.html:渲染错误消息 ```
{{ form.csrf_token }} {{ form.username.label }}
{{ form.username() }}
{% for message in form.username.errors %}
{{ message }}
{% endfor %} {{ form.password.label }}
{{ form.password }}
{% for message in form.password.errors %}
{{ message }}
{% endfor %} {{ form.remember }}{{ form.remember.label }}
{{ form.submit }}
``` 为了让错误消息更加醒目,我们为错误消息元素添加了error类,这个样式类在style.css文件中定义,它会将文字颜色设为红色。 如果你运行了示例程序,请访问[http://localhost:5000/basic](http://localhost:5000/basic) 打开基本表单示例,如果你没有输入内容而按下提交按钮,会看到浏览器内置的错误提示。 在使用DataRequired和InputRequired验证器时,WTForms会在字段输出的HTML代码中添加required属性,所以会弹出浏览器内置的错误提示。同时,WTForms也会在表单字段的flags属性添加required标志(比如form.username.flags.required),所以我们可以在模板中通过这个标志值来判断是否在字段文本中添加一个*号或文字标注,以表示必填项。 如果你在用户名字段输入空格,在密码字段输入的数值长度小于6,返回响应后会看到对应的错误消息显示在字段下方,如图4-4所示。  图4-4 显示错误信息 InputRequired验证器和DataRequired很相似,但InputRequired仅验证用户是否有输入,而不管输入的值是否有效。例如,由空格组成的数据也会通过验证。当使用DataRequired时,如果用户输入的数据不符合字段要求,比如在IntegerField输入非数字时会视为未输入,而不是类型错误。 至此,我们已经介绍了在Python中处理HTML表单的所有基本内容。完整的表单处理过程的流程图如图所示。 