在最开始要弄明白一点,类都是由元类创建的。在定义类 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():
#
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

python-flask-wtforms组件流程源码的更多相关文章

  1. Flask框架整个流程源码解读

    Flask框架整个流程源码解读 一.总的流程 运行Flask其本质是运行Flask对象中的__call__,而__call__本质调用wsgi_app的方法 wsgi_app方法 def wsgi_a ...

  2. Spark(四十九):Spark On YARN启动流程源码分析(一)

    引导: 该篇章主要讲解执行spark-submit.sh提交到将任务提交给Yarn阶段代码分析. spark-submit的入口函数 一般提交一个spark作业的方式采用spark-submit来提交 ...

  3. [Android]Android系统启动流程源码分析

    以下内容为原创,欢迎转载,转载请注明 来自天天博客:http://www.cnblogs.com/tiantianbyconan/p/5013863.html Android系统启动流程源码分析 首先 ...

  4. Spring加载流程源码分析03【refresh】

      前面两篇文章分析了super(this)和setConfigLocations(configLocations)的源代码,本文来分析下refresh的源码, Spring加载流程源码分析01[su ...

  5. Android Activity启动流程源码全解析(1)

    前言 Activity是Android四大组件的老大,我们对它的生命周期方法调用顺序都烂熟于心了,可是这些生命周期方法到底是怎么调用的呢?在启动它的时候会用到startActivty这个方法,但是这个 ...

  6. Android Activity启动流程源码全解析(2)

    接上之前的分析 ++Android Activity启动流程源码全解析(1)++ 1.正在运行的Activity调用startPausingLocked 一个一个分析,先来看看startPausing ...

  7. Spring IOC容器启动流程源码解析(四)——初始化单实例bean阶段

    目录 1. 引言 2. 初始化bean的入口 3 尝试从当前容器及其父容器的缓存中获取bean 3.1 获取真正的beanName 3.2 尝试从当前容器的缓存中获取bean 3.3 从父容器中查找b ...

  8. Spark(五十一):Spark On YARN(Yarn-Cluster模式)启动流程源码分析(二)

    上篇<Spark(四十九):Spark On YARN启动流程源码分析(一)>我们讲到启动SparkContext初始化,ApplicationMaster启动资源中,讲解的内容明显不完整 ...

  9. 【图解源码】Zookeeper3.7源码分析,包含服务启动流程源码、网络通信源码、RequestProcessor处理请求源码

    Zookeeper3.7源码剖析 能力目标 能基于Maven导入最新版Zookeeper源码 能说出Zookeeper单机启动流程 理解Zookeeper默认通信中4个线程的作用 掌握Zookeepe ...

随机推荐

  1. SQLite EF Core Database Provider

    原文链接 This database provider allows Entity Framework Core to be used with SQLite. The provider is mai ...

  2. ComponentOne使用技巧——从Winform穿越到WPF

    概述 WPF 和 Winform 是两个单独的平台,但二者又都是基于 .NET 4.0 以上版本开发的,所以很多.NET开发人员就开始研究如何在WPF中使用Winform.微软已经架设了两个开发平台的 ...

  3. 【译】第42节---EF6-DbSet.AddRange & DbSet.RemoveRange

    原文:http://www.entityframeworktutorial.net/entityframework6/addrange-removerange.aspx EF 6中的DbSet引入了新 ...

  4. Android 使alertDialog.builder不会点击外面和按返回键消失

    这个问题之前一直困扰我,我的需求就是点击对话框外面和按返回键对话框不会消失,按返回键还好解决,拦截下返回键就OK了. 但是点击外面不好解决.之前有人说模态对话框,我看了一会,觉得不是我想要的效果.po ...

  5. File类文件的常见操作

    boolean exists() 判断文件或者目录是否存在 boolean isFile()  判断是否是文件 boolean isDirectory() 判断是否是目录 String getPath ...

  6. Centos7安装JDK8以及环境配置

    下载,选择centos7 64位版本 https://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.h ...

  7. 浅谈Linux文件系统

    Linux 与其他类 UNIX 系统一样并不区分文件与目录:目录是记录了其他文件名的文件.使用命令 mkdir 创建目录时,若期望创建的目录的名称与现有的文件名(或目录名)重复,则会创建失败. Lin ...

  8. 有了art-template,如有神助

    <div class="form-group col-lg-12"> <label class="control-label col-lg-3 text ...

  9. SpringBoot和Mybatis的整合

    这里介绍两种整合SpringBoot和Mybatis的模式,分别是“全注解版” 和 “注解xml合并版”. 前期准备开发环境 开发工具:IDEAJDK:1.8技术:SpringBoot.Maven.M ...

  10. L1-048. 矩阵A乘以B

    水题不多说,直接上代码:#include<stdio.h> using namespace std; int main() { ][]; ][]; int m,n; int x,y; sc ...