在Python的Flask框架中构建Web表单的教程


Posted in Python onJune 04, 2016

尽管Flask的request对象提供的支持足以处理web表单,但依然有许多任务会变得单调且重复。表单的HTML代码生成和验证提交的表单数据就是两个很好的例子。

Flask-WTF扩展使得处理web表单能获得更愉快的体验。该扩展是一个封装了与框架无关的WTForms包的Flask集成。

Flask-WTF和它的依赖集可以通过pip来安装:

(venv) $ pip install flask-wtf

1、跨站请求伪造(CSRF)保护
默认情况下,Flask-WTF保护各种形式对跨站请求伪造(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和一些第三方扩展的通用加密密钥。加密的强度取决于这个变量的值。给你构建的每个应用程序选择不同的密钥,并确保这个字符串不被其他任何人知道。

注:为了提高安全性,密钥应该存储在一个环境变量中,而不是嵌入到代码中。这个会在第7章中描述。

2、表单类
使用Flask-WTF时,每个web表单是由继承自Form类的子类来展现的。该类在表单中定义了一组表单域,每个都表示为一个对象。每个表单域都可以连接到一个或多个validators;validators是一个用于检查用户提交的输入是否合法的函数。

下面的示例展示了一个拥有文本框和提交按钮的简单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')

表单中的域被定义为类的变量,且每个类的变量都指定一个表单域类型对象。在上一个示例中,NameForm表单有一个name文本框和submit提交按钮。StringField类表示一个type="text"属性的<input>标签。SubmitField类表示一个type="submit"属性的<input>标签。表单域构造函数的第一个参数是一个label,在渲染表单到HTML时会使用。

StringField构造函数包含可选参数validators,它定义了一组检查来验证用户提交的数据。Required()验证确保提交的表单域不为空。

注:Flask-WTF扩展定义了表单基类,所以它从flask.ext.wtf导入。表单域、验证都是直接从WTForms包中导入。
下面的表格展示了一组WTForms支持的标准表单域。
表格WTForms标准HTML表单域

在Python的Flask框架中构建Web表单的教程

下面则展示了一组WTForms内建验证。
 WTForms验证

在Python的Flask框架中构建Web表单的教程

3、HTML渲染的表单
表单域是可调用的,调用时从模板渲染它们到HTML。假设视图函数传递一个参数名为form的NameForm实例给模板,模板就会生成一个简单的HTML表单,如下所示:

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

当然,结果是什么都没有。为了改变表单的外观显示,任何发送给该表单域的参数会被转换为HTML表单域属性;例如,你可以给定表单域id或class属性,然后定义CSS样式:

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

即使有HTML属性,努力用这种方式渲染表单是非常重要的,所以最好是尽可能的使用Bootstrap自带的一系列表单样式。Flask-Bootstrap使用Bootstrap的预定义表单样式来提供高级的帮助函数来渲染整个Flask-WTF表单,这些操作都只需要一个调用即可完成。使用Flask-Bootstrap,上一个表单可以像下面这样来渲染:

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

import指令和常规的Python脚本一样的作用并且允许模板元素被导入并在许多模板中使用。被导入的bootstrap/wtf.html文件,定义了帮助函数使用Bootstrap来渲染Flask-WTF表单。wtf.quick_form()函数传入Flask-WTF表单对象并使用默认Bootstrap样式渲染它。示例4-3展示了完整的hello.py模板。

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

{% 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 %}

目前模板的内容区有两块。第一块是类为page-header的div输出一个问候语。这里使用了模板条件判断语句。在Jinja2中格式为{% if variable %}...{% else %}...{% endif %}。如果判断条件为True则渲染if和else之间的内容。如果判断条件为False则渲染else和endif之间的内容。示例模板会渲染字符串“Hello, Stranger!”当name模板参数未定义的时候。第二块内容使用wtf.quick_form()函数渲染NameForm对象。

4、启动脚本
顶层目录中的manage.py文件用于启动应用。这个脚本会在示例7-8中展示。

示例 manage.py:启动脚本

#!/usr/bin/env python
import os
from app import create_app, db
from app.models import User, Role
from flask.ext.script import Manager, Shell
from flask.ext.migrate import Migrate, MigrateCommand

app = create_app(os.getenv('FLASK_CONFIG') or 'default') 
manager = Manager(app)
migrate = Migrate(app, db)

def make_shell_context():
 return dict(app=app, db=db, User=User, Role=Role)

manager.add_command("shell", Shell(make_context=make_shell_context))
manager.add_command('db', MigrateCommand)

if __name__ == '__main__': 
 manager.run()

这个脚本开始于创建应用程序。使用环境变量FLASK_CONFIG,若它已经定义了则从中获取配置;如果没有,则是用默认配置。然后用于Python shell的Flask-Script、Flask-Migrate以及自定义上下文会被初始化。

为了方便,会增加一行执行环境,这样在基于Unix的操作系统上可以通过./manage.py来执行脚本来替代冗长的python manage.py。

5、需求文件
应用程序必须包含requirements.txt文件来记录所有依赖包,包括精确的版本号。这很重要,因为可以在不同的机器上重新生成虚拟环境,例如在生产环境的机器上部署应用程序。这个文件可以通过下面的pip命令自动生成:

(venv) $ pip freeze >requirements.txt

当安装或更新一个包之后最好再更新一下这个文件。以下展示了一个需求文件示例:

Flask==0.10.1
Flask-Bootstrap==3.0.3.1
Flask-Mail==0.9.0
Flask-Migrate==1.1.0
Flask-Moment==0.2.0
Flask-SQLAlchemy==1.0
Flask-Script==0.6.6
Flask-WTF==0.9.4
Jinja2==2.7.1
Mako==0.9.1
MarkupSafe==0.18
SQLAlchemy==0.8.4
WTForms==1.0.5
Werkzeug==0.9.4
alembic==0.6.2
blinker==1.3
itsdangerous==0.23

当你需要完美复制一个虚拟环境的时候,你可以运行以下命令创建一个新的虚拟环境:

(venv) $ pip install -r requirements.txt

当你读到这时,示例requirements.txt文件中的版本号可能已经过时了。如果喜欢你可以尝试用最近发布的包。如果遇到任何问题,你可以随时回退到需求文件中与应用兼容的指定版本。

6、单元测试
这个应用非常小以至于不需要太多的测试,但是作为示例会在示例7-9中展示两个简单的测试定义。

示例 tests/test_basics.py:单元测试

import unittest
from flask import current_app 
from app import create_app, db

class BasicsTestCase(unittest.TestCase): 
 def setUp(self):
  self.app = create_app('testing')
  self.app_context = self.app.app_context()
  self.app_context.push()
  db.create_all()

 def tearDown(self): 
  db.session.remove() 
  db.drop_all() 
  self.app_context.pop()

 def test_app_exists(self): 
  self.assertFalse(current_app is None)

 def test_app_is_testing(self): 
  self.assertTrue(current_app.config['TESTING'])

编写好的测试使用的是来自于Python标准库中标准的unittest包。setUp()和tearDown()方法在每个测试之前和之后运行,且任何一个方法必须以test_开头作为测试来执行。

建议:如果你想要学习更多使用Python的unittest包来写单元测试的内容,请参阅官方文档。
setUp()方法尝试创建一个测试环境,类似于运行应用程序。首先它创建应用程序配置用于测试并激活上下文。这一步确保测试可以和常规请求一样访问current_app。然后,当需要的时候,可以创建一个供测试使用的全新数据库。数据库和应用程序上下文会在tearDown()方法中被移除。

第一个测试确保应用程序实例存在。第二个测试确保应用程序在测试配置下运行。为了确保tests目录有效,需要在tests目录下增加__init__.py文件,不过该文件可以为空,这样unittest包可以扫描所有模块并定位测试。

建议:如果你有克隆在GitHub上的应用程序,你现在可以运行git checkout 7a来切换到这个版本的应用程序。为了确保你已经安装了所有依赖集,需要运行pip install -r requirements.txt。
为了运行单元测试,可以在manage.py脚本中增加一个自定义的命令。

下面的例子展示如何添加测试命令。

示例 manage.pyt:单元测试启动脚本

@manager.command
def test():
 """Run the unit tests."""
 import unittest
 tests = unittest.TestLoader().discover('tests') 
 unittest.TextTestRunner(verbosity=2).run(tests)

manager.command装饰器使得它可以很容易的实现自定义命令。被装饰的函数名可以被当做命令名使用,且函数的文档字符串会显示帮助信息。test()函数的执行会调用unittest包中的测试运行器。

单元测试可以像下面这样执行:

(venv) $ python manage.py test
test_app_exists (test_basics.BasicsTestCase) ... ok
test_app_is_testing (test_basics.BasicsTestCase) ... ok

.----------------------------------------------------------------------
Ran 2 tests in 0.001s

OK

7、数据库启动
与单脚本的应用相比,重构后的应用使用不同数据库。

从环境变量中获取的数据库URL作为首选,默认SQLite数据库作为可选。三个配置中的环境变量和SQLite数据库文件名是不一样的。例如,开发配置的URL是从DEV_DATABASE_URL环境变量中获取,如果没有定义则会使用名为data-dev.sqlite的SQLite数据库。

无论数据库URL源的是哪一个,都必须为新的数据库创建数据库表。如果使用了Flask-Migrate来保持迁移跟踪,数据库表可以被创建或更新到最近的版本通过下面的命令:

(venv) $ python manage.py db upgrade

相信与否,已经到了第一部分结束的地方。你现在已经学到了Flask必要的基本要素,但是你不确定如何将这些零散的知识组合在一起形成一个真正的应用程序。第二部分的目的是通过开发一个完整的应用程序来带领你继续前行。

Python 相关文章推荐
python统计一个文本中重复行数的方法
Nov 19 Python
Python错误提示:[Errno 24] Too many open files的分析与解决
Feb 16 Python
python 截取 取出一部分的字符串方法
Mar 01 Python
使用Python操作excel文件的实例代码
Oct 15 Python
快速入门python学习笔记
Dec 06 Python
python3 读取Excel表格中的数据
Oct 16 Python
python 自动批量打开网页的示例
Feb 21 Python
python 实现保存最新的三份文件,其余的都删掉
Dec 22 Python
python线程join方法原理解析
Feb 11 Python
python爬虫开发之PyQuery模块详细使用方法与实例全解
Mar 09 Python
Python新手学习函数默认参数设置
Jun 03 Python
python爬虫要用到的库总结
Jul 28 Python
Python中规范定义命名空间的一些建议
Jun 04 #Python
全面理解Python中self的用法
Jun 04 #Python
举例讲解Python中字典的合并值相加与异或对比
Jun 04 #Python
详解Python中open()函数指定文件打开方式的用法
Jun 04 #Python
Python中import导入上一级目录模块及循环import问题的解决
Jun 04 #Python
Python输出汉字字库及将文字转换为图片的方法
Jun 04 #Python
使用Python的Flask框架来搭建第一个Web应用程序
Jun 04 #Python
You might like
overlord人气高涨,却被菲利普频繁举报,第四季很难在国内上映
2020/05/06 日漫
php防注入,表单提交值转义的实现详解
2013/06/10 PHP
解析PHP强制转换类型及远程管理插件的安全隐患
2014/06/30 PHP
Linux php 中文乱码的快速解决方法
2016/05/13 PHP
js动态设置鼠标事件示例代码
2013/10/30 Javascript
js调试工具console.log()方法查看js代码的执行情况
2014/08/08 Javascript
Javascript实现多彩雪花从天降散落效果的方法
2015/02/02 Javascript
AngularJS学习笔记之依赖注入详解
2016/05/16 Javascript
详解react使用react-bootstrap当轮子造车
2017/08/15 Javascript
javascript中的replace函数(带注释demo)
2018/01/07 Javascript
vue如何在自定义组件中使用v-model
2018/05/14 Javascript
浅谈React的最大亮点之虚拟DOM
2018/05/29 Javascript
详解vue使用vue-layer-mobile组件实现toast,loading效果
2018/08/31 Javascript
mpvue网易云短信接口实现小程序短信登录的示例代码
2020/04/03 Javascript
[03:42]2014DOTA2西雅图国际邀请赛7月9日TOPPLAY
2014/07/09 DOTA
[55:45]LGD vs OG 2019国际邀请赛淘汰赛 胜者组 BO3 第三场 8.24
2019/09/10 DOTA
python中ConfigParse模块的用法
2014/09/29 Python
Python 多核并行计算的示例代码
2017/11/07 Python
python实战之实现excel读取、统计、写入的示例讲解
2018/05/02 Python
python中对数据进行各种排序的方法
2019/07/02 Python
Python sys模块常用方法解析
2020/02/20 Python
总结Pyinstaller的坑及终极解决方法(小结)
2020/09/21 Python
amazeui时间组件的实现示例
2020/08/18 HTML / CSS
日本最新流行服饰网购:Nissen
2016/07/24 全球购物
新东方旗下远程教育网站:新东方在线
2020/03/19 全球购物
面向对象编程的优势是什么
2015/12/17 面试题
C++面试题目
2013/06/25 面试题
应用艺术专业个人的自我评价
2014/01/03 职场文书
加多宝凉茶广告词
2014/03/18 职场文书
立志成才演讲稿
2014/09/04 职场文书
学校教学工作总结2015
2015/05/19 职场文书
七年级思品教学反思
2016/02/20 职场文书
2016大学生国家助学贷款承诺书
2016/03/25 职场文书
基于Golang 高并发问题的解决方案
2021/05/08 Golang
python实现自动清理文件夹旧文件
2021/05/10 Python
Java实现房屋出租系统详解
2021/10/05 Java/Android