# WTForms

WTForms是一个支持多个web框架的form组件,主要用于对用户请求数据进行验证

# 登陆注册实例

  • 与Django的表单验证的过程类似

    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
    
    #相当于django中的form组件,用于进行表单和数据验证
    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 = html5.EmailField() # 可以使用html5的验证方式
        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()
    
  • 对应的login.html文件

    </html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
    <h1>登录</h1>
    <form method="post" novalidate>
        <p>{{form.name.label}} {{form.name}} {{form.name.errors[0] }}</p>
    
        <p>{{form.pwd.label}} {{form.pwd}} {{form.pwd.errors[0] }}</p>
        <input type="submit" value="提交">
    </form>
    </body>
    </html>
    

# 注册的简单使用

  • 在WTForms中也存在数据验证等操作

    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 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'}
        )
    
        pwd_confirm = simple.PasswordField(
            label='重复密码',
            validators=[
                validators.DataRequired(message='重复密码不能为空.'),
                #内部校验器:与哪一个字段要一直,比django自带的要简单一些,否则还要使用钩子
                validators.EqualTo('pwd', message="两次密码输入不一致")
            ],
            widget=widgets.PasswordInput(),
            render_kw={'class': 'form-control'}
        )
    
        #email在html5中
        email = html5.EmailField(
            label='邮箱',
            validators=[
                validators.DataRequired(message='邮箱不能为空.'),
                validators.Email(message='邮箱格式错误')
            ],
            widget=widgets.TextInput(input_type='email'),
            render_kw={'class': 'form-control'}
        )
    
        # 单选框在core中
        gender = core.RadioField(
            label='性别',
            choices=(
                (1, '男'),
                (2, '女'),
            ),
            #将用户提交过来的'1' 转换为 1
            coerce=int
        )
        city = core.SelectField(
            label='城市',
            choices=(
                ('bj', '北京'),
                ('sh', '上海'),
            )
        )
    
        hobby = core.SelectMultipleField(
            label='爱好',
            choices=(
                (1, '篮球'),
                (2, '足球'),
            ),
            coerce=int
        )
    
        favor = core.SelectMultipleField(
            label='喜好',
            choices=(
                (1, '篮球'),
                (2, '足球'),
            ),
            #多选
            widget=widgets.ListWidget(prefix_label=False),
            option_widget=widgets.CheckboxInput(),
            coerce=int,
            default=[1, 2]
        )
    
        # 重写字段,因为如所写,所有的字段都是静态的,不能根据数据库同步,所以每次实例化类数据的时候都要初始化一下
        def __init__(self, *args, **kwargs):
            super(RegisterForm, self).__init__(*args, **kwargs)
            # 原来只有俩个现在页面显示3个,当然也可以去数据库中拿,使数据动态起来
            self.favor.choices = ((1, '篮球'), (2, '足球'), (3, '羽毛球'))
    
        #定制钩子函数,使用validate_字段名
        def validate_pwd_confirm(self, field):
            """
            自定义pwd_confirm字段规则,例:与pwd字段是否一致
            :param field:
            :return:
            """
            # 最开始初始化时,self.data中已经有所有的值
    
            if field.data != self.data['pwd']:
                # raise validators.ValidationError("密码不一致") # 继续后续验证
                raise validators.StopValidation("密码不一致")  # 不再继续后续验证
    
    @app.route('/register', methods=['GET', 'POST'])
    def register():
        if request.method == 'GET':
            #data是传入的默认值,表示gender字段默认选中1,来实例化form
            form = RegisterForm(data={'gender': 1,'hobby':[1,2]})
            return render_template('register.html', form=form)
        else:
            # 使用formdata接收数据
            form = RegisterForm(formdata=request.form)
            if form.validate():
                print('用户提交数据通过格式验证,提交的值为:', form.data)
            else:
                print(form.errors)
            return render_template('register.html', form=form)
    
    if __name__ == '__main__':
        app.run()
    
  • register.html文件,可遵循for循环

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
    <h1>用户注册</h1>
    <form method="post" novalidate style="padding:0  50px">
        {% for item in form %}
        <p>{{item.label}}: {{item}} {{item.errors[0] }}</p>
        {% endfor %}
        <input type="submit" value="提交">
    </form>
    </body>
    </html>
    

# WTForms 组件

# 实现方式
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'}
    )

​ 这里我们以上面的继承Form类的类为例,LoginForm类是Form的子类,如果我们要实例化LoginForm成对象form,那么,LoginForm中的name、pwd又是字段类的对象,即实例化的form将也拥有name、pwd字段对象,在进行渲染时触发了form的for循环,按理说对象是没有for循环机制的,除非类中实现了 __iter__, 除了这个内置方法还有很多方法比如:

@app.route('/login', methods=['GET', 'POST'])
def login():
    #情景一:
	obj = LoginForm()
	print(obj.name)  #也就是说触发了__str__方法,相应的会打印出 <input />,相当于前端的form.name效果
    #<input class="form-control" id="name" name="name" required type="text" value="">
    
    #情景二:
    for item in obj:  #相当于触发了__iter__
        print(item)  #打印字段对象,触发__str__
    #<input class="form-control" id="name" name="name" required type="text" value="">
	#<input class="form-control" id="pwd" name="pwd" required type="password" value="">
    
    if request.method == 'GET':
        form = LoginForm()
        return render_template('login.html', form=form)
  • # 演示类层层包裹
    class InputText:
    
        def __str__(self):
            return "<input type='type'/>"
    
    class InputEmail: #插件
    
        def __str__(self):
            return "<input type='email'/>"
    
    class StringField:  #字段类
        def __init__(self,wg=InputText()):
            self.wg = wg
    
        def __str__(self):
            # 此str可以触发自身的,还可以继承上一个
            # return 'xxx'
    
            return str(self.wg)  # 自动触发上一级的__str__
    
    class LoginForm:  #form类
        a = StringField()
        b = StringField(wg=InputEmail())
    
    c = LoginForm()
    print(c.a) #<input type='type'/>
    print(c.b) #<input type='email'/>
    
  • # WTForms中Form类中字段源码部分

    我们以StringFields为例,其他的也是类似的,点击看源码

    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 ''
    

    当模板中使用了form.name时(name假如时StringField的对象),那么就会触发StringField的 __str__ 方法,找父类Field的 __str__ 方法

        def __str__(self):
            """
            Returns a HTML representation of the field. For more powerful rendering,
            see the `__call__` method.
            """
            return self()  #触发Field的__call__方法
        
        def __call__(self, **kwargs):
            """
            Render this field as HTML, using keyword args as additional attributes.
    
            This delegates rendering to
            :meth:`meta.render_field <wtforms.meta.DefaultMeta.render_field>`
            whose default behavior is to call the field's widget, passing any
            keyword arguments from this call along to the widget.
    
            In all of the WTForms HTML widgets, keyword arguments are turned to
            HTML attributes, though in theory a widget is free to do anything it
            wants with the supplied keyword arguments, and widgets don't have to
            even do anything related to HTML.
            """
            #这里的meta,也就是我们在执行FormMeta(type)的__call__方法时,通过查找继承类的Meta属性,在Form --> Meta = DefaultMeta,找到的类,并将实例化的结果赋值给self.meta
            return self.meta.render_field(self, kwargs) #这里的self是StringField
    
  
调用render_field(self, kwargs),将触发各自字段插件的call方法
  
  ```python
      def render_field(self, field, render_kw): #field为传入的StringField对象,包含里面的插件
          """
          render_field allows customization of how widget rendering is done.
  
          The default implementation calls ``field.widget(field, **render_kw)``
          """
          other_kw = getattr(field, 'render_kw', None)
          if other_kw is not None:
              render_kw = dict(other_kw, **render_kw)
          return field.widget(field, **render_kw)   #触发插件的call方法,这里是TextInput,因为widget本身已经是对象了,加括号就要执行call方法

查看插件TextInput的__call__方法

class TextInput(Input):
    """
    Render a single-line text input.
    """
    input_type = 'text'
    
 #打开Input类的call方法:
    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))
# WTForms中的校验
  • 后台定义好正则规则,生成HTML --> 插件

  • 对数据进行校验 --> 字段

  • Form的继承类 --> 用来统一管理全部字段格式

    class InputText:  #生成HTML
        def __str__(self):
            return "<input type='type'/>"
    
    class InputEmail:
        def __str__(self):
            return "<input type='email'/>"
    
    class StringField:  #数据校验
        def __init__(self,reg,wg=InputText()):
            self.wg = wg
            self.reg = reg
    
        def __str__(self):
            return str(self.wg)  # 自动触发上一级的__str__
    
        def valid(self,val):
            import re
            # 自定义字段验证
            return re.match(val,self.reg)
    
    class LoginForm:   #统一管理字段
        a = StringField(reg="\d+")
        b = StringField(reg='\w+', wg=InputEmail())
    
        def __init__(self,formdata):
            # 前端传入的数据,"用户发来的所有数据{user:'df',pwd:'sdf'}"
            self.formdata =formdata
    
        def validate(self):
            fields = {'a':self.a,'b':self.b}
            for name, field in fields.items():  #调用每个字段的校验规则
                #name 是字段,field 是字段对应的插件对象 a = StringField(reg="\d+")
    
                #从前端获取的数据中取出对应字段的值,前提是要求用户提供的数据的key 要与 字段保持一致 a {'a':'xx'}
                #self.formdata.get(name)
                field.valid(self.formdata.get(name))  #可根据各字段的规则校验值,校验每个字段的值是否合乎规则
    
    c = LoginForm(formdata=request.form)
    
    • 我们可以使用 ___dict__ 来查看LoginForm类有那些属性

      print(LoginForm.__dict__)
      
       {
      	'__module__': '__main__',
      	'name': < UnboundField(StringField, (), {
      		'label': '用户名',
      		'validators': [ < wtforms.validators.DataRequired object at 0x000001AE6CB1BF98 > , < wtforms.validators.Length object at 0x000001AE6CB2E3C8 > ],
      		'widget': < wtforms.widgets.core.TextInput object at 0x000001AE6CB2E198 > ,
      		'render_kw': {
      			'class': 'form-control'
      		}
      	}) > ,
      	'pwd': < UnboundField(PasswordField, (), {
      		'label': '密码',
      		'validators': [ < wtforms.validators.DataRequired object at 0x000001AE6CB2E2E8 > , < wtforms.validators.Length object at 0x000001AE6CB2EE80 > , < wtforms.validators.Regexp object at 0x000001AE6CB2EF98 > ],
      		'widget': < wtforms.widgets.core.PasswordInput object at 0x000001AE6CB2EFD0 > ,
      		'render_kw': {
      			'class': 'form-control'
      		}
      	}) > ,
      	'__doc__': None,
      	'_unbound_fields': None,
      	'_wtforms_meta': None
      }
      

# WTForms的源码实现

还是以登陆例子开头

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

 #由于 metaclass=FormMeta,所以LoginForm是由FormMeta创建,执行init方法
 #1、在init方法中生成了 LoginForm._unbound_fields=None
 #				     LoginForm._wtforms_meta = None
    
 #2、解释字段:
 #		name=simple.StringField()
 #   	pwd =simple.StringField()
 #  结果:name = UnboundField(cls,*args,**kwargs)
 #   	 pwd = UnboundField(cls,*args,**kwargs)  相当于将cls=simple.StringField, StringFiel的参数传入
class LoginForm(Form):
    #3、执行StringField的new方法,本身没有就去找父类Field,在实例化之前先执行__new__、再执行__init__
    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':
        #实列LoginForm
        #执行LoginForm的__new__方法,显然没有
        #1、执行FormMeta的__call__方法,因为LoginForm是Form的继承类,而FormMeta是元类,先会触发__call__默认
        	#返回的数据类型:
           """
               fields=[
                    (name,UnboundField对象), 
                    (pwd,UnboundField对象), 
               ]
           """    
        #2、执行LoginForm的__init__方法
            #返回的数据类型:
                #form = {
                #    _fields:{
                #        name:StringField对象(widget=widget.TextInput()),
                #        pwd:Password对象,
                #    },
                #    name:StringField对象,
                #    pwd:Password对象,
                #}
        form = LoginForm()
        return render_template('login.html', form=form)
    else:
        #提交数据后再次实例化
        #依旧的执行FormMeta的__call__方法,再执行LoginForm的__init__方法,
        #但是这里主要多了一步:process()
        form = LoginForm(formdata=request.form)  # 请求传过来给formdata,值.getlist('name')
        #form = LoginForm(obj='值')  #传数据库对象ORM,在LoginForm类中可以 值.name/值.pwd 
        #form = LoginForm(data={})  #可传递一个字典过来 值['name']
        
        #1、循环所有的字段,为每一个字段执行验证流程
        #2、获取每个字段的钩子函数,为每个字段执行他的验证流程,字段.validate(钩子函数)
        if form.validate():
            print('用户提交数据通过格式验证,提交的值为:', form.data)
        else:
            print(form.errors)
        return render_template('login.html', form=form)
 
if __name__ == '__main__':
    app.run()
  • 首先,我们知道LoginForm类是继承的Form,所以我们先打开Form的源码部分,在打开Form之前必须执行with_metaclass函数,所以我们先查看with_metaclass函数

    def with_metaclass(meta, base=object):
        return meta("NewBase", (base,), {})  #meta也就是下面传入的FormMeta类,从而触发它的call方法
    

    查看FormMeta类的call方法,和init方法

    class FormMeta(type): 
        # 最先执行FormMeta的init方法
        def __init__(cls, name, bases, attrs):
            type.__init__(cls, name, bases, attrs)
            cls._unbound_fields = None
            cls._wtforms_meta = None
            
        #触发了继承类的new方法后执行call,即实例化时第一步执行,本身
        def __call__(cls, *args, **kwargs):
            if cls._unbound_fields is None: #第一次显然为空,初始化就为空
                
                fields = []
                #取出LoginForm类中__dict__的所有key:'__moudlue__','name','pwd','_unbound_field'...
                for name in dir(cls):
                    # 排除以_开头的
                    if not name.startswith('_'):
                        unbound_field = getattr(cls, name)  #从类中获得字段,也就是__dict__中的每个字段值
                        if hasattr(unbound_field, '_formfield'):#UnboundField开头将其设置为True
                            fields.append((name, unbound_field))  #添加为元组在fields中
               """
               fields=[
               		(name,UnboundField对象), 
             ]
               """     
      		# 根据计数器排序,元组排序,如果数字相同,就以第二个数字排序,如x[1],x[0]
                fields.sort(key=lambda x: (x[1].creation_counter, x[0]))
                cls._unbound_fields = fields  # 实例化之后,_unbound_fields不为空了
    
            # Create a subclass of the 'class Meta' using all the ancestors.
            if cls._wtforms_meta is None:
                bases = []
                #查找继承的类,钻石继承
                #LoginForm
                #Form --> Meta = DefaultMeta, 存在Meta属性
                #BaseForm
                for mro_class in cls.__mro__:
                    #查找每个继承的类中是否有 'Meta' 属性
                    if 'Meta' in mro_class.__dict__:
                        #bases=[DefaultMeta,]
                        bases.append(mro_class.Meta)
                        
                 '''
                 class Meta(DefalutMeta):
                 	pass
                 '''
                cls._wtforms_meta = type('Meta', tuple(bases), {}) #cls.wtform_meta也有值了
            return type.__call__(cls, *args, **kwargs) #执行type.__call__触发本身的init方法
    

    Form的源码的部分

    class Form(with_metaclass(FormMeta, BaseForm)):
        Meta = DefaultMeta
      
        #执行完FormMeta的__call__方法后,执行此初始化函数
        def __init__(self, formdata=None, obj=None, prefix='', data=None, meta=None, **kwargs):
            #self._wtforms_meta 为继承DefaultMeta类的Meta类,有CSRF生成input框的功能 
            meta_obj = self._wtforms_meta()
            
            #添加meta信息:或者传入meta信息
            #如,我们写了: class Meta:
            # 				 csrf = True
            if meta is not None and isinstance(meta, dict):
                meta_obj.update_values(meta) #将信息添加进去
            
            """
               _unbound_fields=[
               		(name,UnboundField对象), 
              	 ]
               meta:Meta()
            """   
            #执行父类的构造方法,触发的时BaseForm的__init__函数,元类的init函数已经在初始化时用来生成类了,返回self.field
            super(Form, self).__init__(self._unbound_fields, meta=meta_obj, prefix=prefix)
    
            for name, field in iteritems(self._fields):
    			#给对象赋值,因为目前要想取值只能:form._fields['name'],我们只想form.name就能取值
                #循环:form.name = field
                setattr(self, name, field)
             
            # 为每个字段设置默认值,以及提供验证的值
            self.process(formdata, obj, data=data, **kwargs)
        ...
            
        def validate(self):
            """
            Validates the form by calling `validate` on each field, passing any
            extra `Form.validate_<fieldname>` validators to the field validator.
            """
            extra = {}
            #循环每个字段
            for name in self._fields:
                inline = getattr(self.__class__, 'validate_%s' % name, None)
                if inline is not None:
                    extra[name] = [inline]
    
  • 触发的BaseForm的__init__函数

    1、查看BaseForm
    class BaseForm(object):
        """
        Base Form Class.  Provides core behaviour like field construction,
        validation, and data and error proxying.
        """
        def __init__(self, fields, prefix='', meta=DefaultMeta()):
            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)
                #添加一个隐藏的字段在fields中
                extra_fields.extend(self._csrf.setup_form(self))
    		
            #chain链条函数将fields字段和刚添加的字段进行拼接,然后进行for循环
            for name, unbound_field in itertools.chain(fields, extra_fields):
                #将字段名等用工厂化字典做成option参数
                options = dict(name=name, prefix=prefix, translations=translations)
                # 调用meta的bind_field方法,在这里猜得到真正的实例化
                field = meta.bind_field(self, unbound_field, options)
                # 放回到BaseForm的对象的self._fields中
                self._fields[name] = field
                
     2、点击bind_field() 函数
    	def bind_field(self, form, unbound_field, options):
            #这里的unbound_field为我们传入的字段对象,如StringField的bind函数执行
            return unbound_field.bind(form=form, **options)
        
    3、在StringField中的父类的bind的函数
    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
            )
            #field_class这里可以是具体的字段,如StringField、等,在这里实例化,这时候下去执行具体的init函数
            return self.field_class(*self.args, **kw)
    
    
  • 以 StringField 字段为例,在我们实例化之前,先执行继承的Field的__new__ 方法、再执行__init__

    class Field:
         ...
        def __new__(cls, *args, **kwargs):
            if '_form' in kwargs and '_name' in kwargs:
                # 如果有以上的这两个参数,才会返回Field的对象
                return super(Field, cls).__new__(cls)
            else:
                #一般情况下返回的是,UnboundField对象,将StringField传入UnboundField多封装了一层
                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):
            if _translations is not None:
                self._translations = _translations
    
            if _meta is not None:
                self.meta = _meta
            elif _form is not None:
                self.meta = _form.meta
            else:
                raise TypeError("Must provide one of _form or _meta")
    
            self.default = default
            self.description = description
            self.render_kw = render_kw
            self.filters = filters
            self.flags = Flags()
            self.name = _prefix + _name
            self.short_name = _name
            self.type = type(self).__name__
            self.validators = validators or list(self.validators)
    
            self.id = id or self.name
            self.label = Label(self.id, label if label is not None else self.gettext(_name.replace('_', ' ').title()))
    
            if widget is not None:
                self.widget = widget
    
            for v in itertools.chain(self.validators, [self.widget]):
                flags = getattr(v, 'field_flags', ())
                for f in flags:
                    setattr(self.flags, f, True)
    
    

    UnboundField的源码和作用:由于传入的字段如:name、pwd,在前端使用的时候,是有顺序的,否则每次form的的输出顺序就会出错,那么如何实现字段的有序性?UnboundField在源码中封装了一个计数器‘’,标记每个字段的顺序

    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  #将计数的值给每个字段
    

# Form的提交数据校验

  • 和之前的LoginForm的实例化类似,主要是多出一步process函数,在class Form(with_metaclass(FormMeta, BaseForm))的init中,点击查看源码(继承的BaseForm)

     def process(self, formdata=None, obj=None, data=None, **kwargs):
            """
            Take form, object data, and keyword arg input and have the fields
            process them.
    
            :param formdata:
                Used to pass data coming from the enduser, usually `request.POST` or
                equivalent.
            :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 data:
                If provided, must be a dictionary of data. This is only used if
                `formdata` is empty or not provided and `obj` does not contain
                an attribute named the same as the field.
            :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.
            """
            #获取request.POST数据,解包后:formdata={'name':'xxx','pwd':123}
            formdata = self.meta.wrap_formdata(self, formdata)
    
            if data is not None:
                # XXX we want to eventually process 'data' as a new entity.
                #     Temporarily, this can simply be merged with kwargs.
                kwargs = dict(data, **kwargs)
    		
            # 循环所有的字段
            for name, field, in iteritems(self._fields):
                #obj 传值?
                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:
                    #自行各自字段的process方法,把自己对应的值放到自己数据中,处理完后每个字段的对应的对象中,再添加一个数据
                    field.process(formdata)
                    
                  #返回的数据类型:
                    #form = {
                    #    _fields:{
                    #        name:StringField对象(widget=widget.TextInput(name='你输入的用户名'),),
                    #        pwd:Password对象(widget=widget.TextInput(pwd ='密码'),),
                    #    },
                    #    name:StringField对象,
                    #    pwd:Password对象,
                    #}
    
  • # 执行validate函数
      	def validate(self):
            """
            Validates the form by calling `validate` on each field, passing any
            extra `Form.validate_<fieldname>` validators to the field validator.
            """
            extra = {}
             '''
               _fields:{
                   name:StringField对象(widget=widget.TextInput(name='你输入的用户名'),),
                   pwd:Password对象(widget=widget.TextInput(pwd ='密码'),),
                 }
            '''
            for name in self._fields:
                #name --> validate_name
                #pwd --> validate_pwd
                #去每个字段拿钩子函数
                inline = getattr(self.__class__, 'validate_%s' % name, None)
                if inline is not None:
                    #有钩子函数就放进extra中
                    extra[name] = [inline]
    
            return super(Form, self).validate(extra) #传入extra字典,触发BaseForm的validate函数
        
    2、查看BaseForm的validate函数
        def validate(self, extra_validators=None):
            """
            Validates the form by calling `validate` on each field.
    
            :param extra_validators:
                If provided, is a dict mapping field names to a sequence of
                callables which will be passed as extra validators to the field's
                `validate` method.
    
            Returns `True` if no errors occur.
            """
            #extra_validators = {name:[钩子函数,],pwd:[钩子函数,]}
            self._errors = None
            success = True
            #取出每个字段
            #name=name  field=StringField对象
            for name, field in iteritems(self._fields):
                if extra_validators is not None and name in extra_validators:
                    #赋值给extra将钩子函数
                    extra = extra_validators[name]
                else:
                    #没有就是一个空元组
                    extra = tuple()
                 #将钩子函数传入每个字段的validate中,
                if not field.validate(self, extra):
                    success = False
            return success
    
    3、最后在每个字段的validate方法中执行所有的验证规则,以StringField为例,其实所有的字段类都是继承Feild类,在其中执行的validate校验
    
      def validate(self, form, extra_validators=tuple()):#传入的钩子函数
            """
            Validates the field and returns True or False. `self.errors` will
            contain any errors raised during validation. This is usually only
            called by `Form.validate`.
    
            Subfields shouldn't override this, but rather override either
            `pre_validate`, `post_validate` or both, depending on needs.
    
            :param form: The form the field belongs to.
            :param extra_validators: A sequence of extra validators to run.
            """
            #extra_validators=[钩子函数,]
            self.errors = list(self.process_errors)
            stop_validation = False
    
            try:
                self.pre_validate(form)
            except StopValidation as e:
                if e.args and e.args[0]:
                    self.errors.append(e.args[0])
                stop_validation = True
            except ValueError as e:
                self.errors.append(e.args[0])
    
            # Run validators
            if not stop_validation:
                #self.validators是内置校验函数,存在自身中,extra_validators是自定义的钩子函数,链条函数
                chain = itertools.chain(self.validators, extra_validators)
                stop_validation = self._run_validation_chain(form, chain)
    
            # Call post_validate
            try:
                self.post_validate(form, stop_validation)
            except ValueError as e:
                self.errors.append(e.args[0])
    
            return len(self.errors) == 0
        
       # 执行每个字段的校验器,如果报错,返回False,下个字段不会再进行验证
      def _run_validation_chain(self, form, validators):
            """
            Run a validation chain, stopping if any validator raises StopValidation.
            """
            #validators = [
                #validators.DataRequired(message='用户名不能为空'),  #执行DataRequired的call方法
                #钩子函数,
            #]
            for validator in validators:
                try:            
                    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    
    
  • # WTForms 给每个字段进行验证,也给用户预留了扩展的函数,相当于信号
    字段的pre_validate 【预留的扩展】
    字段的_run_validation_chain,对正则和字段的钩子函数进行校验
    字段的post_validate【预留的扩展】
    

# Meta的使用

  • 使用Meta类可以定制自己的Form类

    #!/usr/bin/env python
    # -*- coding:utf-8 -*-
    from flask import Flask, render_template, request, redirect, session
    from wtforms import Form
    from wtforms.csrf.core import CSRF
    from wtforms.fields import core
    from wtforms.fields import html5
    from wtforms.fields import simple
    from wtforms import validators
    from wtforms import widgets
    from hashlib import md5
    
    app = Flask(__name__, template_folder='templates')
    app.debug = True
    
    
    class MyCSRF(CSRF):
        """
        Generate a CSRF token based on the user's IP. I am probably not very
        secure, so don't use me.
        """
        def setup_form(self, form):
            self.csrf_context = form.meta.csrf_context()
            self.csrf_secret = form.meta.csrf_secret
            return super(MyCSRF, self).setup_form(form)
    
        def generate_csrf_token(self, csrf_token):
            gid = self.csrf_secret + self.csrf_context
            token = md5(gid.encode('utf-8')).hexdigest()
            return token
    
        def validate_csrf_token(self, form, field):
            print(field.data, field.current_token)
            if field.data != field.current_token:
                raise ValueError('Invalid CSRF')
    
    class TestForm(Form):
        name = html5.EmailField(label='用户名')
        pwd = simple.StringField(label='密码')
    
        class Meta:
            # -- CSRF
            # 是否自动生成CSRF标签
            csrf = True
            # 生成CSRF标签name
            csrf_field_name = 'csrf_token'
    
            # 自动生成标签的值,加密用的csrf_secret
            csrf_secret = 'xxxxxx'
            # 自动生成标签的值,加密用的csrf_context
            csrf_context = lambda x: request.url
            # 生成和比较csrf标签
            csrf_class = MyCSRF
    
            # -- i18n
            # 是否支持本地化
            # locales = False
            locales = ('zh', 'en')
            # 是否对本地化进行缓存
            cache_translations = True
            # 保存本地化缓存信息的字段
            translations_cache = {}
    
    
    @app.route('/index/', methods=['GET', 'POST'])
    def index():
        if request.method == 'GET':
            form = TestForm()
        else:
            form = TestForm(formdata=request.form)
            if form.validate():
                print(form)
        return render_template('index.html', form=form)
    
    if __name__ == '__main__':
        app.run()