WTForms

  WTForms基础操作,可见骚师博客

  WTForms主要是两个功能:1.生成HTML标签  2.对数据格式进行验证  

  这里主要带大家去看下WTForms源码,是怎么个实现流程?

  首先看到下面的示例代码,捋顺一下类之间的关系

#!/usr/bin/env python
# -*- coding:utf-8 -*-
from flask import Flask, render_template, request, redirect
from wtforms import Form
from wtforms.fields import core
from wtforms.fields import html5
from wtforms.fields import simple
from wtforms import validators
from wtforms import widgets

app = Flask(__name__, template_folder='templates')
app.debug = True


class LoginForm(Form):
    name = simple.StringField(
        label='用户名',
        validators=[
            validators.DataRequired(message='用户名不能为空.'),
            validators.Length(min=6, max=18, message='用户名长度必须大于%(min)d且小于%(max)d')
        ],
        widget=widgets.TextInput(),
        render_kw={'class': 'form-control'}

    )
    pwd = simple.PasswordField(
        label='密码',
        validators=[
            validators.DataRequired(message='密码不能为空.'),
            validators.Length(min=8, message='用户名长度必须大于%(min)d'),
            validators.Regexp(regex="^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[$@$!%*?&])[A-Za-z\d$@$!%*?&]{8,}",
                              message='密码至少8个字符,至少1个大写字母,1个小写字母,1个数字和1个特殊字符')

        ],
        widget=widgets.PasswordInput(),
        render_kw={'class': 'form-control'}
    )



@app.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'GET':
        form = LoginForm()
        return render_template('login.html', form=form)
    else:
        form = LoginForm(formdata=request.form)
        if form.validate():
            print('用户提交数据通过格式验证,提交的值为:', form.data)
        else:
            print(form.errors)
        return render_template('login.html', form=form)

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

   form类下,封装了两个field对象,分别为name,pwd,每个field对象下面又给widget封装 了input对象

  第一:生成 HTML标签

  在前端执行{{form.name}}和后端print(form.name)的效果是一样的,所以它会执行对象里的__str__方法,form.name是field对象,也就是执行field对象下的__str__方法,比如StringField,它下面是没有这方法的,但是父类中Field是有的

    def __str__(self):
        return self()

   代码执行了return self(),也就会执行field类中的__call__方法

    def __call__(self, **kwargs):
        return self.meta.render_field(self, kwargs)

   而在render_field方法中最后又执行了field.widget(field, **render_kw),field.widget是StringField下面封装的widgets.TextInput对象,所以它又会去调用TextInput类下的__call__方法

  在父类input类下,找到了生成html标签的代码

    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 HTMLString('<input %s>' % self.html_params(name=field.name, **kwargs))

 

  第二:对数据进行验证

  1.  获取前端请求字段数据
  2. 循环每个字段进行正则匹配进行验证
class InputText(object):

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

class InputEmail(object):

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


class StringField(object):
    def __init__(self,wg,reg):
        self.wg = wg
        self.reg = reg

    def __str__(self):
        return str(self.wg)

    def valid(self,val):
        import re
        return re.match(self.reg,val)

class LoginForm(object):
    xyy = StringField(wg=InputText(),reg='\d+')
    lw = StringField(wg=InputEmail(),reg='\w+')

    def __str__(self,form):
        self.form = "用户发来的所有数据{xyy:'df',lw:'sdf'}"

    def validate(self):
        fields = {'xyy':self.xyy,'lw':self.lw}
        for name,field in fields.items():
            # name是 xyy、。lw
            # field:  StringField(wg=InputText(),reg='\d+') / StringField(wg=InputEmail(),reg='\w+')
             # 'df'
            field.valid(self.form[name])


#
# wp = LoginForm()
# print(wp.xyy)
# print(wp.lw)

wp = LoginForm(formdata=request.form)
wp.validate()

   从上面我们抽离出来的简单模型,看得出:各个类,分工还是很明确的,input类负责生成标签,而field类负责自己字段数据验证,而form主要统一验证,返回结果

 

  上面过程只是简单分析,详细分析,请收看下一集

  开始分析前,我们需要了解几个知识点

  • __new__ 用于生成对象
class Foo(object):

    def __init__(self):
        pass


    def __new__(cls, *args, **kwargs):
        """
        用于生成对象
        :param args:
        :param kwargs:
        :return:
        """

        return super(Foo,cls).__new__(cls, *args, **kwargs)
        # return "666"


obj = Foo()
print(obj)
  •  __dict__获取所有成员
class Foo(object):
    AGE = 123
    def __init__(self, na):
        self.name = na

#获取所有成员键值对
print(Foo.__dict__)
#获取所有成员的键
print(dir(Foo))

obj = Foo('laoliu')
print(obj.__dict__)
  •  列表排序
v = [11,22,334,4,2]
v.sort()
print(v)


v1 = [
    (11,'alex1'),
    (2,'alex2'),
    (2,'alex3'),
    (7,'alex4'),
]

#以元组的第一个元素排序
v1.sort(key=lambda x:x[0])
print(v1)

#以元组的第一个元素排序,如果相同,则以第二个元素排序
v1.sort(key=lambda x:(x[0],x[1]))
print(v1)
  •  __mro__ 按深度优先的关系,找到所有的父类
class F4(object):
    pass

class F3(F4):
    pass

class F2_5(object):
    pass

class F2(F2_5):
    pass

class F1(F2,F3):
    pass

print(F1.__mro__)

   接下来就开始我们快乐的源码分析之旅吧,let's go

 

  还是看到我们的示例代码中,代码从上往下解释class LoginForm(Form),会去执行创建LoginForm类里的__init__方法,那么创建LoginForm的类是哪个呢,你从父类中看到这句Form(with_metaclass(FormMeta, BaseForm)),就应该很快就能确定是FormMeta,而且FormMeta确实继承type类,所以创建LoginForm类时会去执行FormMeta里的__init__方法(如果这里还不懂,可以去看我的另一篇博客)

    def __init__(cls, name, bases, attrs):
        type.__init__(cls, name, bases, attrs)
        cls._unbound_fields = None
        cls._wtforms_meta = None

 

   在__init__方法里,给LoginForm类封装了两个字段_unbound_fields,_wtforms_meta,都为None

  代码继续往下解释,遇到LoginForm定义两个静态字段name,pwd,进行simple.StringField实例化操作,所以会去执行StringField的__init__方法,执行__init__方法前,它先执行__new__方法,StringField中没有,父类Field中有

    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)

   kwargs是 我们传入的参数,我们并没有传入了_form和_name参数,所以它会走else分支,最终name,pwd被赋值UnboundField对象,在这个对象中,封装了StringField类和我们实例的参数,所以创建LoginForm类,会是这个样子

print(LoginForm.__dict__)
LoginForm ={
	'__module__': '__main__', 
	'name': <1 UnboundField(StringField, (), {'label': '用户名', 'validators': [<wtforms.validators.DataRequired object at 0x00000000037DAEB8>, <wtforms.validators.Length object at 0x000000000382B048>], 'widget': <wtforms.widgets.core.TextInput object at 0x000000000382B080>, 'render_kw': {'class': 'form-control'}})>, 
	'pwd': <2 UnboundField(PasswordField, (), {'label': '密码', 'validators': [<wtforms.validators.DataRequired object at 0x000000000382B0F0>, <wtforms.validators.Length object at 0x000000000382B128>, <wtforms.validators.Regexp object at 0x000000000382B160>], 'widget': <wtforms.widgets.core.PasswordInput object at 0x000000000382B208>, 'render_kw': {'class': 'form-control'}})>, 
	'__doc__': None, 
	'_unbound_fields': None, 
	'_wtforms_meta': None
}

   你可能会想:怎么不直接实例StringField,而是实例一个UnboundField对象呢?UnboundField能够帮助我们排序,它里面维护了一个计数器,你可能又想了 干嘛要排序?你在前端渲染时,循环form时需要一直保持一个规定顺序,而在后端,我们在定义代码,是从上往下定义字段的,而获取时,使用字典方式获取的,字典是无序的,为了还保持我们定义的顺序,就用上面对象来做到这点

 

  到视图函数中,实例LoginForm(),会执行下面几个步骤

  1. 执行FormMeta的__call__方法
  2. 执行LoginForm的__new__方法
  3. 执行LoginForm的__init__方法

  首先看到__call__方法中的这段

        if cls._unbound_fields is None:  #此时还为None,会走这个分支
            fields = []
            for name in dir(cls):  #循环 类的成员所有键
                if not name.startswith('_'):  #排除带_成员,只剩name,pwd
                    unbound_field = getattr(cls, name)  #获取name和pwd对应的UnboundField对象
                    if hasattr(unbound_field, '_formfield'):  #UnboundField类中_formfield默认为True
                        fields.append((name, unbound_field))
             #进行计算器排序
            fields.sort(key=lambda x: (x[1].creation_counter, x[0]))
            cls._unbound_fields = fields  #最终赋给LoginForm的_unbound_fields 字段

   所以,此时_unbound_fields的存储结构为:

[
    (name, UnboundField对象(计算器,StringField类,参数)),
    (pwd, UnboundField对象(计算器,PasswordField类,参数))    
]

  我们在看到__call__方法里的这段代码

        if cls._wtforms_meta is None: #此时也是为None
            bases = []
            for mro_class in cls.__mro__:  #获取当前类的所有继承类
#我们写类里是没有Meta这个字段的,但是继承类Form中Meta = DefaultMeta if 'Meta' in mro_class.__dict__: bases.append(mro_class.Meta) #bases = [DefaultMeta]
       #实例Meta类,继承DefaultMeta,并赋给LoginForm的_wtforms_meta cls._wtforms_meta = type('Meta', tuple(bases), {})

   执行完__call__方法后,此时LoginForm长这样

        class Meta(DefaultMeta,):
            pass

        LoginForm ={
            '__module__': '__main__',
            'name': <2 UnboundField(StringField, (), {'label': '用户名', 'validators': [<wtforms.validators.DataRequired object at 0x00000000037DAEB8>, <wtforms.validators.Length object at 0x000000000382B048>], 'widget': <wtforms.widgets.core.TextInput object at 0x000000000382B080>, 'render_kw': {'class': 'form-control'}})>,
            'pwd': <1 UnboundField(PasswordField, (), {'label': '密码', 'validators': [<wtforms.validators.DataRequired object at 0x000000000382B0F0>, <wtforms.validators.Length object at 0x000000000382B128>, <wtforms.validators.Regexp object at 0x000000000382B160>], 'widget': <wtforms.widgets.core.PasswordInput object at 0x000000000382B208>, 'render_kw': {'class': 'form-control'}})>,
            '__doc__': None,
            '_unbound_fields': [
                (name, UnboundField对象(1,simple.StringField,参数)),
                (pwd, UnboundField对象(2,simple.PasswordField,参数)),
            ],
            '_wtforms_meta': Meta

    }

 

  由于LoginForm和父类中没有__new__方法,没有就不执行

   

  LoginForm中没有定义__init__方法,找到父类中Form的__init__方法执行

   看到这段代码,最后执行了又执行了父类BaseForm的__init__方法,并把_unbound_fields和meta_obj传了进入

        #实例Meta对象(主要用于生成csrf隐藏标签)
        meta_obj = self._wtforms_meta()
        if meta is not None and isinstance(meta, dict):
            meta_obj.update_values(meta)  #如果form里有设置meta,更新到meta_obj
        #执行父类中的__init__方法
        #_unbound_fields =  [
        #        (name, UnboundField对象(1,simple.StringField,参数)),
        #        (pwd, UnboundField对象(2,simple.PasswordField,参数)),
        #    ],
        super(Form, self).__init__(self._unbound_fields, meta=meta_obj, prefix=prefix)

   BaseForm的__init__方法这段代码,真正实例StringField对象和PasswordField是在这下面干的

        if meta.csrf:  #如果有设置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):
            #name  -->  name,pwd
            #unbound_field --> UnboundField对象(1,simple.StringField,参数)
            #               UnboundField对象(2,simple.PasswordField,参数)
            options = dict(name=name, prefix=prefix, translations=translations)
            field = meta.bind_field(self, unbound_field, options)
            #实例化Field对象后,把LoginForm对象中_fields下每个字段的UnboundField对象替换成Field对象
            self._fields[name] = field

   看到meta.bind_field,最终调用 了unbound_field.bind方法

    def bind(self, form, name, prefix='', translations=None, **kwargs):
        kw = dict(
            self.kwargs,
            _form=form,
            _prefix=prefix,
            _name=name,
            _translations=translations,
            **kwargs
        )
        #实例化Field对象,此时参数携带_form和_name,在__new__方法中直接实例Field对象,不再是UnboundField对象
        return self.field_class(*self.args, **kw) 

   执行BaseForm的__init__方法后,此时LoginForm对象的存储内容长这样

    form = {
        _fields: {
                name: StringField对象(),
                pwd: PasswordField对象(),
            }
    
        }
    

   再看到Form.__init__下的另外一段代码

        for name, field in iteritems(self._fields):
            setattr(self, name, field)  #把每个字段设置到对象中,方便支持obj.name,obj.pwd访问
        #为每个字段标签设置默认值和验证的值
        self.process(formdata, obj, data=data, **kwargs)

   所以此时的数据结构为

    form = {
        _fields: {
                name: StringField对象(),
                pwd: PasswordField对象(),
            }
        name:  StringField对象(widget=widgets.TextInput()),
        pwd:  PasswordField对象(widget=widgets.PasswordInput())
    
        }
    

   上面源码分析也只在get请求下,LoginForm不传值

 

  那如果走post,也是LoginForm传值,它又会怎么走呢?

  请求来的时候,它还是要先走实例LoginForm类的流程,这和get请求里的过程是一样的,唯一不同的,在执行LoginForm父类__init__方法时,self.process(formdata, obj, data=data, **kwargs)这里的formdata是有值的,然后看到process方法里的这段代码

        #循环所有的字段
        '''
                _fields: {
                name: StringField对象(),
                pwd: PasswordField对象(),
            }
        '''
        #formdata 前端传来的值  类似于{'name':alex,'pwd':123}
        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:  #kwargs 和 obj此时都没值,所以会走这个分支
                #field=StringField...
                #把前端的值 封装在StringField对象里,此时对象里有 要验证的值 和 正则
                field.process(formdata)

   大概都能猜到field.process(formdata)干了一件什么事,就把前端的值对应字段对象进行封装

        try:
            self.process_data(data)  #进行字段赋值操作
        except ValueError as e:
            self.process_errors.append(e.args[0])

        if formdata is not None:
            if self.name in formdata:
                self.raw_data = formdata.getlist(self.name)
            else:
                self.raw_data = []

            try:
                self.process_formdata(self.raw_data)  #也是进行字段赋值操作
            except ValueError as e:
                self.process_errors.append(e.args[0])

   

  传值form后,接下来的就是验证了,猜它都会循环每个字段,调用每个字段的验证方法

  接下来就看到LoginForm的验证方法

    def validate(self):
        extra = {}
        for name in self._fields:
            #获取每个字段的钩子函数 validate_name validate_pwd
            inline = getattr(self.__class__, 'validate_%s' % name, None)
            if inline is not None:
                extra[name] = [inline]
        '''
        前提是你有定义这么一个函数
        添加完后extra = {
            name : [validate_name],
            pwd : [validate_pwd]
        }
        '''
        return super(Form, self).validate(extra)

   而在执行父类里的验证方法里,就负责调用了每个字段里的验证方法

    def validate(self, extra_validators=None):
        self._errors = None
        success = True
        '''
        extra_validators = {
            name : [validate_name],
            pwd : [validate_pwd]
        }
        '''
        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()
            #调用字段的验证方法,并传入钩子函数,self为form对象,到时候用它里面的正则和要验证的值
            if not field.validate(self, extra):
                success = False
        return success

   在字段的验证方法中,有这么一段,调用了_run_validation_chain方法

        if not stop_validation:
            '''
            validators=[
                validators.DataRequired(message='用户名不能为空.'),
                validators.Length(min=6, max=18, message='用户名长度必须大于%(min)d且小于%(max)d')
            ]
            extra_validators=[钩子函数]
            '''
            chain = itertools.chain(self.validators, extra_validators)
            stop_validation = self._run_validation_chain(form, chain)

   执行_run_validation_chain

        #循环每条验证规则进行验证
        for validator in validators:
            try:
                #validator要么是钩子函数 要么是对象,对象则又去执行__call__方法
                validator(form, self)
            except StopValidation as e:
                if e.args and e.args[0]:
                    self.errors.append(e.args[0])
                return True
            except ValueError as e:
                self.errors.append(e.args[0])

        return False

 

 分析实例代码

#!/usr/bin/env python
# -*- coding:utf-8 -*-
from flask import Flask, render_template, request, redirect
from wtforms import Form
from wtforms.fields import simple
from wtforms import validators
from wtforms import widgets

app = Flask(__name__, template_folder='templates')

app.debug = True


# 1.由于 metaclass=FormMeta,所以LoginForm是由FormMeta创建
# 2. 执行 FormMeta.__init__
#       LoginForm._unbound_fields = None
#       LoginForm._wtforms_meta = None
# 3. 解释字段:
#       name = simple.StringField(...)
#       pwd = simple.PasswordField(...)
#    结果:
#       LoginForm.name = UnboundField(simple.StringField,StringField的所有参数)
#       LoginForm.pwd = UnboundField(simple.PasswordField,PasswordField的所有参数)

class LoginForm(Form):
    # 字段(内部包含正则表达式)
    name = simple.StringField(
        label='用户名',
        validators=[
            validators.DataRequired(message='用户名不能为空.'),
            validators.Length(min=6, max=18, message='用户名长度必须大于%(min)d且小于%(max)d')
        ],
        widget=widgets.TextInput(), # 页面上显示的插件
        render_kw={'class': 'form-control'}
    )
    # 字段(内部包含正则表达式)
    pwd = simple.PasswordField(
        label='密码',
        validators=[
            validators.DataRequired(message='密码不能为空.'),
            validators.Length(min=8, message='用户名长度必须大于%(min)d'),
            validators.Regexp(regex="^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[$@$!%*?&])[A-Za-z\d$@$!%*?&]{8,}",
                              message='密码至少8个字符,至少1个大写字母,1个小写字母,1个数字和1个特殊字符')

        ],
        widget=widgets.PasswordInput(),
        render_kw={'class': 'form-control'}
    )
    def validate_name(self,form):
        pass

"""
print(LoginForm.__dict__)
LoginForm ={
	'__module__': '__main__', 
	'name': <1 UnboundField(StringField, (), {'label': '用户名', 'validators': [<wtforms.validators.DataRequired object at 0x00000000037DAEB8>, <wtforms.validators.Length object at 0x000000000382B048>], 'widget': <wtforms.widgets.core.TextInput object at 0x000000000382B080>, 'render_kw': {'class': 'form-control'}})>, 
	'pwd': <2 UnboundField(PasswordField, (), {'label': '密码', 'validators': [<wtforms.validators.DataRequired object at 0x000000000382B0F0>, <wtforms.validators.Length object at 0x000000000382B128>, <wtforms.validators.Regexp object at 0x000000000382B160>], 'widget': <wtforms.widgets.core.PasswordInput object at 0x000000000382B208>, 'render_kw': {'class': 'form-control'}})>, 
	'__doc__': None, 
	'_unbound_fields': None, 
	'_wtforms_meta': None
}
"""

@app.route('/login', methods=['GET', 'POST'])
def login():
    # 实例LoginForm
    # 1. 执行FormMeta的__call__方法
    """
        class Meta(DefaultMeta,):
            pass

        LoginForm ={
            '__module__': '__main__',
            'name': <2 UnboundField(StringField, (), {'label': '用户名', 'validators': [<wtforms.validators.DataRequired object at 0x00000000037DAEB8>, <wtforms.validators.Length object at 0x000000000382B048>], 'widget': <wtforms.widgets.core.TextInput object at 0x000000000382B080>, 'render_kw': {'class': 'form-control'}})>,
            'pwd': <1 UnboundField(PasswordField, (), {'label': '密码', 'validators': [<wtforms.validators.DataRequired object at 0x000000000382B0F0>, <wtforms.validators.Length object at 0x000000000382B128>, <wtforms.validators.Regexp object at 0x000000000382B160>], 'widget': <wtforms.widgets.core.PasswordInput object at 0x000000000382B208>, 'render_kw': {'class': 'form-control'}})>,
            '__doc__': None,
            '_unbound_fields': [
                (name, UnboundField对象(1,simple.StringField,参数)),
                (pwd, UnboundField对象(2,simple.PasswordField,参数)),
            ],
            '_wtforms_meta': Meta

    }
    """
    # 2. 执行LoginForm的__new__方法
    #    pass
    # 3. 执行LoginForm的__init__方法
    """
     LoginForm ={
            '__module__': '__main__',
            'name': <2 UnboundField(StringField, (), {'label': '用户名', 'validators': [<wtforms.validators.DataRequired object at 0x00000000037DAEB8>, <wtforms.validators.Length object at 0x000000000382B048>], 'widget': <wtforms.widgets.core.TextInput object at 0x000000000382B080>, 'render_kw': {'class': 'form-control'}})>,
            'pwd': <1 UnboundField(PasswordField, (), {'label': '密码', 'validators': [<wtforms.validators.DataRequired object at 0x000000000382B0F0>, <wtforms.validators.Length object at 0x000000000382B128>, <wtforms.validators.Regexp object at 0x000000000382B160>], 'widget': <wtforms.widgets.core.PasswordInput object at 0x000000000382B208>, 'render_kw': {'class': 'form-control'}})>,
            '__doc__': None,
            '_unbound_fields': [
                (name, UnboundField对象(1,simple.StringField,参数)),
                (pwd, UnboundField对象(2,simple.PasswordField,参数)),
            ],
            '_wtforms_meta': Meta

        }
    form = {
        _fields: {
                name: StringField对象(),
                pwd: PasswordField对象(),
            }
        name:  StringField对象(widget=widgets.TextInput()),
        pwd:  PasswordField对象(widget=widgets.PasswordInput())
    
        }
    
    """
    if request.method == 'GET':

        form = LoginForm()
        # form._fields['name']
        # form.name = StringField对象()
        """
        1. StringField对象.__str__
        2. StringField对象.__call__
        3. meta.render_field(StringField对象,)
        4. StringField对象.widget(field, **render_kw)
        5. 插件.__call__()
        """
        print(form.name) #
        """
        0. Form.__iter__: 返回所有字段对象
            1. StringField对象.__str__
            2. StringField对象.__call__
            3. meta.render_field(StringField对象,)
            4. StringField对象.widget(field, **render_kw)
            5. 插件.__call__()
        """
        for item in form:
            # item是fields中的每一个字段
            print(item)

        return render_template('login.html',form=form)
    else:
        # 上述流程+
        # 从请求中获取每个值,再复制到到每个字段对象中
        """
         form = {
            _fields: {
                    name: StringField对象(data=你输入的用户名),
                    pwd: PasswordField对象(pwd=你输入的密码),
                }
            name:  StringField对象(widget=widgets.TextInput(data=你输入的用户名)),
            pwd:  PasswordField对象(widget=widgets.PasswordInput(pwd=你输入的密码))
        
            }
        """
        # 请求发过来的值
        form = LoginForm(formdata=request.form) # 值.getlist('name')

        # 实例:编辑
        # # 从数据库对象
        # form = LoginForm(obj='值') # 值.name/值.pwd
        #
        # # 字典 {}
        # form = LoginForm(data=request.form) # 值['name']

        # 1. 循环所有的字段
        # 2. 获取每个字段的钩子函数
        # 3. 为每个字段执行他的验证流程 字段.validate(钩子函数+内置验证规则)
        if form.validate():
            print(form.data)
        else:
            print(form.errors)


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

 

posted @ 2018-09-11 09:53  财经知识狂魔  阅读(670)  评论(0编辑  收藏  举报