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实例化而来

查看上述 LoginForm 中的 name 字段,可以看到它的值是通过 StringField 类实例化返回的对象。查看 StringField 类,会发现它的整个继承体系中都没有指定 metaclass ,所以实例化时真正返回的对象是由它的 __new__ ,它的 __new__ 方法继承自 wtforms.fields.core.Field :

 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)(?=.*[$@$!%*?&])[A-Za-z\d$@$!%*?&]{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中

如果一个A类有metaclass,那么该A类创建的时候会执行她的metaclass类中的__init__()方法,实例话这个A类回执行 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)#baseForm中的__init__() 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)#在继续回到Form的构造方法中循环_fields,为对象设置属性
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)#从中执行UnboundField中的bind()方法:
self._fields[name] = field #将上一行代码返回值添加到 self._fields[name] 中
  #将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])

 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【预留的扩展】

flask 源码专题(四):wtforms Form实例化流程以及csrf验证的更多相关文章

  1. flask 源码专题(五):SqlAlchemy 中操作数据库时session和scoped_session的区别

    1原生session: from sqlalchemy.orm import sessionmaker from sqlalchemy import create_engine from sqlalc ...

  2. flask 源码专题(二):请求上下文与全文上下文

    源码解析 0. 请求入口 if __name__ == '__main__': app.run() def run(self, host=None, port=None, debug=None, lo ...

  3. 07 flask源码剖析之用户请求过来流程

    07 Flask源码之:用户请求过来流程 目录 07 Flask源码之:用户请求过来流程 1.创建ctx = RequestContext对象 2. 创建app_ctx = AppContext对象 ...

  4. flask 源码专题(九):flask扩展点

    1. 信号(源码) 信号,是在flask框架中为我们预留的钩子,让我们可以进行一些自定义操作. pip3 install blinker 2. 根据flask项目的请求流程来进行设置扩展点 中间件 # ...

  5. flask 源码专题(十一):LocalStack和Local对象实现栈的管理

    目录 04 LocalStack和Local对象实现栈的管理 1.源码入口 1. flask源码关于local的实现 2. flask源码关于localstack的实现 3. 总结 04 LocalS ...

  6. flask 源码专题(一):app.run()的背后

    当我们用Flask写好一个app后, 运行app.run()表示监听指定的端口, 对收到的request运行app生成response并返回. 现在分析一下, 运行app.run()后具体发生了什么事 ...

  7. Python Web Flask源码解读(四)——全局变量

    关于我 一个有思想的程序猿,终身学习实践者,目前在一个创业团队任team lead,技术栈涉及Android.Python.Java和Go,这个也是我们团队的主要技术栈. Github:https:/ ...

  8. flask 源码专题(八):路由加载

    1.示例代码 from flask import Flask app = Flask(__name__,static_url_path='/xx') @app.route('/index') def ...

  9. flask 源码专题(六):session处理机制

    前言 flask_session是flask框架实现session功能的一个插件,用来替代flask自带的session实现机制,flask默认的session信息保存在cookie中,不够安全和灵活 ...

随机推荐

  1. 04 . Docker安全与Docker底层实现

    Docker安全 Docker安全性时,主要考虑三个方面 # 1. 由内核的名字空间和控制组机制提供的容器内在安全 # 2. Docker程序(特别是服务端)本身的抗攻击性 # 3. 内核安全性的加强 ...

  2. Redis底层结构全了解

    第一篇文章,思来想去,写一写Redis吧,最近在深入研究它. 一丶Redis底层结构 1. redis 存储结构 redis的存储结构从外层往内层依次是redisDb.dict.dictht.dict ...

  3. linux使用组ID(SGID)共享文件

    假如你有这样一个需求,一个小组内很多成员共同研究一个项目,为了这个项目我们需要分配一个具体的目录. 所有成员都拥有该目录的使用权限,可以互相操作成员的文件及内容.而且不允许其他人查看. 现在开始操作: ...

  4. Spark GraphX从入门到实战

      第1章 Spark GraphX 概述 1.1 什么是 Spark GraphX   Spark GraphX 是一个分布式图处理框架,它是基于 Spark 平台提供对图计算和图挖掘简洁易用的而丰 ...

  5. Mysq数据库索引(B-Tree索引)

    一.B-Tree索引的底层结构 所有的值都是按顺序存储的,并且每一个叶子页到根的距离相同,如图所示,B-Tree索引的底层数据结构一般是B+树,反应了MyISAM索引是如何工作的.     二.B-T ...

  6. cocos2dx Mac平台 打印长字符串,游戏卡死

    1,打开了输出控制台,输出卡死的解决方案: 打开控制台: game -console enable 关闭控制台: game -console false 修改文件 ConsoleWindowContr ...

  7. ca72a_c++_标准IO库:面向对象的标准库

    /*ca72a_c++_标准IO库:面向对象的标准库继承:基类->派生类3个头文件9个标准库类型IO对象不可复制或赋值 ofstream, f--file,文件输出流ostringstream, ...

  8. Mybatis框架介绍

    MyBatis 本是apache的一个开源项目iBatis, 2010年这个项目由apache software foundation 迁移到了google code,并且改名为MyBatis.201 ...

  9. 安卓开发-Activity-多个Activity的开发方法。

    原文链接:https://blog.csdn.net/weixin_38420342/article/details/84344496 一.切换Activity的5种方式 Intent intent ...

  10. Spring事务深入剖析--spring事务失效的原因

    之前我们讲的分布式事务的调用都是在一个service中的事务方法,去调用另外一个service中的业务方法, 如果在一个sevice中存在两个分布式事务方法,在一个seivice中两个事务方法相互嵌套 ...