WTForms基础操作,可见骚师博客
WTForms主要是两个功能:1.生成HTML标签 2.对数据格式进行验证
这里主要带大家去看下WTForms源码,是怎么个实现流程?
首先看到下面的示例代码,捋顺一下类之间的关系
#!/usr/bin/env python
# -*- coding:utf-8 -*-
from flask import Flask, render_template, request, redirect
from wtforms import Form
from wtforms.fields import core
from wtforms.fields import html5
from wtforms.fields import simple
from wtforms import validators
from wtforms import widgets
app = Flask(__name__, template_folder='templates')
app.debug = True
class LoginForm(Form):
name = simple.StringField(
label='用户名',
validators=[
validators.DataRequired(message='用户名不能为空.'),
validators.Length(min=6, max=18, message='用户名长度必须大于%(min)d且小于%(max)d')
],
widget=widgets.TextInput(),
render_kw={'class': 'form-control'}
)
pwd = simple.PasswordField(
label='密码',
validators=[
validators.DataRequired(message='密码不能为空.'),
validators.Length(min=8, message='用户名长度必须大于%(min)d'),
validators.Regexp(regex="^(?=.*[a-z])(?=.*[A-Z])(?=.*d)(?=.*[$@$!%*?&])[A-Za-zd$@$!%*?&]{8,}",
message='密码至少8个字符,至少1个大写字母,1个小写字母,1个数字和1个特殊字符')
],
widget=widgets.PasswordInput(),
render_kw={'class': 'form-control'}
)
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'GET':
form = LoginForm()
return render_template('login.html', form=form)
else:
form = LoginForm(formdata=request.form)
if form.validate():
print('用户提交数据通过格式验证,提交的值为:', form.data)
else:
print(form.errors)
return render_template('login.html', form=form)
if __name__ == '__main__':
app.run()
form类下,封装了两个field对象,分别为name,pwd,每个field对象下面又给widget封装 了input对象
第一:生成 HTML标签
在前端执行{{form.name}}和后端print(form.name)的效果是一样的,所以它会执行对象里的__str__方法,form.name是field对象,也就是执行field对象下的__str__方法,比如StringField,它下面是没有这方法的,但是父类中Field是有的
def __str__(self):
return self()
代码执行了return self(),也就会执行field类中的__call__方法
def __call__(self, **kwargs):
return self.meta.render_field(self, kwargs)
而在render_field方法中最后又执行了field.widget(field, **render_kw),field.widget是StringField下面封装的widgets.TextInput对象,所以它又会去调用TextInput类下的__call__方法
在父类input类下,找到了生成html标签的代码
def __call__(self, field, **kwargs):
kwargs.setdefault('id', field.id)
kwargs.setdefault('type', self.input_type)
if 'value' not in kwargs:
kwargs['value'] = field._value()
if 'required' not in kwargs and 'required' in getattr(field, 'flags', []):
kwargs['required'] = True
return HTMLString('<input %s>' % self.html_params(name=field.name, **kwargs))
第二:对数据进行验证
- 获取前端请求字段数据
- 循环每个字段进行正则匹配进行验证
class InputText(object):
def __str__(self):
return '<input type="text" />'
class InputEmail(object):
def __str__(self):
return '<input type="mail" />'
class StringField(object):
def __init__(self,wg,reg):
self.wg = wg
self.reg = reg
def __str__(self):
return str(self.wg)
def valid(self,val):
import re
return re.match(self.reg,val)
class LoginForm(object):
xyy = StringField(wg=InputText(),reg='d+')
lw = StringField(wg=InputEmail(),reg='w+')
def __str__(self,form):
self.form = "用户发来的所有数据{xyy:'df',lw:'sdf'}"
def validate(self):
fields = {'xyy':self.xyy,'lw':self.lw}
for name,field in fields.items():
# name是 xyy、。lw
# field: StringField(wg=InputText(),reg='d+') / StringField(wg=InputEmail(),reg='w+')
# 'df'
field.valid(self.form[name])
#
# wp = LoginForm()
# print(wp.xyy)
# print(wp.lw)
wp = LoginForm(formdata=request.form)
wp.validate()
从上面我们抽离出来的简单模型,看得出:各个类,分工还是很明确的,input类负责生成标签,而field类负责自己字段数据验证,而form主要统一验证,返回结果
上面过程只是简单分析,详细分析,请收看下一集
开始分析前,我们需要了解几个知识点
- __new__ 用于生成对象
class Foo(object):
def __init__(self):
pass
def __new__(cls, *args, **kwargs):
"""
用于生成对象
:param args:
:param kwargs:
:return:
"""
return super(Foo,cls).__new__(cls, *args, **kwargs)
# return "666"
obj = Foo()
print(obj)
- __dict__获取所有成员
class Foo(object):
AGE = 123
def __init__(self, na):
self.name = na
#获取所有成员键值对
print(Foo.__dict__)
#获取所有成员的键
print(dir(Foo))
obj = Foo('laoliu')
print(obj.__dict__)
- 列表排序
v = [11,22,334,4,2]
v.sort()
print(v)
v1 = [
(11,'alex1'),
(2,'alex2'),
(2,'alex3'),
(7,'alex4'),
]
#以元组的第一个元素排序
v1.sort(key=lambda x:x[0])
print(v1)
#以元组的第一个元素排序,如果相同,则以第二个元素排序
v1.sort(key=lambda x:(x[0],x[1]))
print(v1)
- __mro__ 按深度优先的关系,找到所有的父类
class F4(object):
pass
class F3(F4):
pass
class F2_5(object):
pass
class F2(F2_5):
pass
class F1(F2,F3):
pass
print(F1.__mro__)
接下来就开始我们快乐的源码分析之旅吧,let's go
还是看到我们的示例代码中,代码从上往下解释class LoginForm(Form),会去执行创建LoginForm类里的__init__方法,那么创建LoginForm的类是哪个呢,你从父类中看到这句Form(with_metaclass(FormMeta, BaseForm)),就应该很快就能确定是FormMeta,而且FormMeta确实继承type类,所以创建LoginForm类时会去执行FormMeta里的__init__方法(如果这里还不懂,可以去看我的另一篇博客)
def __init__(cls, name, bases, attrs):
type.__init__(cls, name, bases, attrs)
cls._unbound_fields = None
cls._wtforms_meta = None
在__init__方法里,给LoginForm类封装了两个字段_unbound_fields,_wtforms_meta,都为None
代码继续往下解释,遇到LoginForm定义两个静态字段name,pwd,进行simple.StringField实例化操作,所以会去执行StringField的__init__方法,执行__init__方法前,它先执行__new__方法,StringField中没有,父类Field中有
def __new__(cls, *args, **kwargs):
if '_form' in kwargs and '_name' in kwargs:
return super(Field, cls).__new__(cls)
else:
return UnboundField(cls, *args, **kwargs)
kwargs是 我们传入的参数,我们并没有传入了_form和_name参数,所以它会走else分支,最终name,pwd被赋值UnboundField对象,在这个对象中,封装了StringField类和我们实例的参数,所以创建LoginForm类,会是这个样子
print(LoginForm.__dict__)
LoginForm ={
'__module__': '__main__',
'name': <1 UnboundField(StringField, (), {'label': '用户名', 'validators': [<wtforms.validators.DataRequired object at 0x00000000037DAEB8>, <wtforms.validators.Length object at 0x000000000382B048>], 'widget': <wtforms.widgets.core.TextInput object at 0x000000000382B080>, 'render_kw': {'class': 'form-control'}})>,
'pwd': <2 UnboundField(PasswordField, (), {'label': '密码', 'validators': [<wtforms.validators.DataRequired object at 0x000000000382B0F0>, <wtforms.validators.Length object at 0x000000000382B128>, <wtforms.validators.Regexp object at 0x000000000382B160>], 'widget': <wtforms.widgets.core.PasswordInput object at 0x000000000382B208>, 'render_kw': {'class': 'form-control'}})>,
'__doc__': None,
'_unbound_fields': None,
'_wtforms_meta': None
}
你可能会想:怎么不直接实例StringField,而是实例一个UnboundField对象呢?UnboundField能够帮助我们排序,它里面维护了一个计数器,你可能又想了 干嘛要排序?你在前端渲染时,循环form时需要一直保持一个规定顺序,而在后端,我们在定义代码,是从上往下定义字段的,而获取时,使用字典方式获取的,字典是无序的,为了还保持我们定义的顺序,就用上面对象来做到这点
到视图函数中,实例LoginForm(),会执行下面几个步骤
- 执行FormMeta的__call__方法
- 执行LoginForm的__new__方法
- 执行LoginForm的__init__方法
首先看到__call__方法中的这段
if cls._unbound_fields is None: #此时还为None,会走这个分支
fields = []
for name in dir(cls): #循环 类的成员所有键
if not name.startswith('_'): #排除带_成员,只剩name,pwd
unbound_field = getattr(cls, name) #获取name和pwd对应的UnboundField对象
if hasattr(unbound_field, '_formfield'): #UnboundField类中_formfield默认为True
fields.append((name, unbound_field))
#进行计算器排序
fields.sort(key=lambda x: (x[1].creation_counter, x[0]))
cls._unbound_fields = fields #最终赋给LoginForm的_unbound_fields 字段
所以,此时_unbound_fields的存储结构为:
[
(name, UnboundField对象(计算器,StringField类,参数)),
(pwd, UnboundField对象(计算器,PasswordField类,参数))
]
我们在看到__call__方法里的这段代码
if cls._wtforms_meta is None: #此时也是为None
bases = []
for mro_class in cls.__mro__: #获取当前类的所有继承类
#我们写类里是没有Meta这个字段的,但是继承类Form中Meta = DefaultMeta
if 'Meta' in mro_class.__dict__:
bases.append(mro_class.Meta) #bases = [DefaultMeta]
#实例Meta类,继承DefaultMeta,并赋给LoginForm的_wtforms_meta
cls._wtforms_meta = type('Meta', tuple(bases), {})
执行完__call__方法后,此时LoginForm长这样
class Meta(DefaultMeta,):
pass
LoginForm ={
'__module__': '__main__',
'name': <2 UnboundField(StringField, (), {'label': '用户名', 'validators': [<wtforms.validators.DataRequired object at 0x00000000037DAEB8>, <wtforms.validators.Length object at 0x000000000382B048>], 'widget': <wtforms.widgets.core.TextInput object at 0x000000000382B080>, 'render_kw': {'class': 'form-control'}})>,
'pwd': <1 UnboundField(PasswordField, (), {'label': '密码', 'validators': [<wtforms.validators.DataRequired object at 0x000000000382B0F0>, <wtforms.validators.Length object at 0x000000000382B128>, <wtforms.validators.Regexp object at 0x000000000382B160>], 'widget': <wtforms.widgets.core.PasswordInput object at 0x000000000382B208>, 'render_kw': {'class': 'form-control'}})>,
'__doc__': None,
'_unbound_fields': [
(name, UnboundField对象(1,simple.StringField,参数)),
(pwd, UnboundField对象(2,simple.PasswordField,参数)),
],
'_wtforms_meta': Meta
}
由于LoginForm和父类中没有__new__方法,没有就不执行
LoginForm中没有定义__init__方法,找到父类中Form的__init__方法执行
看到这段代码,最后执行了又执行了父类BaseForm的__init__方法,并把_unbound_fields和meta_obj传了进入
#实例Meta对象(主要用于生成csrf隐藏标签)
meta_obj = self._wtforms_meta()
if meta is not None and isinstance(meta, dict):
meta_obj.update_values(meta) #如果form里有设置meta,更新到meta_obj
#执行父类中的__init__方法
#_unbound_fields = [
# (name, UnboundField对象(1,simple.StringField,参数)),
# (pwd, UnboundField对象(2,simple.PasswordField,参数)),
# ],
super(Form, self).__init__(self._unbound_fields, meta=meta_obj, prefix=prefix)
BaseForm的__init__方法这段代码,真正实例StringField对象和PasswordField是在这下面干的
if meta.csrf: #如果有设置meta的csrf,就创建隐藏标签
self._csrf = meta.build_csrf(self)
extra_fields.extend(self._csrf.setup_form(self))
for name, unbound_field in itertools.chain(fields, extra_fields):
#name --> name,pwd
#unbound_field --> UnboundField对象(1,simple.StringField,参数)
# UnboundField对象(2,simple.PasswordField,参数)
options = dict(name=name, prefix=prefix, translations=translations)
field = meta.bind_field(self, unbound_field, options)
#实例化Field对象后,把LoginForm对象中_fields下每个字段的UnboundField对象替换成Field对象
self._fields[name] = field
看到meta.bind_field,最终调用 了unbound_field.bind方法
def bind(self, form, name, prefix='', translations=None, **kwargs):
kw = dict(
self.kwargs,
_form=form,
_prefix=prefix,
_name=name,
_translations=translations,
**kwargs
)
#实例化Field对象,此时参数携带_form和_name,在__new__方法中直接实例Field对象,不再是UnboundField对象
return self.field_class(*self.args, **kw)
执行BaseForm的__init__方法后,此时LoginForm对象的存储内容长这样
form = {
_fields: {
name: StringField对象(),
pwd: PasswordField对象(),
}
}
再看到Form.__init__下的另外一段代码
for name, field in iteritems(self._fields):
setattr(self, name, field) #把每个字段设置到对象中,方便支持obj.name,obj.pwd访问
#为每个字段标签设置默认值和验证的值
self.process(formdata, obj, data=data, **kwargs)
所以此时的数据结构为
form = {
_fields: {
name: StringField对象(),
pwd: PasswordField对象(),
}
name: StringField对象(widget=widgets.TextInput()),
pwd: PasswordField对象(widget=widgets.PasswordInput())
}
上面源码分析也只在get请求下,LoginForm不传值
那如果走post,也是LoginForm传值,它又会怎么走呢?
请求来的时候,它还是要先走实例LoginForm类的流程,这和get请求里的过程是一样的,唯一不同的,在执行LoginForm父类__init__方法时,self.process(formdata, obj, data=data, **kwargs)这里的formdata是有值的,然后看到process方法里的这段代码
#循环所有的字段
'''
_fields: {
name: StringField对象(),
pwd: PasswordField对象(),
}
'''
#formdata 前端传来的值 类似于{'name':alex,'pwd':123}
for name, field, in iteritems(self._fields):
if obj is not None and hasattr(obj, name):
field.process(formdata, getattr(obj, name))
elif name in kwargs:
field.process(formdata, kwargs[name])
else: #kwargs 和 obj此时都没值,所以会走这个分支
#field=StringField...
#把前端的值 封装在StringField对象里,此时对象里有 要验证的值 和 正则
field.process(formdata)
大概都能猜到field.process(formdata)干了一件什么事,就把前端的值对应字段对象进行封装
try:
self.process_data(data) #进行字段赋值操作
except ValueError as e:
self.process_errors.append(e.args[0])
if formdata is not None:
if self.name in formdata:
self.raw_data = formdata.getlist(self.name)
else:
self.raw_data = []
try:
self.process_formdata(self.raw_data) #也是进行字段赋值操作
except ValueError as e:
self.process_errors.append(e.args[0])
传值form后,接下来的就是验证了,猜它都会循环每个字段,调用每个字段的验证方法
接下来就看到LoginForm的验证方法
def validate(self):
extra = {}
for name in self._fields:
#获取每个字段的钩子函数 validate_name validate_pwd
inline = getattr(self.__class__, 'validate_%s' % name, None)
if inline is not None:
extra[name] = [inline]
'''
前提是你有定义这么一个函数
添加完后extra = {
name : [validate_name],
pwd : [validate_pwd]
}
'''
return super(Form, self).validate(extra)
而在执行父类里的验证方法里,就负责调用了每个字段里的验证方法
def validate(self, extra_validators=None):
self._errors = None
success = True
'''
extra_validators = {
name : [validate_name],
pwd : [validate_pwd]
}
'''
for name, field in iteritems(self._fields):
if extra_validators is not None and name in extra_validators:
extra = extra_validators[name] #获取字段钩子函数列表
else:
extra = tuple()
#调用字段的验证方法,并传入钩子函数,self为form对象,到时候用它里面的正则和要验证的值
if not field.validate(self, extra):
success = False
return success
在字段的验证方法中,有这么一段,调用了_run_validation_chain方法
if not stop_validation:
'''
validators=[
validators.DataRequired(message='用户名不能为空.'),
validators.Length(min=6, max=18, message='用户名长度必须大于%(min)d且小于%(max)d')
]
extra_validators=[钩子函数]
'''
chain = itertools.chain(self.validators, extra_validators)
stop_validation = self._run_validation_chain(form, chain)
执行_run_validation_chain
#循环每条验证规则进行验证
for validator in validators:
try:
#validator要么是钩子函数 要么是对象,对象则又去执行__call__方法
validator(form, self)
except StopValidation as e:
if e.args and e.args[0]:
self.errors.append(e.args[0])
return True
except ValueError as e:
self.errors.append(e.args[0])
return False
分析实例代码
#!/usr/bin/env python
# -*- coding:utf-8 -*-
from flask import Flask, render_template, request, redirect
from wtforms import Form
from wtforms.fields import simple
from wtforms import validators
from wtforms import widgets
app = Flask(__name__, template_folder='templates')
app.debug = True
# 1.由于 metaclass=FormMeta,所以LoginForm是由FormMeta创建
# 2. 执行 FormMeta.__init__
# LoginForm._unbound_fields = None
# LoginForm._wtforms_meta = None
# 3. 解释字段:
# name = simple.StringField(...)
# pwd = simple.PasswordField(...)
# 结果:
# LoginForm.name = UnboundField(simple.StringField,StringField的所有参数)
# LoginForm.pwd = UnboundField(simple.PasswordField,PasswordField的所有参数)
class LoginForm(Form):
# 字段(内部包含正则表达式)
name = simple.StringField(
label='用户名',
validators=[
validators.DataRequired(message='用户名不能为空.'),
validators.Length(min=6, max=18, message='用户名长度必须大于%(min)d且小于%(max)d')
],
widget=widgets.TextInput(), # 页面上显示的插件
render_kw={'class': 'form-control'}
)
# 字段(内部包含正则表达式)
pwd = simple.PasswordField(
label='密码',
validators=[
validators.DataRequired(message='密码不能为空.'),
validators.Length(min=8, message='用户名长度必须大于%(min)d'),
validators.Regexp(regex="^(?=.*[a-z])(?=.*[A-Z])(?=.*d)(?=.*[$@$!%*?&])[A-Za-zd$@$!%*?&]{8,}",
message='密码至少8个字符,至少1个大写字母,1个小写字母,1个数字和1个特殊字符')
],
widget=widgets.PasswordInput(),
render_kw={'class': 'form-control'}
)
def validate_name(self,form):
pass
"""
print(LoginForm.__dict__)
LoginForm ={
'__module__': '__main__',
'name': <1 UnboundField(StringField, (), {'label': '用户名', 'validators': [<wtforms.validators.DataRequired object at 0x00000000037DAEB8>, <wtforms.validators.Length object at 0x000000000382B048>], 'widget': <wtforms.widgets.core.TextInput object at 0x000000000382B080>, 'render_kw': {'class': 'form-control'}})>,
'pwd': <2 UnboundField(PasswordField, (), {'label': '密码', 'validators': [<wtforms.validators.DataRequired object at 0x000000000382B0F0>, <wtforms.validators.Length object at 0x000000000382B128>, <wtforms.validators.Regexp object at 0x000000000382B160>], 'widget': <wtforms.widgets.core.PasswordInput object at 0x000000000382B208>, 'render_kw': {'class': 'form-control'}})>,
'__doc__': None,
'_unbound_fields': None,
'_wtforms_meta': None
}
"""
@app.route('/login', methods=['GET', 'POST'])
def login():
# 实例LoginForm
# 1. 执行FormMeta的__call__方法
"""
class Meta(DefaultMeta,):
pass
LoginForm ={
'__module__': '__main__',
'name': <2 UnboundField(StringField, (), {'label': '用户名', 'validators': [<wtforms.validators.DataRequired object at 0x00000000037DAEB8>, <wtforms.validators.Length object at 0x000000000382B048>], 'widget': <wtforms.widgets.core.TextInput object at 0x000000000382B080>, 'render_kw': {'class': 'form-control'}})>,
'pwd': <1 UnboundField(PasswordField, (), {'label': '密码', 'validators': [<wtforms.validators.DataRequired object at 0x000000000382B0F0>, <wtforms.validators.Length object at 0x000000000382B128>, <wtforms.validators.Regexp object at 0x000000000382B160>], 'widget': <wtforms.widgets.core.PasswordInput object at 0x000000000382B208>, 'render_kw': {'class': 'form-control'}})>,
'__doc__': None,
'_unbound_fields': [
(name, UnboundField对象(1,simple.StringField,参数)),
(pwd, UnboundField对象(2,simple.PasswordField,参数)),
],
'_wtforms_meta': Meta
}
"""
# 2. 执行LoginForm的__new__方法
# pass
# 3. 执行LoginForm的__init__方法
"""
LoginForm ={
'__module__': '__main__',
'name': <2 UnboundField(StringField, (), {'label': '用户名', 'validators': [<wtforms.validators.DataRequired object at 0x00000000037DAEB8>, <wtforms.validators.Length object at 0x000000000382B048>], 'widget': <wtforms.widgets.core.TextInput object at 0x000000000382B080>, 'render_kw': {'class': 'form-control'}})>,
'pwd': <1 UnboundField(PasswordField, (), {'label': '密码', 'validators': [<wtforms.validators.DataRequired object at 0x000000000382B0F0>, <wtforms.validators.Length object at 0x000000000382B128>, <wtforms.validators.Regexp object at 0x000000000382B160>], 'widget': <wtforms.widgets.core.PasswordInput object at 0x000000000382B208>, 'render_kw': {'class': 'form-control'}})>,
'__doc__': None,
'_unbound_fields': [
(name, UnboundField对象(1,simple.StringField,参数)),
(pwd, UnboundField对象(2,simple.PasswordField,参数)),
],
'_wtforms_meta': Meta
}
form = {
_fields: {
name: StringField对象(),
pwd: PasswordField对象(),
}
name: StringField对象(widget=widgets.TextInput()),
pwd: PasswordField对象(widget=widgets.PasswordInput())
}
"""
if request.method == 'GET':
form = LoginForm()
# form._fields['name']
# form.name = StringField对象()
"""
1. StringField对象.__str__
2. StringField对象.__call__
3. meta.render_field(StringField对象,)
4. StringField对象.widget(field, **render_kw)
5. 插件.__call__()
"""
print(form.name) #
"""
0. Form.__iter__: 返回所有字段对象
1. StringField对象.__str__
2. StringField对象.__call__
3. meta.render_field(StringField对象,)
4. StringField对象.widget(field, **render_kw)
5. 插件.__call__()
"""
for item in form:
# item是fields中的每一个字段
print(item)
return render_template('login.html',form=form)
else:
# 上述流程+
# 从请求中获取每个值,再复制到到每个字段对象中
"""
form = {
_fields: {
name: StringField对象(data=你输入的用户名),
pwd: PasswordField对象(pwd=你输入的密码),
}
name: StringField对象(widget=widgets.TextInput(data=你输入的用户名)),
pwd: PasswordField对象(widget=widgets.PasswordInput(pwd=你输入的密码))
}
"""
# 请求发过来的值
form = LoginForm(formdata=request.form) # 值.getlist('name')
# 实例:编辑
# # 从数据库对象
# form = LoginForm(obj='值') # 值.name/值.pwd
#
# # 字典 {}
# form = LoginForm(data=request.form) # 值['name']
# 1. 循环所有的字段
# 2. 获取每个字段的钩子函数
# 3. 为每个字段执行他的验证流程 字段.validate(钩子函数+内置验证规则)
if form.validate():
print(form.data)
else:
print(form.errors)
if __name__ == '__main__':
app.run()