python-flask-wtforms组件流程源码
在最开始要弄明白一点,类都是由元类创建的。在定义类 class Foo:pass的时候(类也是对象),就会执行type类或者type派生类的__init__方法,当Foo()时:执行type类或者type派生类的__call__方法,在__call__方法中调用了Foo类的__new__方法创建了一个对象,接着执行__init__方法为这个创建的对象进行赋值属性。
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 widgets from wtforms import validators app = Flask(__name__, template_folder='templates') app.debug = True #0 定义LogonForm类 class LoginForm(Form):
#1 StringField类的实例化 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'} ) class Meta: csrf = False def validate_pwd(self,*args,**kwargs): pass @app.route('/login', methods=['GET', 'POST']) def login(): if request.method == 'GET':
#2.实例化一个LoginForm对象 form = LoginForm()
#第3步
# print(form.name) return render_template('login.html', form=form) else:
#2.3步 form = LoginForm(formdata=request.form)
#4步,验证 if form.validate(): print('用户提交数据通过格式验证,提交的值为:', form.data) else: print(form.errors) return render_template('login.html', form=form) def test(): form = LoginForm() if __name__ == '__main__': app.run()
第0步:
在定义LoginForm类的时候我们看看发生了什么
首先我们要知道metaclass的另外一种方式:with_metaclass
metaclass的另外一种方式: class MyType(type): def __init__(self,*args,**kwargs): print('xxxx') super(MyType,self).__init__(*args,**kwargs) def __call__(cls, *args, **kwargs): obj = cls.__new__(cls,*args, **kwargs) cls.__init__(obj,*args, **kwargs) # Foo.__init__(obj) return obj def with_metaclass(base): return MyType("MyType",(base,),{}) class Foo(with_metaclass(object)): def __init__(self,name): self.name = name #打印结果: xxxx xxxx
所以我们去Form中找找,发现了metaclass的另外一种方式
class Form(with_metaclass(FormMeta, BaseForm)): pass
我们再去with_metaclass看看
def with_metaclass(meta, base=object): return meta("NewBase", (base,), {})
# FormMeta("NewBase", (BaseForm,), {}) # 通过FormMeta创建了一个NewBase类,NewBase类继承了BaseForm类
# 那你有没有疑问,为什么 FormMeta类可以创建类呢? 我们去FormMeta类看看
class FormMeta(type):
pass
#发现FormMeta继承了type类,所以刚才我们的疑问迎刃而解。
那就是说当LoginForm定义的时候执行了FormMeta类的__init__方法
class FormMeta(type): def __init__(cls, name, bases, attrs): type.__init__(cls, name, bases, attrs) cls._unbound_fields = None cls._wtforms_meta = None
这步完成后 LoginForm类有两个属性:cls._unbound_fields = None和 cls._wtforms_meta = None
第1步:实例化StringField类的对象,首先应该去StringField中找__new__方法
class StringField(Field): pass
#发现StringField类中没有,那我们就去基类中
class Field(object): def __new__(cls, *args, **kwargs):
#判断不成立,走else if '_form' in kwargs and '_name' in kwargs: return super(Field, cls).__new__(cls) else:
#返回一个UnboundField对象 return UnboundField(cls, *args, **kwargs) # cls = simple.StringField 这个类
class UnboundField(object): _formfield = True creation_counter = 0 def __init__(self, field_class, *args, **kwargs): UnboundField.creation_counter += 1 self.field_class = field_class # self.field_class = simple.StringField self.args = args self.kwargs = kwargs self.creation_counter = UnboundField.creation_counter #这个数字,在后面会根据这个进行排序
这步完成后,我们知道 LoginForm的 name和pwd字段都等于UnboundField 的对象
第2步:实例化LoginForm的对象会执行FormMeta的__call__方法
class FormMeta(type): def __call__(cls, *args, **kwargs): if cls._unbound_fields is None: fields = [] #获取LoginForm类中的所有字段 for name in dir(cls): if not name.startswith('_'): #获取该字段的值 unbound_field = getattr(cls, name) #unbound_field 是一个UnboundField对象 if hasattr(unbound_field, '_formfield'): # _formfield = True fields.append((name, unbound_field)) # [("name",UnboundField对象),("pwd",UnboundField对象)] fields.sort(key=lambda x: (x[1].creation_counter, x[0])) #根据UnboundField对象的creation_counter属性对fields列表进行排序 cls._unbound_fields = fields # LoginForm._unbound_fields = [("name",UnboundField对象),("pwd",UnboundField对象)] if cls._wtforms_meta is None: bases = [] for mro_class in cls.__mro__: #循环当前类和基类组成的元组 if 'Meta' in mro_class.__dict__: #如果类中有Meta类,就把Meta类添加到 bases列表中 bases.append(mro_class.Meta) cls._wtforms_meta = type('Meta', tuple(bases), {}) #LoginForm._wtforms_meta = 一个新的Meta类,它继承了所有的Meta类,这样做好处在于:通过新Meta类可以取到无论是LoginForm或者LoginForm基类中的任何Meta类 return type.__call__(cls, *args, **kwargs)
接着到LoginForm或基类中找__new__方法,发现都没有,那就继续找LoginForm或基类中的__init__方法
class Form(with_metaclass(FormMeta, BaseForm)): Meta = DefaultMeta def __init__(self, formdata=None, obj=None, prefix='', data=None, meta=None, **kwargs): meta_obj = self._wtforms_meta()
#2.1 执行父类的__init__方法 super(Form, self).__init__(self._unbound_fields, meta=meta_obj, prefix=prefix) #2.2 for name, field in iteritems(self._fields):
#这个self是LoginForm对象 setattr(self, name, field)
#2.3步 self.process(formdata, obj, data=data, **kwargs)
这时候要注意:Form的基类是with_metaclass函数创建的 NewBase类,NewBase类继承了BaseForm类
所以第2.1步:执行了BaseForm类的__init__方法
class BaseForm(object): #这个self是LoginForm对象 def __init__(self, fields, prefix='', meta=DefaultMeta()): self.meta = meta #meta是新创建的Meta类的对象 self._fields = OrderedDict() extra_fields = [] #从这句话我们可以看出 在自定义LoginForm中的Meta类内可以写字段: csrf = False if meta.csrf: self._csrf = meta.build_csrf(self) extra_fields.extend(self._csrf.setup_form(self)) #第2.1.1步 for name, unbound_field in itertools.chain(fields, extra_fields):
#name 是LoginForm中的字段,unbound_field是 UnboundField对象 field = meta.bind_field(self, unbound_field, options)
#第2.1.2步: 到有序字典中赋值 self._fields[name] = field # {"name":simple.StringField类对象,"pwd":simple.PasswordField类对象}
第2.1.1步:执行meta.bind_field方法
class DefaultMeta(object): def bind_field(self, form, unbound_field, options):
#2.1.1.1 form是LoginFrom对象 return unbound_field.bind(form=form, **options)
在 这里你没有疑问:meta是新创建的Meta类对象,为什么会执行 DefaultMeta类的bind_field方法呢?
那是因为新Meta类继承了 LoginForm类和其基类中的所有Meta类
class Form(with_metaclass(FormMeta, BaseForm)): Meta = DefaultMeta
看到上面这段代码之后你就明白为什么会跑到DefaultMeta类中找方法了吧
第2.1.1.1步:
class UnboundField(object):
def bind(self, form, name, prefix='', translations=None, **kwargs):
#就是在这步把_form当参数传进field_class方法
kw = dict(
self.kwargs,
_form=form,
_prefix=prefix,
_name=name,
_translations=translations,
**kwargs
)
#2.1.1.1.1步
return self.field_class(*self.args, **kw)
#在第1步中我们可以看到 self.field_class = simple.StringField
把各自字段相应的类实例化的对象返回给2.1.1步,然后又赋值给了 field 变量
第2.1.1.1.1步:执行simple.StringField或其基类的__init__方法
class Field(object): def __init__(self, label=None, validators=None, filters=tuple(), description='', id=None, default=None, widget=None, render_kw=None, _form=None, _name=None, _prefix='', _translations=None, _meta=None): if _meta is not None: self.meta = _meta
#_form=form elif _form is not None: self.meta = _form.meta #self.meta = form.meta = 新创建的Meta类对象 else: raise TypeError("Must provide one of _form or _meta") self.render_kw = render_kw self.name = _prefix + _name self.type = type(self).__name__ self.id = id or self.name
故,2.1步执行完成后 form(LoginForm对象)._fields["name"] = simple.StringField类对象
第2.2步:赋值操作
form.name=simple.StringField类对象
form.pwd=simple.PasswordField类对象
第2.3步:此步是实例化LoginForm时传值的情况分析
根据此行代码我们可以看出,数据初始化传值有三种形式: 1. data=字典, 2. obj=对象.字段 , 3. formdata=有getlist方法
self.process(formdata, obj, data=data, **kwargs)
例如:
class User:
def __init__(self,name):
self.name = name
obj = User('alex')
form = LoginForm(data={'name':'alex'})
form = LoginForm(obj=obj)
form = LoginForm(formdata=request.form/args)
class BaseForm(object): def process(self, formdata=None, obj=None, data=None, **kwargs): #第2.3.1步 formdata = self.meta.wrap_formdata(self, formdata) if data is not None: kwargs = dict(data, **kwargs) for name, field, in iteritems(self._fields):
#name = name ,field = simple.StringField类对象
#传值第2种方式 if obj is not None and hasattr(obj, name): field.process(formdata, getattr(obj, name))
#传值第1种方式 elif name in kwargs: field.process(formdata, kwargs[name])
#传值第3种方式 else:
# 第 2.3.2步 field.process(formdata)
第2.3.1步
class DefaultMeta(object):
def wrap_formdata(self, form, formdata):
#判断传进来的formdata有没有getlist方法 if formdata is not None and not hasattr(formdata, 'getlist'): if hasattr(formdata, 'getall'): return WebobInputWrapper(formdata) else: raise TypeError("formdata should be a multidict-type wrapper that supports the 'getlist' method") return formdata
第2.3.2步:
class Field(object):
#self 是 field对象 def process(self, formdata, data=unset_value): #formdata传值的方式: if formdata: try:
#获取值 if self.name in formdata: self.raw_data = formdata.getlist(self.name) else: self.raw_data = []
#2.3.2.1 self.process_formdata(self.raw_data)
第2.3.2.1步:
class Field(object): def process_formdata(self, valuelist): if valuelist: self.data = valuelist[0] #赋值给self.data
第3步:form.name 是如果在页面上显示出的 input 标签?
print(form.name) #form.name = simple.StringField 类的对象
# 结果:<input class="form-control" id="name" name="name" type="text" value="">
#我们看到打印的结果是 input 标签,其实form.name结果不一定是它,我们去simple.StringField类或其基类中找找__str__方法
class Field(object): def __str__(self): return self() #对象() ,执行__call__方法
class Field(object): def __call__(self, **kwargs): return self.meta.render_field(self, kwargs) #第2.1.1.1.1步可以看出 self.meta 是新创建Meta类对象, self 是simple.StringField类对象
class DefaultMeta(object): def render_field(self, field, render_kw): #获取标签属性,field = simple.StringField类对象 other_kw = getattr(field, 'render_kw', None) if other_kw is not None: render_kw = dict(other_kw, **render_kw) #{'class': 'form-control'} return field.widget(field, **render_kw)
# class StringField(Field):
# widget = widgets.TextInput() #widget 是一个TextInput类的对象
# TextInput()() 执行__call__方法,去TextInput或基类中找
class Input(object): def __call__(self, field, **kwargs): kwargs.setdefault('id', field.id) kwargs.setdefault('type', self.input_type)
#如果render_kw中没有给value定义值 if 'value' not in kwargs:
#3.1步 kwargs['value'] = field._value() #field 是 simple.StringField类对象
#在这里,给input 标签添加属性,这样在页面上显示的标签就有了默认值 return HTMLString('<input %s>' % self.html_params(name=field.name, **kwargs))
第3.1步
class StringField(Field): def _value(self):
#取self.data 中的值,返回 return text_type(self.data) if self.data is not None else ''
第4步:验证
class Form(with_metaclass(FormMeta, BaseForm)): def validate(self): extra = {} for name in self._fields: #{"name":simple.StringField类对象,"pwd":simple.PasswordField类对象} #到 LoginForm类中获取钩子函数 inline = getattr(self.__class__, 'validate_%s' % name, None) if inline is not None: #保存到extra字段中 extra[name] = [inline] #执行Form基类的validate方法 return super(Form, self).validate(extra)
class BaseForm(object): #self是LoginForm对象 def validate(self, extra_validators=None): #extra_validators是所有的钩子 self._errors = None success = True 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() #4.1执行字段的validate方法 if not field.validate(self, extra): success = False return success
第4.1步
class Field(object): def validate(self, form, extra_validators=tuple()): self.errors = list(self.process_errors) #self.errors 是个列表,所以我们在前端页面上只显示一个 stop_validation = False if not stop_validation:
#把字段本身的校验规则和钩子规则放在一起 chain = itertools.chain(self.validators, extra_validators) #4.1.1 执行字段的_run_validation_chain stop_validation = self._run_validation_chain(form, chain) #如果校验未通过 stop_validation = True
return len(self.errors) == 0
第4.1.1步
class Field(object): def _run_validation_chain(self, form, validators): for validator in validators: try: #validator是validators.DataRequired类对象 #4.1.1.1 对象() 调用__call__方法 validator(form, self)
#4.1.1.2 如果校验出异常 except StopValidation as e: if e.args and e.args[0]:
#在该字段的errors列表中添加错误信息 self.errors.append(e.args[0]) return True except ValueError as e: self.errors.append(e.args[0]) return False
第4.1.1.1步:分别执行各个的校验规则类的__call__方法和钩子函数
class DataRequired(object): def __call__(self, form, field): #校验 该字段有数据并是字符串类型 if not field.data or isinstance(field.data, string_types) and not field.data.strip(): if self.message is None: message = field.gettext('This field is required.') else: message = self.message field.errors[:] = [] #不然抛出异常 raise StopValidation(message)
class Length(object): def __call__(self, form, field): l = field.data and len(field.data) or 0 if l < self.min or self.max != -1 and l > self.max: message = self.message if message is None: if self.max == -1: message = field.ngettext('Field must be at least %(min)d character long.', 'Field must be at least %(min)d characters long.', self.min) elif self.min == -1: message = field.ngettext('Field cannot be longer than %(max)d character.', 'Field cannot be longer than %(max)d characters.', self.max) else: message = field.gettext('Field must be between %(min)d and %(max)d characters long.') raise ValidationError(message % dict(min=self.min, max=self.max, length=l))
class Regexp(object): def __call__(self, form, field, message=None): match = self.regex.match(field.data or '') if not match: if message is None: if self.message is None: message = field.gettext('Invalid input.') else: message = self.message raise ValidationError(message) return match
def validate_pwd(self,*args,**kwargs): pass
解释说明:
如果校验成功,4.1.1.1步不抛出异常 → 4.1.1.2步不执行(self.errors没有值)→ 4.1.1步返回False → 4.1步返回True → success=True → 校验成功·
如果校验不成功,每个字段的 .errors里都有错误信息,可以在前端页面上显示出来·
注意:wtforms组件没有clean_data的概念,即使数据校验不成功,打印form.data也会打印出你输入的数据
#print(form.data)
class BaseForm(object):
@property
def data(self):
return dict((name, f.data) for name, f in iteritems(self._fields))
最后我们也可以自己定义一个Form:
from flask import Flask, render_template, request, redirect,Markup app = Flask(__name__, template_folder='templates') import wtforms app.debug = True # 插件 class Widget(object): pass class InputText(Widget): def __call__(self, *args, **kwargs): return "<input type='text' name='name' />" class TextArea(Widget): def __call__(self, *args, **kwargs): return "<textarea name='email'> </textarea>" # Form class BaseForm(object): def __init__(self): # 获取当前字段 _fields = {} for name,field in self.__class__.__dict__.items(): if isinstance(field,Field): _fields[name] = field self._fields = _fields self.data = {} def validate(self,request_data): # 找到所有的字段,执行每个字段的validate方法 flag = True for name,field in self._fields.items(): # 123 input_val = request_data.get(name,'') result = field.validate(input_val) if not result: flag = False else: self.data[name] = input_val return flag # 字段 class Field(object): def __str__(self): return Markup(self.widget()) class StringField(Field): widget = InputText() def validate(self,val): if val: return True class EmailField(Field): widget = TextArea() # EmailField.widget/ self.widget reg = ".*@.*" def validate(self, val): import re if re.match(self.reg,val): return True # ############################ 使用 ########################### class LoginForm(BaseForm): name = StringField() email = EmailField() @app.route('/login', methods=['GET', 'POST']) def login(): form = LoginForm() ret = form.validate(request.form) print("验证成功",ret) print("验证成功值",form.data) # print(obj.name) # print(obj.email) return render_template('login.html',form=form) if __name__ == '__main__': app.run()