内容简介:wtforms快速使用和源码分析(基于flask)
wtforms
和django的form组件大同小异,下面给出一个应用举例以便快速查询。
开始使用
1 from flask import Flask, render_template, request, redirect 2 3 from wtforms import Form 4 5 from wtforms.fields import core 6 from wtforms.fields import html5 7 from wtforms.fields import simple 8 9 from wtforms import validators 10 from wtforms import widgets 11 12 app = Flask(__name__, template_folder='templates') 13 app.debug = True 14 15 class MyValidator(object): 16 def __init__(self,message): 17 self.message = message 18 def __call__(self, form, field): 19 print(field.data) 20 if field.data == '王浩': 21 return None 22 raise validators.StopValidation(self.message) 23 24 25 class LoginForm(Form): 26 name = simple.StringField( 27 label='用户名', 28 validators=[ 29 # MyValidator(message='用户名必须等于王浩') 30 validators.DataRequired(message='用户名不能为空.'), 31 validators.Length(min=6, max=18, message='用户名长度必须大于%(min)d且小于%(max)d') 32 ], 33 widget=widgets.TextInput(), 34 render_kw={'class': 'form-control'} 35 ) 36 pwd = simple.PasswordField( 37 label='密码', 38 validators=[ 39 validators.DataRequired(message='密码不能为空.'), 40 validators.Length(min=8, message='用户名长度必须大于%(min)d'), 41 validators.Regexp(regex="^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[$@$!%*?&])[A-Za-z\d$@$!%*?&]{8,}", 42 message='密码至少8个字符,至少1个大写字母,1个小写字母,1个数字和1个特殊字符') 43 ], 44 widget=widgets.PasswordInput(), 45 render_kw={'class': 'form-control'} 46 ) 47 48 49 @app.route('/login', methods=['GET', 'POST']) 50 def login(): 51 if request.method == 'GET': 52 form = LoginForm() 53 return render_template('login.html', form=form) 54 else: 55 form = LoginForm(formdata=request.form) 56 if form.validate(): 57 print('用户提交数据通过格式验证,提交的值为:', form.data) 58 else: 59 print(form.errors) 60 return render_template('login.html', form=form) 61 62 63 # ########################### 用户注册 ########################## 64 class RegisterForm(Form): 65 name = simple.StringField( 66 label='用户名', 67 validators=[ 68 validators.DataRequired() 69 ], 70 widget=widgets.TextInput(), 71 render_kw={'class': 'form-control'}, 72 default='alex' 73 ) 74 75 pwd = simple.PasswordField( 76 label='密码', 77 validators=[ 78 validators.DataRequired(message='密码不能为空.') 79 ], 80 widget=widgets.PasswordInput(), 81 render_kw={'class': 'form-control'} 82 ) 83 84 pwd_confirm = simple.PasswordField( 85 label='重复密码', 86 validators=[ 87 validators.DataRequired(message='重复密码不能为空.'), 88 validators.EqualTo('pwd', message="两次密码输入不一致") 89 ], 90 widget=widgets.PasswordInput(), 91 render_kw={'class': 'form-control'} 92 ) 93 94 email = html5.EmailField( 95 label='邮箱', 96 validators=[ 97 validators.DataRequired(message='邮箱不能为空.'), 98 validators.Email(message='邮箱格式错误') 99 ], 100 widget=widgets.TextInput(input_type='email'), 101 render_kw={'class': 'form-control'} 102 ) 103 104 gender = core.RadioField( 105 label='性别', 106 choices=( 107 (1, '男'), 108 (2, '女'), 109 ), 110 coerce=int 111 ) 112 city = core.SelectField( 113 label='城市', 114 choices=( 115 ('bj', '北京'), 116 ('sh', '上海'), 117 ) 118 ) 119 120 hobby = core.SelectMultipleField( 121 label='爱好', 122 choices=( 123 (1, '篮球'), 124 (2, '足球'), 125 ), 126 coerce=int 127 ) 128 129 favor = core.SelectMultipleField( 130 label='喜好', 131 choices=( 132 (1, '篮球'), 133 (2, '足球'), 134 ), 135 widget=widgets.ListWidget(prefix_label=False), 136 option_widget=widgets.CheckboxInput(), 137 coerce=int, 138 default=[1, 2] 139 ) 140 141 def __init__(self, *args, **kwargs): 142 super(RegisterForm, self).__init__(*args, **kwargs) 143 self.favor.choices = ((1, '篮球'), (2, '足球'), (3, '羽毛球')) 144 145 def validate_pwd_confirm(self, field): 146 """ 147 自定义pwd_confirm字段规则,例:与pwd字段是否一致 148 :param field: 149 :return: 150 """ 151 # 最开始初始化时,self.data中已经有所有的值 152 153 if field.data != self.data['pwd']: 154 # raise validators.ValidationError("密码不一致") # 继续后续验证 155 raise validators.StopValidation("密码不一致") # 不再继续后续验证 156 157 158 @app.route('/register', methods=['GET', 'POST']) 159 def register(): 160 if request.method == 'GET': 161 # 设置默认值 162 form = RegisterForm(data={'gender': 1}) 163 return render_template('register.html', form=form) 164 else: 165 form = RegisterForm(formdata=request.form) 166 if form.validate(): 167 print('用户提交数据通过格式验证,提交的值为:', form.data) 168 else: 169 print(form.errors) 170 return render_template('register.html', form=form) 171 172 173 if __name__ == '__main__': 174 app.run() View Code
源码分析
-
高级用法
- metaclass的另类使用
-
切入点:
- 当定义好一个自定义的 Form 类,项目加载 Form 类所在模块,代码都做了什么?
-
在视图函数中实例化 Form
类,代码都做了什么?
- 模板渲染调用 Form 的字段时,代码做了什么?
- 前端填好数据,返回后端校验时,代码做了什么?
详细分析
-
项目加载 Form
类所在模块
-
Form
类
-
这是声明 Form 的代码:
class Form(with_metaclass(FormMeta, BaseForm))
-
可见这里调用一个函数 with_metaclass
1 def with_metaclass(meta, base=object): 2 return meta("NewBase", (base,), {}) View Code
-
该函数返回了一个 FormMeta 元类创建的 NewBase 类作为 Form 类的基类
-
元类创建类会执行元类的 __init__ 方法
1 def __init__(cls, name, bases, attrs): 2 type.__init__(cls, name, bases, attrs) 3 cls._unbound_fields = None 4 cls. = None View Code
-
此处为 Form 类定义了 _unbound_fields 和 _wtforms_meta 两个静态字段
-
-
类的字段
-
拿一个字段举例:
username = simple.StringField()
-
可见是实例化了一个 StringField
类
-
StringField
类定义了一个静态字段:
widget = widgets.TextInput()
-
这里是实例化了一个插件类 TextInput
-
TextInput类定义了一个静态字段:
input_type = 'text'
指定生成html标签时的type - 基类中的__init__方法只是将检测了一下input_type
-
TextInput类定义了一个静态字段:
-
这里是实例化了一个插件类 TextInput
-
StringField
类没有__init__方法,__new__方法,显然要从基类中寻找
-
基类中的__new__方法返回的是:
UnboundField(cls, *args, **kwargs)
实例化了一个 UnboundField 类的对象1 creation_counter = 0 2 def __init__(self, field_class, *args, **kwargs): 3 UnboundField.creation_counter += 1 4 self.field_class = field_class 5 self.args = args 6 self.kwargs = kwargs 7 self.creation_counter = UnboundField.creation_counter View Code
- 可见 UnboundField 类封装了字段的类 StringField ,并给了字段一个编号,看来wtforms应该就是通过这个编号来识别字段的顺序
-
基类中的__init__方法
- 有意思的是此时__init__的接收的self已经不是 Field 类的实例,而是 UnboundField 类的实例
-
init这里大部分操作都是常规的赋值操作,不过也有个值得关注的地方
-
- 通过这两行代码可以看出,wtforms内部还实现了实现了多语言的提示信息
-
-
-
StringField
类定义了一个静态字段:
- 最后得出结论,form类的静态字段如username此时存储的是 UnboundField 类的实例
-
拿一个字段举例:
-
Form
类
-
在视图函数中实例化form类
-
首先执行元类的__call__方法
1 def __call__(cls, *args, **kwargs): 2 """ 3 Construct a new `Form` instance. 4 5 Creates the `_unbound_fields` list and the internal `_wtforms_meta` 6 subclass of the class Meta in order to allow a proper inheritance 7 hierarchy. 8 """ 9 if cls._unbound_fields is None: 10 fields = [] 11 for name in dir(cls): 12 if not name.startswith('_'): 13 unbound_field = getattr(cls, name) 14 if hasattr(unbound_field, '_formfield'): 15 fields.append((name, unbound_field)) 16 # We keep the name as the second element of the sort 17 # to ensure a stable sort. 18 fields.sort(key=lambda x: (x[1].creation_counter, x[0])) 19 cls._unbound_fields = fields 20 21 # Create a subclass of the 'class Meta' using all the ancestors. 22 if cls._wtforms_meta is None: 23 bases = [] 24 for mro_class in cls.__mro__: 25 if 'Meta' in mro_class.__dict__: 26 bases.append(mro_class.Meta) 27 cls._wtforms_meta = type('Meta', tuple(bases), {}) 28 return type.__call__(cls, *args, **kwargs) View Code
-
这里就是给form类的_unbound_fields和_wtforms_meta赋值
-
使用
dir(cls)
获取form类的所有变量字符串,当其不为'_'开头时说明是自定义form的字段,获取字段的对应的对象,根据字段的编号 排序 后加入_unbound_fields列表-
此处的判断做的还不够好,或许通过
cls.__dict__.items()
获取到所有的变量名和值,判断值是否为 UnboundFie ld的实例,若为 UnboundField 的实例则加入列表
-
此处的判断做的还不够好,或许通过
- 使用__mro__获取form类所有的继承关系,挨个寻找这些类中的 Meta 字段对应的类计入bases列表,最后通过type一次型创建一个继承了所有bases列表中的类的 Meta 类,并存入_wtforms_meta字段
-
使用
-
这里就是给form类的_unbound_fields和_wtforms_meta赋值
-
接着应该执行类的__new__方法,不过这里没有定义,忽略此步骤
-
然后执行类的__init__方法
1 def __init__(self, formdata=None, obj=None, prefix='', data=None, meta=None, **kwargs): 2 meta_obj = self._wtforms_meta() 3 if meta is not None and isinstance(meta, dict): 4 meta_obj.update_values(meta) 5 super(Form, self).__init__(self._unbound_fields, meta=meta_obj, prefix=prefix) 6 7 for name, field in iteritems(self._fields): 8 setattr(self, name, field) 9 self.process(formdata, obj, data=data, **kwargs) View Code
-
这里首先实例化了_wtforms_meta字段对应的 Meta 类然后传入了基类的__init__方法
1 def __init__(self, fields, prefix='', meta=DefaultMeta()): 2 if prefix and prefix[-1] not in '-_;:/.': 3 prefix += '-' 4 5 self.meta = meta 6 self._prefix = prefix 7 self._errors = None 8 self._fields = OrderedDict() 9 10 if hasattr(fields, 'items'): 11 fields = fields.items() 12 13 translations = self._get_translations() 14 extra_fields = [] 15 if meta.csrf: 16 self._csrf = meta.build_csrf(self) 17 extra_fields.extend(self._csrf.setup_form(self)) 18 19 for name, unbound_field in itertools.chain(fields, extra_fields): 20 options = dict(name=name, prefix=prefix, translations=translations) 21 field = meta.bind_field(self, unbound_field, options) 22 self._fields[name] = field View Code
- prefix是设置生成html标签的name属性的值的前缀
-
meta是一个继承了form类以及所有form类继承的类中的 Meta
类的实例
- 这里检测了是否启用了csrf字段
- 并将meta中额外定义的有关csrf的字段加入extra_fields
-
最后将fields和extra_fields中所有的字段全部放置在form类实例的_fields字段中
-
这里通过
field = meta.bind_field(self, unbound_field, options)
对所有的UnboundField类实例做了处理,找回了原有的Field
-
这里通过
-
然后将类中的_fields表中的字段设置为form类实例的属性
- 注意:此时的字段已经变回了原来的 Field ,尚不明确为何要多进行这样的操作
-
这行代码主要是针对有数据传入时的操作
self.process(formdata, obj, data=data, **kwargs)
- 在第4点详细查看
-
-
-
模板渲染调用form的字段
-
此时本质上就是调用了字段的__str__方法,把返回的字符串放置在模板
1 def __str__(self): 2 """ 3 Returns a HTML representation of the field. For more powerful rendering, 4 see the `__call__` method. 5 """ 6 return self() View Code
-
转为调用字段的__call__方法
1 def __call__(self, **kwargs): 2 """ 3 Render this field as HTML, using keyword args as additional attributes. 4 5 This delegates rendering to 6 :meth:`meta.render_field <wtforms.meta.DefaultMeta.render_field>` 7 whose default behavior is to call the field's widget, passing any 8 keyword arguments from this call along to the widget. 9 10 In all of the WTForms HTML widgets, keyword arguments are turned to 11 HTML attributes, though in theory a widget is free to do anything it 12 wants with the supplied keyword arguments, and widgets don't have to 13 even do anything related to HTML. 14 """ 15 return self.meta.render_field(self, kwargs) View Code
-
继续调用 Meta 类的render_field方法,这个方法在 DefaultMeta 类
1 def render_field(self, field, render_kw): 2 """ 3 render_field allows customization of how widget rendering is done. 4 5 The default implementation calls ``field.widget(field, **render_kw)`` 6 """ 7 other_kw = getattr(field, 'render_kw', None) 8 if other_kw is not None: 9 render_kw = dict(other_kw, **render_kw) 10 return field.widget(field, **render_kw) View Code
-
这里调用了字段的插件对象的__call__方法
1 def __call__(self, field, **kwargs): 2 kwargs.setdefault('id', field.id) 3 kwargs.setdefault('type', self.input_type) 4 if 'value' not in kwargs: 5 kwargs['value'] = field._value() 6 return HTMLString('<input %s>' % self.html_params(name=field.name, **kwargs)) View Code
-
至此,完成了 Form 类实例的__str__方法,返回了一个HTML的input标签的字符串
-
-
前端填好数据,返回后端校验
-
依然是实例化一个 Form 类的对象,大部分流程和第2点讨论的一致,不过在执行到 Form 类的__init__方法的最后一行时开始不同
-
self.process(formdata, obj, data=data, **kwargs)
1 def process(self, formdata=None, obj=None, data=None, **kwargs): 2 formdata = self.meta.wrap_formdata(self, formdata) 3 4 if data is not None: 5 kwargs = dict(data, **kwargs) 6 7 for name, field, in iteritems(self._fields): 8 if obj is not None and hasattr(obj, name): 9 field.process(formdata, getattr(obj, name)) 10 elif name in kwargs: 11 field.process(formdata, kwargs[name]) 12 else: 13 field.process(formdata) View Code
-
这里根据传入的数据不同做不同的操作
-
formdata = self.meta.wrap_formdata(self, formdata)
是将不具有getlist方法的formdata的对象封装一个getlist对象 -
field.process函数就是将数据封装进self.data和self.row_data
1 def process(self, formdata, data=unset_value): 2 self.process_errors = [] 3 if data is unset_value: 4 try: 5 data = self.default() 6 except TypeError: 7 data = self.default 8 9 self.object_data = data 10 11 try: 12 self.process_data(data) 13 except ValueError as e: 14 self.process_errors.append(e.args[0]) 15 16 if formdata: 17 try: 18 if self.name in formdata: 19 self.raw_data = formdata.getlist(self.name) 20 else: 21 self.raw_data = [] 22 self.process_formdata(self.raw_data) 23 except ValueError as e: 24 self.process_errors.append(e.args[0]) 25 26 try: 27 for filter in self.filters: 28 self.data = filter(self.data) 29 except ValueError as e: 30 self.process_errors.append(e.args[0]) View Code
-
-
-
然后调用form.validate方法
1 def validate(self): 2 extra = {} 3 for name in self._fields: 4 inline = getattr(self.__class__, 'validate_%s' % name, None) 5 if inline is not None: 6 extra[name] = [inline] 7 return super(Form, self).validate(extra) View Code
- 把每个字段的校验规则封装进extra
-
然后调用 BaseForm 的validate()
1 def validate(self, extra_validators=None): 2 self._errors = None 3 success = True 4 for name, field in iteritems(self._fields): 5 if extra_validators is not None and name in extra_validators: 6 extra = extra_validators[name] 7 else: 8 extra = tuple() 9 if not field.validate(self, extra): 10 success = False 11 return success View Code
-
然后挨个调用字段的校验方法完成校验
-
示例: 自定义简单的form组件
以上所述就是小编给大家介绍的《wtforms快速使用和源码分析(基于flask)》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- Spring事务源码分析专题(一)JdbcTemplate使用及源码分析
- 使用源码编译Hadoop
- GYHttpMock:使用及源码解析
- fishhook使用场景&源码分析
- WMRouter使用和源码分析
- 使用 Clion 阅读 Envoy 源码
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
We Are the Nerds
Christine Lagorio-Chafkin / Hachette Books / 2018-10-2 / USD 18.30
Reddit hails itself as "the front page of the Internet." It's the third most-visited website in the United States--and yet, millions of Americans have no idea what it is. We Are the Nerds is an eng......一起来看看 《We Are the Nerds》 这本书的介绍吧!
图片转BASE64编码
在线图片转Base64编码工具
HTML 编码/解码
HTML 编码/解码