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()
flask自定义Form

 

posted @ 2018-01-22 20:28  Cool·  阅读(1011)  评论(0编辑  收藏  举报