wtforms组件使用实例及源码解析
WTForms是一个支持多个web框架的form组件,主要用于对用户请求数据进行验证。
WTforms作用:当网站中需要用到表单时,WTForms变得很有效。应该把表单定义为类,作为单独的一个模块。
创建表单: 创建表单时,通常是创建一个Form的子类,表单的中的字段作为类的属性。
1 from flask import Flask, render_template, request, redirect 2 from wtforms import Form 3 from wtforms.fields import core 4 from wtforms.fields import html5 5 from wtforms.fields import simple 6 7 from wtforms import validators 8 from wtforms import widgets 9 10 app = Flask(__name__, template_folder='templates') 11 app.debug = True 12 13 class MyValidator(object): # 自定义验证器 14 def __init__(self,message): 15 self.message = message 16 17 def __call__(self, form, field): 18 # print(field.data) 19 if field.data == '王浩': 20 return None 21 raise validators.StopValidation(self.message) 22 23 24 class LoginForm(Form): 25 name = simple.StringField( 26 label='用户名', 27 validators=[ 28 # MyValidator(message='用户名必须等于王浩') 29 validators.DataRequired(message='用户名不能为空.'), 30 validators.Length(min=6, max=18, message='用户名长度必须大于%(min)d且小于%(max)d') 31 ], 32 widget=widgets.TextInput(), 33 render_kw={'class': 'form-control'} 34 ) 35 pwd = simple.PasswordField( 36 label='密码', 37 validators=[ 38 validators.DataRequired(message='密码不能为空.'), 39 validators.Length(min=8, message='用户名长度必须大于%(min)d'), 40 validators.Regexp(regex="^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[$@$!%*?&])[A-Za-z\d$@$!%*?&]{8,}", 41 message='密码至少8个字符,至少1个大写字母,1个小写字母,1个数字和1个特殊字符') 42 ], 43 widget=widgets.PasswordInput(), 44 render_kw={'class': 'form-control'} 45 ) 46 47 48 @app.route('/login', methods=['GET', 'POST']) 49 def login(): 50 if request.method == 'GET': 51 form = LoginForm() 52 return render_template('login.html', form=form) 53 else: 54 form = LoginForm(formdata=request.form) 55 if form.validate(): 56 print('用户提交数据通过格式验证,提交的值为:', form.data) 57 else: 58 print(form.errors) 59 return render_template('login.html', form=form) 60 61 62 # ########################### 用户注册 ########################## 63 class RegisterForm(Form): 64 name = simple.StringField( 65 label='用户名', 66 validators=[ 67 validators.DataRequired() 68 ], 69 widget=widgets.TextInput(), 70 render_kw={'class': 'form-control'}, 71 default='alex' 72 ) 73 74 pwd = simple.PasswordField( 75 label='密码', 76 validators=[ 77 validators.DataRequired(message='密码不能为空.') 78 ], 79 widget=widgets.PasswordInput(), 80 render_kw={'class': 'form-control'} 81 ) 82 83 pwd_confirm = simple.PasswordField( 84 label='重复密码', 85 validators=[ 86 validators.DataRequired(message='重复密码不能为空.'), 87 validators.EqualTo('pwd', message="两次密码输入不一致") 88 ], 89 widget=widgets.PasswordInput(), 90 render_kw={'class': 'form-control'} 91 ) 92 93 email = html5.EmailField( 94 label='邮箱', 95 validators=[ 96 validators.DataRequired(message='邮箱不能为空.'), 97 validators.Email(message='邮箱格式错误') 98 ], 99 widget=widgets.TextInput(input_type='email'), 100 render_kw={'class': 'form-control'} 101 ) 102 103 gender = core.RadioField( 104 label='性别', 105 choices=( 106 (1, '男'), 107 (2, '女'), 108 ), 109 coerce=int 110 ) 111 city = core.SelectField( 112 label='城市', 113 choices=( 114 ('bj', '北京'), 115 ('sh', '上海'), 116 ) 117 ) 118 119 hobby = core.SelectMultipleField( 120 label='爱好', 121 choices=( 122 (1, '篮球'), 123 (2, '足球'), 124 ), 125 coerce=int 126 ) 127 128 favor = core.SelectMultipleField( 129 label='喜好', 130 choices=( 131 (1, '篮球'), 132 (2, '足球'), 133 ), 134 widget=widgets.ListWidget(prefix_label=False), 135 option_widget=widgets.CheckboxInput(), 136 coerce=int, 137 default=[1, 2] 138 ) 139 140 def __init__(self, *args, **kwargs): 141 super(RegisterForm, self).__init__(*args, **kwargs) 142 self.favor.choices = ((1, '篮球'), (2, '足球'), (3, '羽毛球')) 143 144 def validate_pwd_confirm(self, field): 145 """ 146 自定义pwd_confirm字段规则,例:与pwd字段是否一致 147 :param field: 148 :return: 149 """ 150 # 最开始初始化时,self.data中已经有所有的值 151 152 if field.data != self.data['pwd']: 153 # raise validators.ValidationError("密码不一致") # 继续后续验证 154 raise validators.StopValidation("密码不一致") # 不再继续后续验证 155 156 157 @app.route('/register', methods=['GET', 'POST']) 158 def register(): 159 if request.method == 'GET': 160 # 设置默认值 161 form = RegisterForm(data={'gender': 1}) 162 return render_template('register.html', form=form) 163 else: 164 form = RegisterForm(formdata=request.form) 165 if form.validate(): 166 print('用户提交数据通过格式验证,提交的值为:', form.data) 167 else: 168 print(form.errors) 169 return render_template('register.html', form=form) 170 171 172 if __name__ == '__main__': 173 app.run()
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Title</title> 6 </head> 7 <body> 8 <h1>用户注册</h1> 9 <form method="post" novalidate style="padding:0 50px"> 10 {% for item in form %} 11 <p>{{item.label}}: {{item}} {{item.errors[0] }}</p> 12 {% endfor %} 13 <input type="submit" value="提交"> 14 </form> 15 </body> 16 </html>
源码分析
将按照代码的执行顺序分析
1.当定义好一个自定义的Form类,项目加载Form类所在模块,代码都做了什么?
class RegisterForm(Form): name = simple.StringField( label='用户名', validators=[ validators.DataRequired() ], widget=widgets.TextInput(), render_kw={'class': 'form-control'}, # default='alex' ) pwd = simple.PasswordField( label='密码', validators=[ validators.DataRequired(message='密码不能为空.') ], widget=widgets.PasswordInput(), render_kw={'class': 'form-control'} )
自定义类继承了Form类,看一下Form类
class Form(with_metaclass(FormMeta, BaseForm)): pass def with_metaclass(meta, base=object): return meta("NewBase", (base,), {}) # 那么相当于Form继承了FormMeta("NewBase",(BaseForm,),{} ) 使用 FormMeta元类创建的对象让Form继承,则Form的元类也被指定为FormMeta class FormMeta(type): def __init__(cls, name, bases, attrs): type.__init__(cls, name, bases, attrs) cls._unbound_fields = None cls._wtforms_meta = None
则 FormMeta创建RegisterForm为了RegisterForm产生了两个类变量
RegisterForm._unbound_fields = None
RegisterForm._wtforms_meta = None
再看类变量name 和 pwd 的字段是什么
class StringField(Field): """ This field is the base for most of the more complicated fields, and represents an ``<input type="text">``. """ widget = widgets.TextInput() def process_formdata(self, valuelist): if valuelist: self.data = valuelist[0] elif self.data is None: self.data = '' def _value(self): return text_type(self.data) if self.data is not None else '' class Field(object): """ Field base class """ 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): pass
此时RegisterForm的类变量为
RegisterForm._unbound_fields = None RegisterForm._wtforms_meta = None RegisterForm.name = UnboundField(simple.StringField) RegisterForm.pwd = UnboundField(simple.PasswordField)
print(RegisterForm.name,type(RegisterForm.name)) “”“ <UnboundField(StringField, (), {'label': '用户名', 'validators': [<wtforms.validators.DataRequired object at 0x000001C76FE778D0>],
'widget': <wtforms.widgets.core.TextInput object at 0x000001C76FE77B00>, 'render_kw': {'class': 'form-control'}, 'default': 'ppp'})> <class 'wtforms.fields.core.UnboundField'> ”“”
class UnboundField(object): _formfield = True creation_counter = 0 # 初始值为0 def __init__(self, field_class, *args, **kwargs): UnboundField.creation_counter += 1 # 每事实例化一次,类变量creation_counter +1 self.field_class = field_class self.args = args self.kwargs = kwargs self.creation_counter = UnboundField.creation_counter # 实例自己的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)
所以可知
RegisterForm._unbound_fields = None RegisterForm._wtforms_meta = None RegisterForm.name = UnboundField(creation_counter=1,simple.StringField) RegisterForm.pwd = UnboundField(creation_counter=,2,simple.PasswordField)
2.接着在视图函数中RegisterForm中实例化发生了什么?
补充:dir() 函数不带参数时,返回当前范围内的变量、方法和定义的类型列表;带参数时,返回参数的属性、方法列表。如果参数包含方法__dir__(),该方法将被调用。如果参数不包含__dir__(),该方法将最大限度地收集参数信息。
1 class A: 2 x = 'xxx' 3 age = 111 4 def __init__(self,name): 5 self.name = 'cp' 6 7 def say_hi(self): 8 print('hi') 9 10 print(dir(A)) 11 a = A('cp') 12 print("="*40) 13 print(dir(a)) 14 """ 15 ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', \ 16 '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', \ 17 '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'age', 'say_hi', 'x' 18 ] 19 ======================================== 20 ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', \ 21 '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', \ 22 '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'age', 'name', 'say_hi', 'x' 23 ] 24 """
执行 form = RegisterForm(data={'gender': 1})
# 因为RegisterForm指定了FormMeta为元类,则()触发其__call__方法 def __call__(cls, *args, **kwargs): if cls._unbound_fields is None: fields = [] for name in dir(cls): # 遍历RegisterForm的成员 name为str if not name.startswith('_'): # 可知自定义表单类中字段名不能以'_'开头 unbound_field = getattr(cls, name) if hasattr(unbound_field, '_formfield'): # _formfield为UnboundField的类变量 默认True 如果有x=123 则为False fields.append((name, unbound_field)) # fields = [ # (name, UnboundField(creation_counter=1,simple.StringField)) # (pwd, UnboundField(creation_counter=2,simple.PasswordField)) # ] # 利用creation_counter序列编号按前后代码中的字段顺序排序 fields.sort(key=lambda x: (x[1].creation_counter, x[0])) cls._unbound_fields = fields # 此时_unbound_fields的初始值None被覆盖了
if cls._wtforms_meta is None:
bases = []
for mro_class in cls.__mro__: # 从RegisterForm的mro列表中遍历
if 'Meta' in mro_class.__dict__: # 查看是否有写去过Meta类
bases.append(mro_class.Meta) # 只在Form中找到 Meta = DefaultMeta
# 相当于 type('Meta', tuple(DefaultMeta), {})
cls._wtforms_meta = type('Meta', tuple(bases), {})
return type.__call__(cls, *args, **kwargs)
接着执行: type.__call__(cls, *args, **kwargs)
因为RegisterForm及其父类没有写__new__方法,则调用object的__new__创建对象,__init__则查找到父类Form中的。
1 class Form(with_metaclass(FormMeta, BaseForm)): 2 def __init__(self, formdata=None, obj=None, prefix='', data=None, meta=None, **kwargs): 3 meta_obj = self._wtforms_meta() 4 if meta is not None and isinstance(meta, dict): 5 meta_obj.update_values(meta) 6 super(Form, self).__init__(self._unbound_fields, meta=meta_obj, prefix=prefix) # 调用父类的方法 7 8 9 for name, field in iteritems(self._fields): # 上一步给self._fields赋好值后,循环 10 setattr(self, name, field) # 给self.name = simple.StringField() self.pwd = simple.PasswordField() 11 # 就可以 form.name form.pwd 了 12 self.process(formdata, obj, data=data, **kwargs) # BaseForm的方法 13 14 15 class BaseForm(object): 16 def __init__(self, fields, prefix='', meta=DefaultMeta()): 17 if prefix and prefix[-1] not in '-_;:/.': 18 prefix += '-' 19 20 self.meta = meta 21 self._prefix = prefix 22 self._errors = None 23 self._fields = OrderedDict() 24 25 if hasattr(fields, 'items'): 26 fields = fields.items() 27 28 translations = self._get_translations() 29 extra_fields = [] 30 if meta.csrf: 31 self._csrf = meta.build_csrf(self) 32 extra_fields.extend(self._csrf.setup_form(self)) 33 34 # fields = _unbound_fields = [ 35 # (name, UnboundField(creation_counter=1,simple.StringField)) 36 # (pwd, UnboundField(creation_counter=2,simple.PasswordField)) 37 # ] 38 for name, unbound_field in itertools.chain(fields, extra_fields): 39 options = dict(name=name, prefix=prefix, translations=translations) 40 field = meta.bind_field(self, unbound_field, options) # 实例化simple.StringField 41 self._fields[name] = field # OrderedDict()向有序字典中添加排好序的fields 42 43 # self._fields = OrderedDict = { 44 # name: simple.StringField(), 45 # pwd: simple.PasswordField(), 46 # } 47 48 def process(self, formdata=None, obj=None, data=None, **kwargs): 49 """ 50 Take form, object data, and keyword arg input and have the fields 51 process them. 52 。。。 53 """ 54 formdata = self.meta.wrap_formdata(self, formdata) 55 56 if data is not None: 57 # XXX we want to eventually process 'data' as a new entity. 58 # Temporarily, this can simply be merged with kwargs. 59 kwargs = dict(data, **kwargs) 60 61 for name, field, in iteritems(self._fields): 62 if obj is not None and hasattr(obj, name): 63 field.process(formdata, getattr(obj, name)) 64 elif name in kwargs: 65 field.process(formdata, kwargs[name]) 66 else: 67 field.process(formdata)
从源码中可知使用注意:
1、字段名是区分大小写的
2、字段名不能以'_'开头
3、字段名不能以'validate'开头
参考:
1.https://www.cnblogs.com/Chuck-Y/p/8260156.html?utm_source=tuicool&utm_medium=referral