python-flask-wtforms组件流程源码
在最开始要弄明白一点,类都是由元类创建的。在定义类 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组件流程源码的更多相关文章
- Flask框架整个流程源码解读
Flask框架整个流程源码解读 一.总的流程 运行Flask其本质是运行Flask对象中的__call__,而__call__本质调用wsgi_app的方法 wsgi_app方法 def wsgi_a ...
- Spark(四十九):Spark On YARN启动流程源码分析(一)
引导: 该篇章主要讲解执行spark-submit.sh提交到将任务提交给Yarn阶段代码分析. spark-submit的入口函数 一般提交一个spark作业的方式采用spark-submit来提交 ...
- [Android]Android系统启动流程源码分析
以下内容为原创,欢迎转载,转载请注明 来自天天博客:http://www.cnblogs.com/tiantianbyconan/p/5013863.html Android系统启动流程源码分析 首先 ...
- Spring加载流程源码分析03【refresh】
前面两篇文章分析了super(this)和setConfigLocations(configLocations)的源代码,本文来分析下refresh的源码, Spring加载流程源码分析01[su ...
- Android Activity启动流程源码全解析(1)
前言 Activity是Android四大组件的老大,我们对它的生命周期方法调用顺序都烂熟于心了,可是这些生命周期方法到底是怎么调用的呢?在启动它的时候会用到startActivty这个方法,但是这个 ...
- Android Activity启动流程源码全解析(2)
接上之前的分析 ++Android Activity启动流程源码全解析(1)++ 1.正在运行的Activity调用startPausingLocked 一个一个分析,先来看看startPausing ...
- Spring IOC容器启动流程源码解析(四)——初始化单实例bean阶段
目录 1. 引言 2. 初始化bean的入口 3 尝试从当前容器及其父容器的缓存中获取bean 3.1 获取真正的beanName 3.2 尝试从当前容器的缓存中获取bean 3.3 从父容器中查找b ...
- Spark(五十一):Spark On YARN(Yarn-Cluster模式)启动流程源码分析(二)
上篇<Spark(四十九):Spark On YARN启动流程源码分析(一)>我们讲到启动SparkContext初始化,ApplicationMaster启动资源中,讲解的内容明显不完整 ...
- 【图解源码】Zookeeper3.7源码分析,包含服务启动流程源码、网络通信源码、RequestProcessor处理请求源码
Zookeeper3.7源码剖析 能力目标 能基于Maven导入最新版Zookeeper源码 能说出Zookeeper单机启动流程 理解Zookeeper默认通信中4个线程的作用 掌握Zookeepe ...
随机推荐
- (转)How Hash Algorithms Work
本文转自:http://www.metamorphosite.com/one-way-hash-encryption-sha1-data-software Home Posted: Novembe ...
- 取消开机logo,改成代码刷屏
将开机logo改成开始时代码刷屏,这样就能很方便看到开始时的一些问题 首先 sudo chmod 666 /etc/default/grub 然后将 GRUB_CMDLINE_LINUX_DEFAUL ...
- 在Vue的构造器里我们写一个add方法,然后我们用实例的方法调用它
html <div id="app"> <div>{{message}}</div> </div> js var vm = new ...
- 剥开比原看代码13:比原是如何通过/list-balances显示帐户余额的?
作者:freewind 比原项目仓库: Github地址:https://github.com/Bytom/bytom Gitee地址:https://gitee.com/BytomBlockchai ...
- 火狐对 min-height 的支持
代码: <!DOCTYPE html> <style> .com-center-banner { background: #f00; } .com-center-banner ...
- el-cascader 级联选择器使用时遇到的一些问题
Element UI Cascader官网文档 <el-form-item label="章节" style="margin-right: 64px"&g ...
- 【Django】Django-REST-Framework
[创建简单的API] 1. cmd.exe >django-admin startproject django_rest>cd django_rest\django_rest>pyt ...
- SSH KEY 设置 目录在open ~ 根目录下的.ssh 里面
当我们从github或者gitlab上clone项目或者参与项目时,需要证明我们的身份.github.gitlab支持使用SSH协议进行免密登录,而SSH协议采用了RSA算法保证了登录的安全性.我们要 ...
- ppython的移位操作
因为要将js的一个签名算法移植到python上,遇到一些麻烦. int无限宽度,不会溢出 算法中需要用到了32位int的溢出来参与运算,但是python的int是不会溢出的,达到界限后会自己转为lon ...
- JAVA基础知识总结:十九
一.多线程使用过程中的临界资源问题 1.临界资源:被多个线程同时访问的资源 临界资源产生的原因:有多个线程同时访问一个资源的时候,如果一个线程在取值的过程中,时间片又被其他的线程抢走了,临界资源问题就 ...