在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中表达式i += x与i = i + x是否等价
Feb 08 Python
python中模块的__all__属性详解
Oct 26 Python
python爬取亚马逊书籍信息代码分享
Dec 09 Python
Python实现基于PIL和tesseract的验证码识别功能示例
Jul 11 Python
Python控制Firefox方法总结
Jun 03 Python
对Python 检查文件名是否规范的实例详解
Jun 10 Python
解决Django Static内容不能加载显示的问题
Jul 28 Python
基于Python实现大文件分割和命名脚本过程解析
Sep 29 Python
Django更新models数据库结构步骤
Apr 01 Python
python/golang实现循环链表的示例代码
Sep 14 Python
python 如何将office文件转换为PDF
Sep 22 Python
python自动化测试之Selenium详解
Mar 13 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
php 正则匹配函数体
2009/08/25 PHP
PHP Parse Error: syntax error, unexpected $end 错误的解决办法
2012/06/05 PHP
PHP的Socket通信之UDP通信实例
2015/07/02 PHP
使用PHP+AJAX让WordPress动态加载文章的教程
2015/12/11 PHP
用js怎么把&amp;字符换成&quot;&amp;amp:&quot;
2006/10/19 Javascript
另一个javascript小测验(代码集合)
2011/07/27 Javascript
jQuery动态改变图片显示大小(修改版)的实现思路及代码
2013/12/24 Javascript
javascript创建数组之联合数组的使用方法示例
2013/12/26 Javascript
jquery form 隐藏的input 选择
2014/04/29 Javascript
jquery实现类似淘宝星星评分功能有截图
2014/09/15 Javascript
完美实现仿QQ空间评论回复特效
2015/05/06 Javascript
JavaScript原型及原型链终极详解
2016/01/04 Javascript
基于bootstrap插件实现autocomplete自动完成表单
2016/05/07 Javascript
JavaScript中闭包的写法和作用详解
2016/06/29 Javascript
jQuery实现自动调用和触发某个事件的方法
2016/11/18 Javascript
详解Nuxt.js Vue服务端渲染摸索
2018/02/08 Javascript
详解性能更优越的小程序图片懒加载方式
2018/07/18 Javascript
vue 弹框产生的滚动穿透问题的解决
2018/09/21 Javascript
vue移动端html5页面根据屏幕适配的四种解决方法
2018/10/19 Javascript
uni-app 组件里面获取元素宽高的实现
2019/12/27 Javascript
详解字典树Trie结构及其Python代码实现
2016/06/03 Python
Python中的os.path路径模块中的操作方法总结
2016/07/07 Python
windows下Python实现将pdf文件转化为png格式图片的方法
2017/07/21 Python
python分治法求二维数组局部峰值方法
2018/04/03 Python
Python实现多条件筛选目标数据功能【测试可用】
2018/06/13 Python
python看某个模块的版本方法
2018/10/16 Python
Python中一些深不见底的“坑”
2019/06/12 Python
numpy和pandas中数组的合并、拉直和重塑实例
2019/06/28 Python
python爬取网易云音乐热歌榜实例代码
2020/08/07 Python
CSS3中HSL和HSLA的简单使用示例
2015/07/14 HTML / CSS
小学生自我评价范文
2014/01/25 职场文书
策划创业计划书
2014/02/06 职场文书
欢迎领导检查标语
2014/06/27 职场文书
关于运动会广播稿50字
2014/10/18 职场文书
工程部主管岗位职责
2015/02/12 职场文书
Windows Server 2008配置防火墙策略详解
2022/06/28 Servers