关键词: flask, form
如何用wtforms来维护一份表单,部分文档来自李林克斯, 然后又根据实际的情况进行了添加,在此感谢!
在一个 Web 应用里,不管是为了业务逻辑的正确性,还是系统安全性,做好参数(querystring, form, json)验证都是非常必要的。
WTForms 是一个非常好用而且强大的表单校验和渲染的库,提供 Form 基类用于定义表单结构(类似 ORM),内置了丰富的字段类型和校验方法,可以很方便的用来做校验。如果应用需要输出 HTML,集成到模板里也很容易。对于 JSON API 应用,用不到渲染的功能,但是结构化的表单和校验功能依然非常有用。
以一个注册的应用场景为例,用户输入用户名、邮箱、密码、确认密码,服务程序先检查参数然后处理登录逻辑。这几个字段都是必填的,此外还有一些额外的限制:
- 用户名:长度在 3-20 之间
- 邮箱:合法的邮箱格式,比如 “abc” 就不合法
- 密码:长度在 8-20 之间,必须同时包含大小写字母
- 确认密码:必须与密码一致
如果参数不合法,返回 400;登录逻辑略去不表。
最原始的做法,就是直接在注册的接口里取出每个参数,逐个手动校验。这种做法可能的代码是:
|
|
有可能是我的写法不太对,但是这样检查参数的合法性,实在不够优雅。检查参数的代码行数甚至超出了注册的逻辑,也有些喧宾夺主的感觉。可以把这些代码移出来,使得业务逻辑代码更加清晰一点。下面先用 WTForms 来改造一下。
|
|
这个版本带来的好处很明显:
- 参数更加结构化了,所有字段名和类型一目了然
- 有内置的,语义清晰的校验方法,可以组合使用
- 还能自定义额外的校验方法,方法签名是
def validate_xx(self, field)
,其中xx
是字段名,通过field.data
来获取输入的值 - 还有没体现出来的,就是丰富的错误提示信息,既有内置的,也可以自定义
再看原来的 register
方法,代码变得更加简洁和清晰,整体的编码质量也得到了提升。
那么再考虑一下更复杂的场景,在一个返回 JSON 的 API 应用里,有很多 API,有不同的参数提交方式(GET 方法通过 query string,POST 方法可能有 form 和 JSON),一样的校验错误处理方式(abort(400) 或其他)。我们依然可以像上面那样处理,但如果再借助装饰器改进一下,又能少写几行“重复”的代码。
需要注意的是,WTForms 的 formdata 支持的是类似 Werkzeug/Django/WebOb 中的 MultiDict
的数据结构。Flask 中的 request.json
是一个 dict
类型,所以需要先包装一下。
继续改造注册的例子:
|
|
实现了一个叫 validate_form
的装饰器,指定一个 Form 类,处理统一的参数获取、校验和错误处理,如果一切正确,再把 Form 对象保存到全局变量 g
里面,这样就可以在 view 函数里取出来用了。现在的 register
方法变得更加简洁,甚至都看不到检查参数的那些代码,只需要关心具体的和注册相关的逻辑本身就好。
这个装饰器的可重用性非常好,其他的接口只要定义一个 Form 类,然后调用一下装饰器,再从 g
获取 Form 对象。不仅省了很多心思和体力劳动,代码也变得更加清晰优雅和 Pythonic.
NOTICE:
因为MultDict
对list
的支持特别不友好,容易把list
的表单变成一个个的tuple形式。
例子media:[‘facebook’,’adwords’]
会被转为(‘media’,’facebook’),(‘media’,’adwords’)
因此,我们在装饰器内加入如下的设置,进行此问题的规避
|
|