标签归档:Flask-WTF

WTForms自定义验证方法(行内验证器)是如何被调用的?

这篇文章基于我在知乎上的这个回答,进行了相应的简化处理,放到这里做个备份。

万能的回答

答案在源码里。

简单的回答

WTForms会在你对表单实例调用Form.validate()方法时收集所有的行内验证方法,然后在对每个字段调用Field.validate()方法验证时将这些自定义行内验证方法一并和通过validators参数传入的验证器列表一起调用,进行验证。因为WTForms在调用时把对应的field作为参数传入了行内验证方法,所以你可以在自定义验证方法中通过field.data获取对应字段的数据。

深入的回答

WTForms会在你对表单实例调用Form.validate()方法时收集所有的行内验证方法。在Form类中的validate()方法定义中,你可以看到WTForms是如何收集这些行内验证方法的:

class Form(with_metaclass(FormMeta, BaseForm)): 
   ....
   def validate(self):
        extra = {}
        for name in self._fields:
            inline = getattr(self.__class__, 'validate_%s' % name, None)
            if inline is not None:
                extra[name] = [inline]

        return super(Form, self).validate(extra)

源码位置:github.com/wtforms/wtfo

这里迭代所有的字段属性,然后表单类中是否包含validate_字段名形式的方法。如果有,那么就添加到extra字段里,这个字段被传递到BaseForm类的validate()方法中。在BaseForm类的validate()方法中,WTForms迭代所有字段,并对每个字段调用Field.validate()方法验证字段,继续传入自定义的行内验证方法:

class BaseForm(object):
    ...
    def validate(self, extra_validators=None):
        self._errors = None
        success = True
        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()
            if not field.validate(self, extra):  # 调用字段类的验证方法进行验证,传入自定义验证器
                success = False
        return success

源码位置:github.com/wtforms/wtfo

而在字段基类Field的validate()方法中,WTForms使用itertool模块提供的chain()函数把你实例化字段类时传入的验证器(self.validators)和自定义行内验证器(extra_validators)连接到一起:

class Field(object):    
    ...
    def validate(self, form, extra_validators=tuple()):
        ...
        # Run validators
        if not stop_validation:
            chain = itertools.chain(self.validators, extra_validators)  # 合并两类验证器
            stop_validation = self._run_validation_chain(form, chain)  # 运行验证器
        ...

源码位置:github.com/wtforms/wtfo

连接起来的所有验证器赋值为chain变量,并传入到self._run_validation_chain(form, chain)进行进一步调用:

class Field(object):    
    ...
    def _run_validation_chain(self, form, validators):
        for validator in validators:
            try:
                validator(form, self)  # 传入字段类本身作为第二个参数

源码位置:github.com/wtforms/wtfo

这个方法迭代所有的验证器对字段数据进行验证。关键在于validator(form, self),可以看到这里传入了第二个参数self,即Field类本身,这也是为什么你可以在自定义验证方法中通过field.data获取当前字段数据。