wtforms

使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
# -*- coding:utf-8 -*-
from flask import Flask, render_template, flash, request, views, jsonify
from flask_wtf import FlaskForm
from wtforms import Form
from wtforms import StringField, PasswordField, SubmitField, widgets
from wtforms.validators import DataRequired, EqualTo, Length, Regexp
from werkzeug.middleware.dispatcher import DispatcherMiddleware
from werkzeug.serving import run_simple
import pymysql
pymysql.install_as_MySQLdb()
from flask_sqlalchemy import SQLAlchemy

# 需要一个导入名,作用为了确定资源所在路径
app = Flask(__name__)
app.secret_key = 'item'

app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql://root:123456@127.0.0.1/flasksql'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app)


class Role(db.Model):
__tablename__ = 'roles'
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
name = db.Column(db.String(16), unique=True)
users = db.relationship('User')

def __repr__(self):
return "<Role:%s %s>" % (self.name, self.id)


class User(db.Model):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
name = db.Column(db.String(16))
email = db.Column(db.String(32))
password = db.Column(db.String(32))
role_id = db.Column(db.Integer, db.ForeignKey('roles.id'))

def __repr__(self):
return "<User: %s %s %s %s>" % (self.name, self.id, self.email, self.password)


# db.create_all()


class LoginForm(FlaskForm):
username = StringField(u"用户名",
# 自定义验证规则
validators=[
DataRequired(message='用户名不能为空'),
Length(min=6, max=18, message='用户名必须大于%(min)d且小于%(max)d')
],
# 制定页面显示插件
widget=widgets.TextInput(),
# 给插件生成属性
render_kw={'class': 'form-control'}
)

# 定制在页面上输入的文本框

pwd1 = PasswordField(u"密码", validators=[DataRequired(), EqualTo('pwd2', '密码填入不一致')])
pwd2 = PasswordField(u"确认密码", validators=[DataRequired(),
EqualTo('pwd1', '密码填入不一致'),
Regexp(regex="^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[\s\S]{8,16}$",
message="密码至少8个字符,需要至于1个大写字母,1个小写字母,"
"1个数字和一个特殊符号")
])
submit = SubmitField(u'提交')


@app.route('/form')
def login():
login_form = LoginForm()
if request.method == 'POST':
username = request.form.get('username')
pwd1 = request.form.get('pwd1')
pwd2 = request.form.get('pwd2')

form = LoginForm(formdata=request.form)
if form.validate():
print('通过验证')
else:
print(form.errors)
if login_form.validate_on_submit():
print(123)
return b"success"
else:
message1 = login_form.pwd2.errors
message2 = login_form.pwd1.errors
print(message1, message2)
flash(message1[0])
return render_template('index.html', login_form=login_form)


if __name__ == "__main__":
app.run()

form的作用

# 生成HTML

1
2
3
4
5
6
7
8
9
10
11
import re
class InputText:

def __str__(self):
return '<input type="text" />'


class InputEmail:

def __str__(self):
return '<input type="text" />'

校验格式

1
2
3
4
5
6
7
class StringField:
def __init__(self, wg, reg):
self.widget = wg
self.reg = reg

def __str__(self):
return self.widget

管理字段分发

1
2
3
4
5
6
7
8
9
10
11
12
13
class LoginForm:
xyy = StringField(wg=InputTest(), reg=r'\d+')
lw = StringField(wg=InputEmail(), reg=r'\w+')

def __str__(self):
self.form = {"xyy": 123213, "lw":' fasfas12'}

def validate(self):
fields = {"xyy": self.xyy, "lw": self.lw}
for name, field in fields.items():
if not re.match(field.reg, self.form.get(name)):
return False
return True

源码流程

form的创建流程

类是由ForMata创建的,执行ForMata的__init__方法。

1
2
3
4
5
class Form(with_metaclass(FormMeta, BaseForm)):
Meta = DefaultMeta

def with_metaclass(meta, base=object):
return meta("NewBase", (base,), {})

执行Formata的__init__方法,loginForm类创建

1
2
3
4
5
6
(这时相当于执行loginForm. _unbound_fields = None和loginForm._wtforms_meta = None)
class FormMeta(type):
def __init__(cls, name, bases, attrs):
type.__init__(cls, name, bases, attrs)
cls._unbound_fields = None
cls._wtforms_meta = None

loginForm创建后,执行其中的Field字段

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
class Field(object):
errors = tuple()
process_errors = tuple()
raw_data = None
validators = tuple()
widget = None
_formfield = True
_translations = DummyTranslations()
do_not_call_in_templates = True # Allow Django 1.4 traversal

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)

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和_name会生成unboundField
这时loginform中:
{
‘__module’: ‘__main__’,
‘name’:<UnboundField>(StringField…..),
‘pwd’: <UnboundField>(PasswordField…..),
‘__doc__’: None,
‘unbound_fields’: None,
‘_wtforms_meta’: None
}
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
validators = kwargs.get('validators')
if validators:
self.field_class.check_validators(validators)

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)
这里unboundfield用计数器给每个实例化类计数,保持python3.6以下的顺序执行。
class Foo:
creator = 0

def __init__(self):
Foo.creator += 1


f = Foo()
print(f.creator)
g = Foo()
print(g.creator)

form()实例化,一个类实例化要哪几步?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
@app.route('/form')
def login():
login_form = LoginForm()
1.先看自己有没有__call__然后一直找到寻源类,metaclass,调用metaclass的__call__方法(这里是FormMeta的__call__)
def __call__(cls, *args, **kwargs):
if cls._unbound_fields is None:
fields = []
for name in dir(cls):
if not name.startswith('_'):
# 获取unboundfield(1,StringField, 参数)
unbound_field = getattr(cls, name)
if hasattr(unbound_field, '_formfield'):
fields.append((name, unbound_field))
# 按照定义顺序排序
fields.sort(key=lambda x: (x[1].creation_counter, x[0]))
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)
cls._wtforms_meta = type('Meta', tuple(bases), {})
return type.__call__(cls, *args, **kwargs)
1.获取定义的Field按照计数器排序绑定进unbound_field
2.调用mro继承算法,继承顺序,按照mro顺序找寻每个父类对象的Meta加入bases,(拿到所有的功能),然后生成一个继承所有meta的类并把它赋值给form的 _wtforms_meta
class Form(with_metaclass(FormMeta, BaseForm)):
Meta = DefaultMeta
3.执行type.__call__( cls, *args, **kwargs)真的call方法
这时loginform中:
{
‘__module’: ‘__main__’,
‘name’:<UnboundField>(StringField…..),
‘pwd’: <UnboundField>(PasswordField…..),
‘__doc__’: None,
‘unbound_fields’: [(‘username’,<UnboundField StringField>), (‘pwd’, …….Field)],
‘_wtforms_meta’: class.Meta,
}

2.然后执行自己的__new__方法(或继承父类的)
发现父类并没有写__new__,继承的object的。不用找了

3.再执行自己的__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()
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)
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)
self.process(formdata, obj, data=data, **kwargs)
1.实例化_wtforms_meta meta_obj = self._wtforms_meta()
meta的功能之一csrf验证隐藏标签,django自带csrf但flask没有,wtform可以提供(生成csrf input框)
meta_obj = self._wtforms_meta()
if meta is not None and isinstance(meta, dict):
meta_obj.update_values(meta)
如果设置了meta参数,比如loginForm(meta={})会帮助更新到wtforms_meta.
2.传入meta和field列表执行父类的构造方法
super(Form, self).__init__(self._unbound_fields, meta=meta_obj, prefix=prefix)

class BaseForm(object):
def __init__(self, fields, prefix='', meta=DefaultMeta()):
if prefix and prefix[-1] not in '-_;:/.':
prefix += '-'

self.meta = meta
self._prefix = prefix
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)
self._fields[name] = field
1.生成csrf的隐藏field作为extra_field
2.遍历大的field可迭代对象
3.把每个field对象实例化(调用field的bind)
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)

4.这时候对象有了_name和_field,开始走自己真的__new__(之前是走Unbound的)
class Field(object):
errors = tuple()
process_errors = tuple()
raw_data = None
validators = tuple()
widget = None
_formfield = True
_translations = DummyTranslations()
do_not_call_in_templates = True # Allow Django 1.4 traversal

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)

然后执行自己的init

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
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 _translations is not None:
self._translations = _translations

if _meta is not None:
self.meta = _meta
elif _form is not None:
self.meta = _form.meta
else:
raise TypeError("Must provide one of _form or _meta")

self.default = default
self.description = description
self.render_kw = render_kw
self.filters = filters
self.flags = Flags()
self.name = _prefix + _name
self.short_name = _name
self.type = type(self).__name__

self.check_validators(validators)
self.validators = validators or self.validators

self.id = id or self.name
self.label = Label(self.id, label if label is not None else self.gettext(_name.replace('_', ' ').title()))

if widget is not None:
self.widget = widget

for v in itertools.chain(self.validators, [self.widget]):
flags = getattr(v, 'field_flags', ())
for f in flags:
setattr(self.flags, f, True)

这个时候loginform中
{
‘__module’: ‘__main__’,
‘name’:<UnboundField>(StringField…..),
‘pwd’: <UnboundField>(PasswordField…..),
‘__doc__’: None,
‘unbound_fields’: [(‘username’,<UnboundField StringField>), (‘pwd’, …….Field)], # 排序加计数
‘_wtforms_meta’: class.Meta,
‘_fields’:{‘username’:StringField(*args), ‘csrf’:….., ‘pwd’:xxxx},
‘_prefix’:xxxxx,xxxx
}

再回到form.__init__继续往下执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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)

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)
self.process(formdata, obj, data=data, **kwargs)
这里为什么要遍历赋值?因为这个时候要取对象还需要loginForm._fields[‘username’]这样取值,而我们想要LoginForm.username这样就可取值
setattr(self, name, value)

class Foo:
creator = 0
value = {'a': 1, 'b': '2'}

def __init__(self):
Foo.creator += 1
for name, val in self.value.items():
setattr(self, name, val)

f = Foo()
print(f.creator)
setattr(f, 'name', 100)
print(f.name, f.a, f.b)
g = Foo()
print(g.creator)

执行process(为每个字段设置默认值)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
这里要传入了formdata才有用,比如loginForm(formdata=request.form)
(每个field里面还有它的widget插件)
form = LoginForm(formdata=request.form)
print(form.username)
执行field.__str__方法
def __str__(self):
return self()
执行field.__call__方法
def __call__(self, **kwargs):
return self.meta.render_field(self, kwargs)
执行meta的render_field
def render_field(self, field, render_kw):
other_kw = getattr(field, 'render_kw', None)
if other_kw is not None:
render_kw = dict(other_kw, **render_kw)
return field.widget(field, **render_kw)
执行widget插件的__call__方法
def __call__(self, field, **kwargs):
kwargs.setdefault('id', field.id)
kwargs.setdefault('type', self.input_type)
if 'value' not in kwargs:
kwargs['value'] = field._value()
if 'required' not in kwargs and 'required' in getattr(field, 'flags', []):
kwargs['required'] = True
return Markup('<input %s>' % self.html_params(name=field.name, **kwargs))
(字符串拼接)最终返回给print(form.name)的就是这个返回值

最后关于form对象的__iter__:
def __iter__(self):
return iter(itervalues(self._fields))
import itertools
from wtforms.compat import with_metaclass, iteritems, itervalues

form的验证流程

用户请求发的所有数据,{‘name’:123,xxxx}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
def process(self, formdata=None, obj=None, data=None, **kwargs):
formdata = self.meta.wrap_formdata(self, formdata)

if data is not None:
# XXX we want to eventually process 'data' as a new entity.
# Temporarily, this can simply be merged with kwargs.
kwargs = dict(data, **kwargs)

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)


其实传这三个值都一样(传的格式不同),可以传三种格式
form = LoginForm(formdata=request.form) # 取val.get_list()
form = LoginForm(obj=request.form) # 取val.name/val.pwd
form = LoginForm(data=request.form) # val[‘name’]

执行form.validate表单

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
if form.validate():
print('通过验证')
else:
print(form.errors)

def validate(self, extra_validators=None):
if extra_validators is not None:
extra = extra_validators.copy()
else:
extra = {}

for name in self._fields:
inline = getattr(self.__class__, 'validate_%s' % name, None)

if inline is not None:
extra.setdefault(name, []).append(inline)

return super(Form, self).validate(extra)

循环fields对象,找寻对象中的钩子函数,放到extra中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
extra= {
name:钩子
}

def validate(self, extra_validators=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

如果有钩子函数则获取,并执行字段的validate(extra)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
def validate(self, form, extra_validators=tuple()):
self.errors = list(self.process_errors)
stop_validation = False

# Check the type of extra_validators
self.check_validators(extra_validators)

# Call pre_validate
try:
self.pre_validate(form)
except StopValidation as e:
if e.args and e.args[0]:
self.errors.append(e.args[0])
stop_validation = True
except ValueError as e:
self.errors.append(e.args[0])

# Run validators
if not stop_validation:
chain = itertools.chain(self.validators, extra_validators)
stop_validation = self._run_validation_chain(form, chain)

# Call post_validate
try:
self.post_validate(form, stop_validation)
except ValueError as e:
self.errors.append(e.args[0])

return len(self.errors) == 0
拼接传入的validators
pwd1 = PasswordField(u"密码", validators=[DataRequired(), EqualTo('pwd2', '密码填入不一致')])
和生成的自定义钩子函数extra_validators
分享到