flask之源码解读wtforms执行流程_fromwt-程序员宅基地

技术标签: flask-web  flask-form  

首先我们看下wtforms的创建
from wtforms.form import Form
# 引入Form元素父类
from wtforms import StringField
# 引入Form验证父类
from wtforms.validators import DataRequired

class LoginForm(Form):
    name = StringField(
        label='姓名',
        validators=[
            DataRequired('不能为空!')
        ],
        render_kw={
            'placeholder': '请输入姓名!'
        }

    )

from wtforms.form import Form
刚开始程序启动时,首先加载compat.py文件,执行:

def with_metaclass(meta, base=object):
    #返回一个类NewBase并且继承BaseForm,类似于:
    #class NewBase(BaseForm):
    #   pass
    return meta("NewBase", (base,), {})

接着加载了form.py文件,包括BaseForm,FormMeta和Form:

class Form(with_metaclass(FormMeta, BaseForm)):
    pass

然后加载Form时执行了with_metaclass(FormMeta, BaseForm),返回meta(“NewBase”, (base,), {})时执行:

class FormMeta(type):
    def __init__(cls, name, bases, attrs):
        #这里会执行type.__init__(),初始化类NewBase类,
        #现在的cls为NewBase
       type.__init__(cls, name, bases, attrs)
       #给NewBase设置属性:初始值为空
       cls._unbound_fields = None
       cls._wtforms_meta = None

NewBase类初始化完毕后加载Form类,Form类加载到内存后,又会调用class FormMeta(type):中的方法,进行Form类的初始化,然后开始加载LoginForm:

class LoginForm(Form):
    name = StringField(
        label='姓名',
        validators=[
            DataRequired('不能为空!')
        ],
        render_kw={
            'placeholder': '请输入姓名!'
        }

    )   

这时所有需要的类加载完毕,开始发送get请求初始化LoginForm:

@home.route('/')
def index():
    form = LoginForm()
    return render_template('login.html', form=form)

那么这是发生了什么呢?

#这时会执行FormMeta的call方法
class FormMeta(type):
    def __call__(cls, *args, **kwargs):
        if cls._unbound_fields is None:
        #判断如果_unbound_fields为空
              fields = []
              for name in dir(cls):
                  #或者该类的所有的属性
                  if not name.startswith('_'):
                      #遍历所有的名字,如果不是以下滑线开头,获取到给unbound_field赋值:
                      unbound_field = getattr(cls, name)
                      if hasattr(unbound_field, '_formfield'):
                          #筛选真正需要的LoginForm字段
                          fields.append((name, unbound_field))
               #对要渲染的字段进行排序,所以字段前端字段的渲染有顺序
               fields.sort(key=lambda x: (x[1].creation_counter, x[0]))
               #将获取到的fields赋值给_unbound_fields
               cls._unbound_fields = fields
        if cls._wtforms_meta is None:
            bases = []
            for mro_class in cls.__mro__:
              if 'Meta' in mro_class.__dict__:
                  #mro_class是<class 'wtforms.form.Form'>
                  #mro_class.Meta是<class 'wtforms.meta.DefaultMeta'>
                  bases.append(mro_class.Meta)
              #这里创建了Meta类并赋值给cls._wtforms_meta
            cls._wtforms_meta = type('Meta', tuple(bases), {})
        #这里调用__call__时调用了Form的__init__方法
        return type.__call__(cls, *args, **kwargs)
这里调用__call__时调用了Form的__init__方法(为什么?你居然问为什么!!!):
class Form(with_metaclass(FormMeta, BaseForm)):
    def __init__(self, formdata=None, obj=None, prefix='', data=None, meta=None, **kwargs):
        meta_obj = self._wtforms_meta()
        #这里又调用了父类的初始化方法,我们去看下都做了什么?
        super(Form, self).__init__(self._unbound_fields, meta=meta_obj, prefix=prefix)
调用父类的初始化方法,那么我们需要看一下BaseForm中都做了什么:
class BaseForm(object):
    def __init__(self, fields, prefix='', meta=DefaultMeta()):
        self._fields = OrderedDict()
        for name, unbound_field in itertools.chain(fields, extra_fields):
            #循环遍历fields,self._fields中有所有的字段
            options = dict(name=name, prefix=prefix, translations=translations)
            field = meta.bind_field(self, unbound_field, options)
            ##OrderedDict([('name', <wtforms.fields.core.StringField object at 0x0000006A6240A438>)])
            self._fields[name] = field

当上述代码赋值完毕后,就会执行这些个代码:

class Form(with_metaclass(FormMeta, BaseForm)):
    def __init__(self, formdata=None, obj=None, prefix='', data=None, meta=None, **kwargs):
        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)
        #上述代码已经跑完,现在开始从这里执行代码,这里的self._fields有自定义的Form中的所有的字段
        for name, field in iteritems(self._fields):
            setattr(self, name, field)
        #此时执行self.process方法,自己这里没process方法,然后就去基类中执行,这时程序跳到了BaseForm中的process方法
        self.process(formdata, obj, data=data, **kwargs)

基类BaseForm中process方法:

class BaseForm(object):
    def process(self, formdata=None, obj=None, data=None, **kwargs):
        formdata = self.meta.wrap_formdata(self, formdata)
        if data is not None:
            kwargs = dict(data, **kwargs)
        #因为formdata和data都为None所以程序执行到了这里:
        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:
                #最后执行到这里,这是调用process是调用了字段对象StringField的process方法,自己没有这个方法跑到了Field中去:
                field.process(formdata)

Field中的代码:

class Field(object):
    def process(self, formdata, data=unset_value):
        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])

    #由于formdata,self.filters字段为空,下段代码不执行。
      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])

至此所有的流程都已经跑完,页面已经显示控件内容,但是有两个疑问:
1. 没有看到页面处理代码,就是生成html的代码?
2. 那个UnboundField是怎样在代码执行期间起作用的?
3. 数据是如何校验的?

首先解决第一个问题,再加载LoginForm时:

class LoginForm(Form):
    name = StringField(
        label='姓名',
        validators=[
            DataRequired('不能为空!')
        ],
        render_kw={
            'placeholder': '请输入姓名!'
        }

    )

name是StringField类的对象,那么加载StringField时内部都做了什么?

class StringField(Field):
    #这里实例化一个控件,接着我们看下TextInput()里面做了什么?
    widget = widgets.TextInput()

class TextInput(Input):
    #这个类加括号调用了基类的__call__方法,我们看下基类:
    input_type = 'text'

class Input(object):
    #我们看到这里渲染了html标签并返回
    def __call__(self, field, **kwargs):
       kwargs.setdefault('id', field.id)
       kwargs.setdefault('type', self.input_type)
       if 'value' not in kwargs:
           #这里会调用_value()给相关标签添加一个'value='的html属性
           kwargs['value'] = field._value()
        return HTMLString('<input %s>' % self.html_params(name=field.name, **kwargs))
#我们看下基类
class Field(object):
    def __str__(self):
       #返回了一个代表html的对象,加括号,又调用了__call__()
       return self()
    def __call__(self, **kwargs):
        #关键代码,我们接着看下render_field
        return self.meta.render_field(self, kwargs)
#这里来到了meta.py
class DefaultMeta(object):
    def render_field(self, field, render_kw):
       #这里控制render的实现,默认调用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)
       #接着调用StringField的静态字段对控件进行最终的渲染
       return field.widget(field, **render_kw)

那个UnboundField是怎样在代码执行期间起作用的:
首先在实例化LoginForm内的字段StringField时会调用父类的__new__方法:

class Field(object)
    def __new__(cls, *args, **kwargs):
        if '_form' in kwargs and '_name' in kwargs:
            return super(Field, cls).__new__(cls)
       else:
           #返回一个UnboundField对象,对StringField的对象内容进行了封装,实例化UnboundField调用\__init__
            return UnboundField(cls, *args, **kwargs)

#看下UnboundField的执行流程:
class UnboundField(object):
    _formfield = True
    #首先这里的计数,是根据字段的书写顺序进行的加减,为了排序使用
    creation_counter = 0
    #现在的self:<UnboundField(StringField, (), {'label': '姓名', 'validators': [<wtforms.validators.DataRequired object at 0x0000009A20B948D0>], 'render_kw': {'placeholder': '请输入姓名!'}})>
    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

现在页面的初始化工作还没有开始,只是将页面初始化所需要的组件全部加载到内存,内存的初始化工作已经完毕!
接着我们发送个get请求,实例化LoginForm:

@home.route('/')
def index():
    form = LoginForm()
    return render_template('login.html', form=form)

调用了父类的call方法:

class FormMeta(type):
    def __call__(cls, *args, **kwargs):
        if cls._unbound_fields is None:
            fields = []
            for name in dir(cls):
                if not name.startswith('_'):
                    unbound_field = getattr(cls, name)
                    if hasattr(unbound_field, '_formfield'):
                        #fields里有username对应的UnboundField
                        fields.append((name, unbound_field))
            #对field进行了排序,字段的先后顺序决定了页面的显示顺序
            fields.sort(key=lambda x: (x[1].creation_counter, x[0]))
            #现在_unbound_fields 中有所有的字段
            cls._unbound_fields = fields
       if cls._wtforms_meta is None:
           bases = []
           for mro_class in cls.__mro__:
               if 'Meta' in mro_class.__dict__:
                  bases.append(mro_class.Meta)
           #创建了一个Meta类继承<class 'list'>: [<class 'wtforms.meta.DefaultMeta'>]
           cls._wtforms_meta = type('Meta', tuple(bases), {})

       return type.__call__(cls, *args, **kwargs)

最后return type.__call__(cls, *args, **kwargs)时来到了class Form(with_metaclass(FormMeta, BaseForm))的init方法:

class Form(with_metaclass(FormMeta, BaseForm)):
    def __init__(self, formdata=None, obj=None, prefix='', data=None, meta=None, **kwargs):
        #得到一个DefaultMeta的对象
        meta_obj = self._wtforms_meta()
        #这里又执行了父类BaseForm的__init__()方法
        super(Form, self).__init__(self._unbound_fields, meta=meta_obj, prefix=prefix)
    #这句话的意思是给每个字段对象设置一个属性,可以obj.attr获取值
    for name, field in iteritems(self._fields):
        setattr(self, name, field)
    #这里去处理数据,校验数据
    self.process(formdata, obj, data=data, **kwargs)

BaseForm的__init__()方法

class BaseForm(object):
    def __init__(self, fields, prefix='', meta=DefaultMeta()):
        self._fields = OrderedDict()
    #遍历循环fields
    for name, unbound_field in itertools.chain(fields, extra_fields):
       options = dict(name=name, prefix=prefix, translations=translations)
       #这里将所有Fields实例化完毕
       field = meta.bind_field(self, unbound_field, options)
       #将值赋给_fields
       self._fields[name] = field

看下这个:field = meta.bind_field(self, unbound_field, options)

class DefaultMeta(object):
    def bind_field(self, form, unbound_field, options):
        #这里又跑到了UnboundField类中执行bind方法
        return unbound_field.bind(form=form, **options)
class UnboundField(object):
    #看下关键参数,别的就不需要看了:
    #form=<app.home.forms.LoginForm object at 0x000000DE0AEA9EF0>
    #name='username'
    def bind(self, form, name, prefix='', translations=None, **kwargs):
        kw = dict(
            self.kwargs,
            _form=form,
            _prefix=prefix,
            _name=name,
            _translations=translations,
            **kwargs
        )
        #这里去创建StringFiled类并实例化
        return self.field_class(*self.args, **kw)

创建StringFiled类并实例化:

class Field(object):
    def __new__(cls, *args, **kwargs):
        if '_form' in kwargs and '_name' in kwargs:
            #现在kwargs中有值,创建一个类:
            return super(Field, cls).__new__(cls)
        else:
            #这个是刚开始加载到内存时,刚开始创建Field类时会返回UnboundField:
            return UnboundField(cls, *args, **kwargs)
    #创建完后去初始化这个类得到StringFiled对象:
    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):

我们先看下当点击:

这时的代码流程,这时是去创建Form并实例化:
class Form(with_metaclass(FormMeta, BaseForm)):
     def __init__(self, formdata=None, obj=None, prefix='', data=None, meta=None, **kwargs):
         #主要去处理数据,调用了基类的self.process方法,看下面:
         self.process(formdata, obj, data=data, **kwargs)

class BaseForm(object):
    def process(self, formdata=None, obj=None, data=None, **kwargs):
        #这时调用了字段自己的process方法,
         field.process(formdata)

class Field(object):
    def process(self, formdata, data=unset_value):
        #这里的self指Field子类对象
        self.process_data(data)
#这里处理了数据
def process_data(self, value):
    self.data = value
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/qq_33733970/article/details/79027984

智能推荐

汇总阿里云ECS云服务器实例升降配不支持变配的规格列表_云盒实例不支持降配-程序员宅基地

文章浏览阅读269次。阿里云ECS云服务器根据应用场景不同分为多种实例规格,不同ECS实例之间可以通过升降配来变更,目前以下ECS实例规格族是不支持规格族之间以及规格族内部变更的,InstanceTypes分享:阿里云ECS实例不支持变配的规格族列表ECS实例规格族实例规格大数据型d1、d1ne本地SSD型i1、i2、i2gGPU计算型vgn5i、gn5、gn6iGPU图形加速ga1FPGA计算型f1、f3弹性裸金属服务器(神龙)ebmgn6e、ebmgn6v、ebmg._云盒实例不支持降配

MEMORY系列之“SRAM”-程序员宅基地

文章浏览阅读6.5k次,点赞4次,收藏47次。1、SRAM结构SRAM常见的结构有两种:四管二电阻结构和六管结构,分别如下图所示,现在基本都用的六管结构。6T:指的是由六个晶体管组成,如图中的M1、M2、M3、M4、M5、M6。SRAM中的每一bit数据存储在由4个场效应管(M1,M2,M3,M4)构成的两个交叉耦合的反相器中。另外两个场效应管(M5,M6)是存储基本单元到用于读写的位线(Bit Line)的控制开关。具体结构简化示意如下:2、SRAM引脚SRAM地址信号没有线序,..._sram

实用干货 | Linux常用基础命令大全(建议收藏)_chattr +s-程序员宅基地

文章浏览阅读95次。系统目录系统信息关机 (系统的关机、重启以及登出 )文件和目录文件搜索挂载一个文件系统磁盘空间用户和群组文件的权限 - 使用 "+" 设置权限,使用 "-" 用于取消文件的特殊属性 - 使用 "+" 设置权限,使用 "-" 用于取消打包和压缩文件RPM 包 - (Fedora, Redhat及类似系统)YUM 软件包升级器 - (Fedora, RedHat及类似系统)DEB 包 (Debian, Ubuntu 以及类似..._chattr +s

我离职后面试收割小米等大厂offer,附小技巧_微软 小米 offer-程序员宅基地

文章浏览阅读54次。前言众所周知,Xamarin应该是.net下的跨平台开发工具。2016年之前还处于收费状态,后被微软收购后开源。但似乎有个现象,开源后的Xamarin发展似乎有些停滞,而且维护Xamarin的团队又很固执不愿变通。社区多次建议UI层应该统一绘图引擎,而不是映射原生控件。Xamarin.Forms给人的感觉就是性能差,动画僵硬,效果不好实现。以至于谷歌的Flutter火爆之后,Xamarin社区很多人便转移阵地,你在百度搜索Xamarin,第一个关键词就是xamarin还有人用么。那么对于C#/.net_微软 小米 offer

Java体系化进阶学习图谱:java进阶路线图_java进阶图谱-程序员宅基地

文章浏览阅读199次。第一篇Linux基础学习篇目录第零章﹑计算机概论关于电脑的硬件组成部分﹐其实你可以观察你的台式机来分析一下﹐依外观来说这家伙主要可分为三部分﹐分别是∶输入单元∶包括键盘﹑鼠标﹑读卡机﹑扫描仪﹑手写板﹑触摸屏等等一堆﹔主机部分∶这个就是系统单元﹐被主机机箱保护住了﹐里面含有一堆板子﹑CPU与内存等﹔输出单元∶例如屏幕打印机等等第一章、Linux是什么与如何学习我们知道Linux这玩意儿是在计算机上面运行的﹐所以说Linux就是一组软件·问题是这个软件是操作系统还是应用程序?且Linux可_java进阶图谱

python编写程序-30分钟学会用Python编写简单程序-程序员宅基地

文章浏览阅读3.9k次,点赞2次,收藏8次。参与文末每日话题讨论,赠送异步新书异步图书君学习目标知道有序的软件开发过程的步骤。了解遵循输入、处理、输出(IPO)模式的程序,并能够以简单的方式修改它们。了解构成有效Python标识符和表达式的规则。能够理解和编写Python语句,将信息输出到屏幕,为变量赋值,获取通过键盘输入的信息,并执行计数循环。软件开发过程运行已经编写的程序很容易。较难的部分实际上是先得到一个程序。计算机是非常实在的,必须..._用python写程序

随便推点

程序员必学!一招彻底帮你搞定HashMap源码-程序员宅基地

文章浏览阅读48次。一,session共享首先第一个要解决的就是sesison共享的问题,如下图。通常有两种解决方案,第1种是配置nginx的负载集群策略为ip_hash,第2种是将session存储到其它地方,一般推荐放到redis中。第1种方案适合于临时解决或者是为了兼容历史项目,但是从应用服务器无状态的角度考虑,推荐把用户会话session放到redis,如下图。二,本地缓存如果使用本地缓存,当从单体迁移到集群后,就会面临缓存同步的问题,如下图。最佳实践是上分布式缓存,既解决了缓存同步的问题,也释放了应

matlab显示下标索引必须为正整数类型或逻辑类型。怎么改_电场求复共轭的时候显示下标索引必须为正整数类型或逻辑类型。-程序员宅基地

文章浏览阅读2.6k次。哪位大佬能帮忙指点一下啊?_电场求复共轭的时候显示下标索引必须为正整数类型或逻辑类型。

[mysql]约束_不能对自增列使用check约束-程序员宅基地

文章浏览阅读370次。sre_不能对自增列使用check约束

事件循环机制的理解-程序员宅基地

文章浏览阅读45次。点此链接

UltraEdit设置出现异常处理_ultraedit 字符不能以utf8打开-程序员宅基地

文章浏览阅读463次。UltraEdit设置出现异常处理UltraEdit常见问题处理方式UltraEdit常见问题1、文件无法转换格式。个人最常遇到的是突然无法GBK文件无法转换成UTF-8。昨天还能用今天就不能用的那种。最是坑爹2、一些临时起意的设置,用一段时间后发现还是最初配置好用。各种卸载重装,清理注册表都没用处理方式删除C盘下相关配置文件夹C:\Users\用户名\AppData\Roaming\IDMComp\UltraEdit\。即可恢复最初配置..._ultraedit 字符不能以utf8打开

java完全自学手册pdf,附答案+考点_java练习手册pdf-程序员宅基地

文章浏览阅读151次。01 阿里面试题之MySQL之前的阿里面试题都有做总结,具体面试题内容整理成了文档,本文是针对MySQL系列的,所以下面只展示了自己第一次面试阿里时被吊打问到的一些MySQL难题请解释关系型数据库概念及主要特点?请说出关系型数据库的典型产品、特点及应用场景?请详细描述 SQL 语句分类及对应代表性关键字。什么是 MySQL 多实例,如何配置 MySQL 多实例?如何加强 MySQL 安全,请给出可行的具体措施?误操作执行了一个 drop 库 SQL 语句,如何完整恢复?详述 MySQL 主_java练习手册pdf

推荐文章

热门文章

相关标签