Flask之wtforms相关
Posted dakrfitch
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Flask之wtforms相关相关的知识,希望对你有一定的参考价值。
基本操作
首先,使用wtforms不能实现前后端分离,准确来说前后端耦合性过高.如果我们对前后端分离要求不高(版本更替少等等)
wtforms与django的modelform类似,回顾的时候可以考虑对比着来看
在后端
创建一个LogIn的模型类(继承于Form)
1 from wtforms import Form 2 from wtforms import widgets,validators 3 from wtforms.fields import simple,html5,core 4 from flask import Flask,render_template,request 5 6 7 class LoginForm(Form): 8 name = simple.StringField( 9 validators=[ 10 validators.DataRequired(message=‘用户名不能为空.‘), 11 # validators.Length(min=6, max=18, message=‘用户名长度必须大于%(min)d且小于%(max)d‘) 12 ], 13 widget=widgets.TextInput(), 14 render_kw={‘placeholder‘:‘请输入用户名‘} 15 ) 16 pwd = simple.PasswordField( 17 validators=[ 18 validators.DataRequired(message=‘密码不能为空.‘), 19 # validators.Length(min=8, message=‘用户名长度必须大于%(min)d‘), 20 # validators.Regexp(regex="^(?=.*[a-z])(?=.*[A-Z])(?=.*d)(?=.*[[email protected]$!%*?&])[A-Za-z[email protected]$!%*?&]{8,}", 21 # message=‘密码至少8个字符,至少1个大写字母,1个小写字母,1个数字和1个特殊字符‘) 22 23 ], 24 render_kw={‘placeholder‘:‘请输入密码‘} 25 )
1 validators =[] #这里面写的是限制条件 2 3 widget = widgets.TextInput() #这里写的是类型 4 5 render_kw = {‘placeholder‘:‘input username‘} #这里写的是h5标签中的操作 6 7 label=‘username‘ #这里跟django一样写的是展示用的名字 8 9 default=‘abc‘ #这里的是默认值 10 11 choices=( 12 (1, ‘男‘), 13 (2, ‘女‘), 14 ), 15 coerce=int #表示强转选择框的值为int型 16 )#这里是单选框 相当于h5中的radio 17 18 19 city = core.SelectField( 20 label=‘城市‘, 21 choices=( 22 (‘bj‘, ‘北京‘), 23 (‘sh‘, ‘上海‘), 24 ) 25 ) #下拉选择框 26 27 28 hobby = core.SelectMultipleField( 29 label=‘爱好‘, 30 choices=( 31 (1, ‘篮球‘), 32 (2, ‘足球‘), 33 ), 34 coerce=int 35 )#多选框 36 37 38 favor = core.SelectMultipleField( 39 label=‘喜好‘, 40 choices=( 41 (1, ‘篮球‘), 42 (2, ‘足球‘), 43 ), 44 widget=widgets.ListWidget(prefix_label=False), 45 option_widget=widgets.CheckboxInput(), 46 coerce=int, 47 default=[1, ] 48 ) #复选框
在视图函数中
1 @app.route(‘/login‘,methods=[‘GET‘,‘POST‘]) 2 def login(): 3 if request.method == "GET": 4 form = LoginForm() 5 return render_template(‘login.html‘,form=form) 6 7 form = LoginForm(formdata=request.form) 8 if form.validate(): 9 print(form.data) 10 return redirect(‘https://www.baidu.com‘) 11 else: 12 return render_template(‘login.html‘, form=form)
在前端
第一版:直接手写
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <title>Title</title> <meta name="viewport" content="width=device-width, initial-scale=1"> </head> <body> <form method="post" novalidate> <p>用户名:{{form.name}}</p> <p>密码:{{form.pwd}} </p> <p><input type="submit" value="提交" ></p> </form> </body> </html>
第二版:显示错误信息:
1 <!DOCTYPE html> 2 <html lang="zh-CN"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Title</title> 6 <meta name="viewport" content="width=device-width, initial-scale=1"> 7 </head> 8 <body> 9 <form method="post" novalidate> 10 <p>用户名:{{form.name}} {{form.name.errors[0]}}</p> 11 <p>密码:{{form.pwd}} {{form.pwd.errors[0]}} </p> 12 <p><input type="submit" value="提交" ></p> 13 </form> 14 </body> 15 </html>
第三版:通过循环,迭代处所有字段
1 <!DOCTYPE html> 2 <html lang="zh-CN"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Title</title> 6 <meta name="viewport" content="width=device-width, initial-scale=1"> 7 </head> 8 <body> 9 <form method="post"> 10 {% for field in form %} 11 <p>{{field.label}}: {{field}} {{field.errors[0]}}</p> 12 {% endfor %} 13 <input type="submit" value="提交"> 14 </form> 15 </body> 16 </html>
源码浅析
序章:假装很正式
在开始之前,我们需要了解关于类和实例创建的相关知识:
- 类是由type类创建的:创建类的时候,首先是执行type()类的 __iinit__方法,在__init__方法中,创建了type()的实例,也就是我们想要的这个类.
- 当我们创建的类创建实例的时候(由于我们的类是type()的实例).所以,我们在实例化--例如tpm_a = Temp()的时候,因为Temp这个实例加上了括号,表示执行,实例执行表示执行类的__call__方法,所以我们自己定义的类(我们暂且叫做Temp)创建实例的时候,会触发type()的__call__方法.__call__方法会调用Temp的__new__方法和__init__方法,最后返回构造好的Temp()的实例
正文:wtforms的Form粗浅理解
就如同上半段的使用篇,想要使用wtfoms的Form需要先引入,之后创建自己想要的form类(这里我们叫它Temp(Form)),继承于Form,类中写自己需要的字段,之后,我们在视图函数中用自己的form类创建实例
这个时候,我们需要回忆起序章中所说的实例化的时候调用了type类的__call__方法,但是,当我们看到Temp(Form)的父类的时候会发现:
class Form(with_metaclass(FormMeta, BaseForm)): def with_metaclass(meta, base=object): return meta("NewBase", (base,), {})
注:下面的with_metaclass函数的作用是用于指定元类 参考链接如下:https://www.cnblogs.com/ajianbeyourself/p/4052084.html
首先,with_metaclass方法返回值形如type(classname, parentclasses , attrs),所以尝试查看FormMeta到底是什么,然后发现FormMeta继承于type类
1 #此处为源码 2 3 class FormMeta(type): 4 """ 5 The metaclass for `Form` and any subclasses of `Form`. 6 7 `FormMeta`‘s responsibility is to create the `_unbound_fields` list, which 8 is a list of `UnboundField` instances sorted by their order of 9 instantiation. The list is created at the first instantiation of the form. 10 If any fields are added/removed from the form, the list is cleared to be 11 re-generated on the next instantiation. 12 13 Any properties which begin with an underscore or are not `UnboundField` 14 instances are ignored by the metaclass. 15 """ 16 def __init__(cls, name, bases, attrs): 17 type.__init__(cls, name, bases, attrs) 18 cls._unbound_fields = None 19 cls._wtforms_meta = None 20 21 def __call__(cls, *args, **kwargs): 22 """ 23 Construct a new `Form` instance. 24 25 Creates the `_unbound_fields` list and the internal `_wtforms_meta` 26 subclass of the class Meta in order to allow a proper inheritance 27 hierarchy. 28 """ 29 if cls._unbound_fields is None: 30 fields = [] 31 for name in dir(cls): 32 if not name.startswith(‘_‘): 33 unbound_field = getattr(cls, name) 34 if hasattr(unbound_field, ‘_formfield‘): 35 fields.append((name, unbound_field)) 36 # We keep the name as the second element of the sort 37 # to ensure a stable sort. 38 fields.sort(key=lambda x: (x[1].creation_counter, x[0])) 39 cls._unbound_fields = fields 40 41 # Create a subclass of the ‘class Meta‘ using all the ancestors. 42 if cls._wtforms_meta is None: 43 bases = [] 44 for mro_class in cls.__mro__: 45 if ‘Meta‘ in mro_class.__dict__: 46 bases.append(mro_class.Meta) 47 cls._wtforms_meta = type(‘Meta‘, tuple(bases), {}) 48 return type.__call__(cls, *args, **kwargs)
所以Form类的元类是FormMeta,同理,继承于Form类的Temp类的元类也是FormMeta,
创建Temp类的执行过程:
当我们创建Temp类的时候,首先会执行FormMeta的__init__方法:
def __init__(cls, name, bases, attrs): type.__init__(cls, name, bases, attrs) cls._unbound_fields = None cls._wtforms_meta = None
相当于给Temp类添加了两个静态字段
以及我们Temp类中自己的字段:
class Temp(Form): name = simple.StringField( validators=[ validators.DataRequired(message=‘用户名不能为空.‘), ], widget=widgets.TextInput(), render_kw={‘placeholder‘:‘请输入用户名‘} ) pwd = simple.PasswordField( validators=[ validators.DataRequired(message=‘密码不能为空.‘), ], render_kw={‘placeholder‘:‘请输入密码‘} )
这个时候,Temp的两个字段的也在分别作实例化,此处以StringField为例:
#代码为源码 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 ‘‘
StringField的父类Fields继承自object,因此它的元类是普通的type(),它的实例化会首先执行__new__()然后执行__init__(),由与本身没有这两个方法,所以去父类调用,Field中__new__方法代码如下:
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)
因为由于我们的代码中未出现过‘_form‘和‘_name‘,所以返回值是UnboundField(cls,*args,**kwargs)
UnboundField是继承于object的类,源码如下:
#此处为源码 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)
首先定义了两个静态字段:
- _formfield = True
- creation_counter = 0 #这个就是我们的form中字段按照我们写好的顺序展示的理由
先执行__new__方法:
- 对静态计数字段进行+1操作
- 将原来的类赋值给UnboundField的field_class
- 其他赋值
- 将静态计数赋值给实例的creation_counter
在实例化Temp对象的时候,会执行FormMeta的__call__方法.源码见上一段代码
FormMeta的__call__执行过程:
首先判断当前类是否有_unbound_fields,而_unbound_fields和_wtforms_meta都是在FormMeta实例化的时候定义好的值为None的变量
if cls._unbound_fields is None: fields = []
然后遍历当前类的所有方法/属性,并获取不是以下划线开头的方法/属性,并通过反射获取到对应的值
for name in dir(cls): if not name.startswith(‘_‘): unbound_field = getattr(cls, name)
判断unbound_field中是否有‘_formfield‘
if hasattr(unbound_field, ‘_formfield‘): fields.append((name, unbound_field)) #这个在我们之前的UnboundField类中有_formfield = True
此时:
fields = [ (‘form的字段名‘,UnboundField的对象) ]
在对fields排序,将排序后的dields赋值给类的_unbound_fields
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)
这一段大致意思是从类的mro继承顺序中遍历判断是否存在‘Meta‘ 如果存在,则将类的_wtforms_meta的值为type()刚刚创建的Meta类
最后再调用父类的__call__方法
Temp类的__new__方法执行过程:
本身和父类中并没有new方法,所以再执行__init__方法
Temp类的__init__方法执行过程:
本身没有,父类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的构造方法如下:
#源码 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) self._fields[name] = field
此处挖坑,之后再填,原谅色以示敬意
BaseForm中的代码执行效果大致为:循环我们赋值过的_unbound_fields(代码中传参为fields),itertools.chain貌似是做一个列表,获得我们的字段名,还有UnboundField对象,并调用对象中原来的类名去实例化...
简而言之就是获取字段名和相应类的对象,并赋值给self._fields,此处完成,回到Form的__init__中:
这里主要是循环self._fields中的元素,并通过setattr进行赋值
self.process是往里面赋值,留作坑
至此,form相关大致解释完毕
以上是关于Flask之wtforms相关的主要内容,如果未能解决你的问题,请参考以下文章