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     )
View Code
 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>

 

 


 

源码浅析

 序章:假装很正式

在开始之前,我们需要了解关于类和实例创建的相关知识:

  1. 类是由type类创建的:创建类的时候,首先是执行type()类的 __iinit__方法,在__init__方法中,创建了type()的实例,也就是我们想要的这个类.
  2. 当我们创建的类创建实例的时候(由于我们的类是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)
View Code

 所以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 ‘‘
View Code

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)
View Code

 首先定义了两个静态字段:

  1. _formfield = True
  2. creation_counter = 0    #这个就是我们的form中字段按照我们写好的顺序展示的理由

先执行__new__方法:

  1. 对静态计数字段进行+1操作 
  2. 将原来的类赋值给UnboundField的field_class
  3. 其他赋值
  4. 将静态计数赋值给实例的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)
View Code

由于调用了父类的构造方法,父类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
View Code

此处挖坑,之后再填,原谅色以示敬意

BaseForm中的代码执行效果大致为:循环我们赋值过的_unbound_fields(代码中传参为fields),itertools.chain貌似是做一个列表,获得我们的字段名,还有UnboundField对象,并调用对象中原来的类名去实例化...

简而言之就是获取字段名和相应类的对象,并赋值给self._fields,此处完成,回到Form的__init__中:

这里主要是循环self._fields中的元素,并通过setattr进行赋值

self.process是往里面赋值,留作坑

 至此,form相关大致解释完毕

以上是关于Flask之wtforms相关的主要内容,如果未能解决你的问题,请参考以下文章

flask之wtform与flask-session组件

Flask之WTForms验证

Flask之WTForms

Flask之wtforms源码分析

Flask之WTForms

Flask之WTForms -- 2019-08-08 18:01:54