• wtforms Form实例化流程(源码解析)


    class LoginForm(Form):
        #首先执行后得到的结果是UnboundField()对象
        name=simple.StringField(
            label='用户名',
            validators=[
                validators.DataRequired(message='用户名不能为空'),
            ],
            widget=widgets.TextInput(),
            render_kw={'class': 'form-control'}
        )
    
        pwd=simple.StringField(
            label='密码',
            validators=[
                validators.DataRequired(message='密码不能为空'),
            ],
            widget=widgets.TextInput(),
            render_kw={'class': 'form-control'}
        )
    
    
    @user.route('/login',methods=['GET','POST'])
    def login():
        if request.method=='GET':
            form=LoginForm()
            print(form)
            return render_template('login.html',form=form)
        else:
            form=LoginForm(request.form)
            if form.validate():

    1.执行Field中的__new__方法

    我们还没执行到form=LoginForm()时,LoginForm里面所有的字段都已经执行加载完了,里面的字段的值都是Field实例化而来,而实例化一个类,先执行该类的__new__方法来创建这个类,然后调用__init__()方法来实例化

    ,本类中没有就去父类中找,

    Field中的__new__()方法

     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)

    可以知道开始的时候所有的Field对象都是UnboundField()对象,我们所写的Filed实例实际开始是这样的(注释)

    class LoginForm(Form):
        # name = UnboundField(StringField, *args, **kwargs) creation_counter=1
        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 = UnboundField(PasswordField, *args, **kwargs) creation_counter=2
        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'}
        )

    2.执行FormMeta的__call__()方法,读取字段到静态字段 cls._unbound_fields 中; meta类读取到cls._wtforms_meta中

    如果一个类有metaclass,那么该类创建的时候会执行她的metaclass类中的__init__()方法,实例话这个类回执行 metaclass中的__call__()方法:

    class Form(with_metaclass(FormMeta, BaseForm)):

    FormMeta的__call__()方法

     def __call__(cls, *args, **kwargs):
            """
            Construct a new `Form` instance.
    
            Creates the `_unbound_fields` list and the internal `_wtforms_meta`
            subclass of the class Meta in order to allow a proper inheritance
            hierarchy.
            """
            if cls._unbound_fields is None:
                fields = []
                #当前类所有的属性
                for name in dir(cls):
                    if not name.startswith('_'):
                        #得到UnboundField()对象
                        unbound_field = getattr(cls, name)
    #UnboundField()对象默认_formfield为True
    if hasattr(unbound_field, '_formfield'): fields.append((name, unbound_field)) # We keep the name as the second element of the sort # to ensure a stable sort. #根据UnboundField()对象的.creation_counter进行排序 fields.sort(key=lambda x: (x[1].creation_counter, x[0])) #fields=[('name',UnboundField()),('pwd',UnboundField())] cls._unbound_fields = fields # Create a subclass of the 'class Meta' using all the ancestors. if cls._wtforms_meta is None: bases = [] #__mro__代表该类的继承关系 for mro_class in cls.__mro__: if 'Meta' in mro_class.__dict__: bases.append(mro_class.Meta) cls._wtforms_meta = type('Meta', tuple(bases), {}) return type.__call__(cls, *args, **kwargs)

    3.执行Form类的构造方法:

        def __init__(self, formdata=None, obj=None, prefix='', data=None, meta=None, **kwargs):
            """
            :param formdata:
                Used to pass data coming from the enduser, usually `request.POST` or
                equivalent. formdata should be some sort of request-data wrapper which
                can get multiple parameters from the form input, and values are unicode
                strings, e.g. a Werkzeug/Django/WebOb MultiDict
            :param obj:
                If `formdata` is empty or not provided, this object is checked for
                attributes matching form field names, which will be used for field
                values.
            :param prefix:
                If provided, all fields will have their name prefixed with the
                value.
            :param data:
                Accept a dictionary of data. This is only used if `formdata` and
                `obj` are not present.
            :param meta:
                If provided, this is a dictionary of values to override attributes
                on this form's meta instance.
            :param `**kwargs`:
                If `formdata` is empty or not provided and `obj` does not contain
                an attribute named the same as a field, form will assign the value
                of a matching keyword argument to the field, if one exists.
            """
            meta_obj = self._wtforms_meta()
            if meta is not None and isinstance(meta, dict):
                meta_obj.update_values(meta)
            super(Form, self).__init__(self._unbound_fields, meta=meta_obj, prefix=prefix)
    
            for name, field in iteritems(self._fields):
                # Set all the fields to attributes so that they obscure the class
                # attributes with the same names.
                setattr(self, name, field)
            self.process(formdata, obj, data=data, **kwargs)
     super(Form, self).__init__(self._unbound_fields, meta=meta_obj, prefix=prefix)

    a.先执行 baseForm中的__init__()

     def __init__(self, fields, prefix='', meta=DefaultMeta()):
            """
            :param fields:
                A dict or sequence of 2-tuples of partially-constructed fields.
            :param prefix:
                If provided, all fields will have their name prefixed with the
                value.
            :param meta:
                A meta instance which is used for configuration and customization
                of WTForms behaviors.
            """
            if prefix and prefix[-1] not in '-_;:/.':
                prefix += '-'
    
            self.meta = meta
            self._prefix = prefix
            self._errors = None
            self._fields = OrderedDict()
    
            if hasattr(fields, 'items'):
                fields = fields.items()
    
            translations = self._get_translations()
            extra_fields = []
            if 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):
                options = dict(name=name, prefix=prefix, translations=translations)
                field = meta.bind_field(self, unbound_field, options)
                self._fields[name] = field
      #将fields和extra_fields链接起来
            for name, unbound_field in itertools.chain(fields, extra_fields):
                options = dict(name=name, prefix=prefix, translations=translations)
                field = meta.bind_field(self, unbound_field, options)
                self._fields[name] = field

    b.执行UnboundField中的bind()方法:

    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.args = args
            self.kwargs = kwargs
            self.creation_counter = UnboundField.creation_counter
    
        def bind(self, form, name, prefix='', translations=None, **kwargs):
            kw = dict(
                self.kwargs,
                _form=form,
                _prefix=prefix,
                _name=name,
                _translations=translations,
                **kwargs
            )
            return self.field_class(*self.args, **kw)
    
        def __repr__(self):
            return '<UnboundField(%s, %r, %r)>' % (self.field_class.__name__, self.args, self.kwargs)

    在bind方法中我们可以看到,由于字段中的__new__方法,实例化时:name = simple.StringField(label='用户名'),创建的是UnboundField(cls, *args, **kwargs),当执行完bind之后,就变成执行 wtforms.fields.core.StringField(),

    c.再回到BaseForm中的__init__()中,将返回值添加到 self._fields[name] 中,既:

     _fields = {
                        name: wtforms.fields.core.StringField(),
                    }

    d.执行玩BaseForm的__init__()后,在继续回到Form的构造方法中循环_fields,为对象设置属性

     for name, field in iteritems(self._fields):
                # Set all the fields to attributes so that they obscure the class
                # attributes with the same names.
                setattr(self, name, field)
            

    e.执行process,为字段设置默认值:self.process(formdata, obj, data=data, **kwargs),再循环执行每个字段的process方法,为每个字段设置值:

    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:
                        field.process(formdata)

    f.执行每个字段的process方法,为字段的data和字段的raw_data赋值

    Field的process

        def process(self, formdata, data=unset_value):
            """
            Process incoming data, calling process_data, process_formdata as needed,
            and run filters.
    
            If `data` is not provided, process_data will be called on the field's
            default.
    
            Field subclasses usually won't override this, instead overriding the
            process_formdata and process_data methods. Only override this for
            special advanced processing, such as when a field encapsulates many
            inputs.
            """
            self.process_errors = []
            if data is unset_value:
                try:
                    data = self.default()
                except TypeError:
                    data = self.default
    
            self.object_data = data
    
            try:
                self.process_data(data)
            except ValueError as e:
                self.process_errors.append(e.args[0])
    
            if formdata:
                try:
                    if self.name in formdata:
                        self.raw_data = formdata.getlist(self.name)
                    else:
                        self.raw_data = []
                    self.process_formdata(self.raw_data)
                except ValueError as e:
                    self.process_errors.append(e.args[0])
    
            try:
                for filter in self.filters:
                    self.data = filter(self.data)
            except ValueError as e:
                self.process_errors.append(e.args[0])

    3. 页面上执行print(form.name) 时,打印标签,流程如下(参考自定义form组件很容易理解)

    我们在前端和后端上打印的name和pwd其实是一个Filed的实例,相当于一个实例对象,我们知道直接print一个对象的时候,会调用该类的__str__方法,所以我们查看Field的__str__方法:

       def __str__(self):
            """
            Returns a HTML representation of the field. For more powerful rendering,
            see the `__call__` method.
            """
            return self()

    我们可以看到他返回self(),对象()---->执行当前类的__call__()方法:

     def __call__(self, **kwargs):
            """
            Render this field as HTML, using keyword args as additional attributes.
    
            This delegates rendering to
            :meth:`meta.render_field <wtforms.meta.DefaultMeta.render_field>`
            whose default behavior is to call the field's widget, passing any
            keyword arguments from this call along to the widget.
    
            In all of the WTForms HTML widgets, keyword arguments are turned to
            HTML attributes, though in theory a widget is free to do anything it
            wants with the supplied keyword arguments, and widgets don't have to
            even do anything related to HTML.
            """
            return self.meta.render_field(self, kwargs)

    最终返回值是meta.render_field(self, kwargs)执行后的结果

        def render_field(self, field, render_kw):
            """
            render_field allows customization of how widget rendering is done.
    
            The default implementation calls ``field.widget(field, **render_kw)``
            """
            other_kw = getattr(field, 'render_kw', None)
            if other_kw is not None:
                render_kw = dict(other_kw, **render_kw)
            return field.widget(field, **render_kw)

    调用插件返回对应的Html页面代码

     4.验证流程

    a. 执行form的validate方法,获取钩子方法
                def validate(self):
                    extra = {}
                    for name in self._fields:
                        inline = getattr(self.__class__, 'validate_%s' % name, None)
                        if inline is not None:
                            extra[name] = [inline]
            
                    return super(Form, self).validate(extra)
            b. 循环每一个字段,执行字段的 validate 方法进行校验(参数传递了钩子函数)
                def validate(self, extra_validators=None):
                    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()
                        if not field.validate(self, extra):
                            success = False
                    return success
            c. 每个字段进行验证时候
                字段的pre_validate 【预留的扩展】
                字段的_run_validation_chain,对正则和字段的钩子函数进行校验
                字段的post_validate【预留的扩展】
  • 相关阅读:
    UTF8转换为GB编码gb2312转换为utf-8
    localtime 和 localtime_r
    无损音频介绍
    bgr to rgb
    Farseer.Net
    为大家分享一个 Ajax Loading —— spin.js(转)
    HTML5-Ajax文件上传(转)
    Firefox火狐Flash插件卡死问题完美解决方法(转载)
    50 个最棒的 jQuery 日历插件,很齐全了!(转)
    linq to xml学习
  • 原文地址:https://www.cnblogs.com/ctztake/p/8260487.html
Copyright © 2020-2023  润新知