WTForms
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-z\d$@$!%*?&]{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-z\d$@$!%*?&]{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()