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

Posted East-L

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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)(?=.*[[email protected]$!%*?&])[A-Za-z\[email protected]$!%*?&]{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【预留的扩展】

 

以上是关于wtforms Form实例化流程(源码解析)的主要内容,如果未能解决你的问题,请参考以下文章

Flask-WTForms

自定义form组件

Flask系列自定义Form组件

Flask 第十篇自定义Form组件

Flask学习第10篇:自定义Form组件

Flask之wtforms源码分析