在最开始要弄明白一点,类都是由元类创建的。在定义类 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. Set和WeakSet数据结构

    学习Set数据结构,注意这里不是数据类型,而是数据结构.它是ES6中新的东西,并且很有用处.Set的数据结构是以数组的形式构建的. Set的声明 let setArr = new Set(['js', ...

  2. NLog——ElasticSearch——Kibana

    Nlog.elasticsearch.Kibana以及logstash在项目中的应用(一) Nlog.elasticsearch.Kibana以及logstash在项目中的应用(二) ASP.NET ...

  3. Kubernetes工作流之Pods一

    This page provides an overview of Pod, the smallest deployable object in the Kubernetes object model ...

  4. vim命令详解

    VIM编辑常用技巧 vim编辑器 简介: vi: Visual Interface,文本编辑器 文本:ASCII, Unicode 文本编辑种类: 行编辑器: sed 全屏编辑器:nano, vi V ...

  5. 常用markdown语法入门

    入门markdown常用基本语法,简单到让你怀疑人生~~ 不说废话,直接上图(如果图片显示不清晰,建议选中图片右键——在新标签页中打开图片,妥妥的呢!!) (左侧黑色背景为markdown语法,右侧为 ...

  6. urllib模块中的方法

    urllib模块中的方法 1.urllib.urlopen(url[,data[,proxies]]) 打开一个url的方法,返回一个文件对象,然后可以进行类似文件对象的操作.本例试着打开google ...

  7. SQLServer代理新建或者编辑作业报错

    SQLServer代理新建或者编辑作业的时候报错如下 错误信息: 标题: Microsoft SQL Server Management Studio------------------------- ...

  8. 关于C++中的friend友元函数的总结

    1.友元函数的简单介绍 1.1为什么要使用友元函数 在实现类之间数据共享时,减少系统开销,提高效率.如果类A中的函数要访问类B中的成员(例如:智能指针类的实现),那么类A中该函数要是类B的友元函数. ...

  9. JAVA之经典算法

    package Set.Java.algorithm; import java.util.Scanner; public class algorithm { /** * [程序1] 题目:古典问题:有 ...

  10. codeforces 15C. Industrial Nim

    题目链接:http://codeforces.com/problemset/problem/15/C $NIM$游戏是次要的,直接异或石头堆就可以了,问题在于给出的石头堆的数量极多. 考虑利用异或的性 ...