这篇博客,我们就来细致的分析下,校验的一步步流程,包含局部钩子、全局钩子等

当我们使用继承forms.Form的类对象时候,我们通常会使用类似如下的代码块

    if request.method == "POST":
        registForm = RegistForm(request.POST)
        # 全部校验无误
        if registForm.is_valid():
            print("is_valid cleaned_data-------",registForm.cleaned_data)
            user = registForm.cleaned_data.get("user")
            pwd = registForm.cleaned_data.get("pwd")

            models.User.objects.create(name=user,pwd=pwd)
            return HttpResponse("ok")

 

那我们就从is_valid方法入手吧,该方法的代码如下:

    def is_valid(self):
        """
        Returns True if the form has no errors. Otherwise, False. If errors are
        being ignored, returns False.
        """
        return self.is_bound and not self.errors

 

is_bound变量是self.is_bound = data is not None or files is not None

意思是如果有数据可校验、或者errors方法返回的_errors,(算是某种定义格式的ErrorDict)不为空,就说明校验通过,去进行接下来的操作,诸如注册,登陆等 
以下就是ErrorDict的输出格式

errors------- <ul class="errorlist"><li>user<ul class="errorlist"><li>too short</li></ul></li><li>pwd<ul class="errorlist"><li>不能为空</li></ul></li><li>rePwd<ul class="errorlist"><li>This field is required.</li></ul></li><li>email<ul class="errorlist"><li>格式错误</li></ul></li><li>phone<ul class="errorlist"><li>手机号格式错误</li></ul></li></ul>

 

我们就看看errors方法是如何进行判断的

 @property
    def errors(self):
        "Returns an ErrorDict for the data provided for the form"
        if self._errors is None:
            self.full_clean()
        return self._errors

 

如果错误字典里面是空就去校验,如果不为空,就直接校验失败,我们接下来看下,是如何进行校验的 
self.full_clean()方法如下

    def full_clean(self):
        self._errors = ErrorDict()
        if not self.is_bound:  # Stop further processing.
            return
        self.cleaned_data = {}
        if self.empty_permitted and not self.has_changed():
            return

        self._clean_fields()
        self._clean_form()
        self._post_clean()

 

定义了2个字典_errors,cleaned_data存放校验错误、正确的字段 
self._clean_fields()方法中,

   def _clean_fields(self):
        for name, field in self.fields.items():
            # value_from_datadict() gets the data from the data dictionaries.
            # Each widget type knows how to retrieve its own data, because some
            # widgets split data over several HTML fields.
            if field.disabled:
                value = self.get_initial_for_field(field, name)
            else:
                value = field.widget.value_from_datadict(self.data, self.files, self.add_prefix(name))
            try:
                if isinstance(field, FileField):
                    initial = self.get_initial_for_field(field, name)
                    value = field.clean(value, initial)
                else:
                    value = field.clean(value)
                self.cleaned_data[name] = value
                if hasattr(self, 'clean_%s' % name):
                    value = getattr(self, 'clean_%s' % name)()
                    self.cleaned_data[name] = value
            except ValidationError as e:
                self.add_error(name, e)

 

以上代码通过遍历OrderDict数据self.fields.items()每一个元素的键值,每一个元素数据格式如下(分别为namefield)

user <django.forms.fields.CharField object at 0x06760130>

 

首先对disabled的可用性进行校验,需要特殊处理如下:

            if field.disabled:
                value = self.get_initial_for_field(field, name)
            else:
                value = field.widget.value_from_datadict(self.data, self.files, self.add_prefix(name))

 

意思就是将Form中的各个字段对应的,诸如input等输入值取出来 
然后进入到try语句块,对FileField类型进行了特殊处理initial = self.get_initial_for_field(field, name)这大概是文件可以反复选择,对数据源的选择做优先级处理吧,呵呵,这里是我猜的,最后校验完毕如果不抛异常就self.cleaned_data[name] = value加入到字典中 
在try代码块还有一个校验,是进行自定义校验的

                if hasattr(self, 'clean_%s' % name):
                    value = getattr(self, 'clean_%s' % name)()
                    self.cleaned_data[name] = value

 

上述代码是什么意思呢?是通过反射进行判断,在构造的Form类中,是否存在类似如下的代码

    def clean_phone(self):
        val = self.cleaned_data.get('phone')
        import re
        ret = re.search("1[356789]\d{9}$", val)
        if ret:
            return val
        else:
            raise ValidationError("手机号格式错误")

 

clean_phone方法可以通过'clean_%s' % name,进行格式化输出,所以对类似如此格式化的方法,进行再次校验我们自定义的规则,这里要注意一点,如果第一次通过各个field校验,在没有出错的前提下,此刻cleaned_data里面已经全部校验成功,然后如果我们通过clean_phone方法进行自定义校验时候,此刻可以从字典cleaned_data拿到第一次校验通过的self.cleaned_data.get('phone')的值,然后利用其在进行自定义校验,如果自定义成功,直接返回即可,当所有的控件都校验完毕通过,整个过程就校验通过

接下来我们说下校验失败的情况 
在遍历校验每个表单元素,如果校验失败会抛出ValidationError异常,不管是利用通过field.clean(value)还是在二次自定义校验,某一方失败的前提下,都会抛出异常, 
会进行self.add_error(name, e),具体如何添加的逻辑,这里就不去分析了 
另外field.clean(value)上篇博客已经讲过了,我接下来简单贴出代码即可

    def clean(self, value):
        value = self.to_python(value)
        self.validate(value)
        self.run_validators(value)
        return value

 

意思就是取出表单元素的值、进行是否为空校验、然后执行各自的校验规则

到此为止,full_clean方法self._clean_fields()执行完毕,我再次将full_clean方法贴出,方便查阅

    def full_clean(self):
        self._errors = ErrorDict()
        if not self.is_bound:  # Stop further processing.
            return
        self.cleaned_data = {}
        if self.empty_permitted and not self.has_changed():
            return

        self._clean_fields()
        self._clean_form()
        self._post_clean()

 

我们看下full_clean方法中的self._clean_form()方法做了什么操作

    def _clean_form(self):
        try:
            cleaned_data = self.clean()
        except ValidationError as e:
            self.add_error(None, e)
        else:
            if cleaned_data is not None:
                self.cleaned_data = cleaned_data

 

这个代码容易理解,意思就是将cleaned_data赋值操作 
最后我们看下full_clean方法中的self._post_clean()方法

    def _post_clean(self):
        """
        An internal hook for performing additional cleaning after form cleaning
        is complete. Used for model validation in model forms.
        """
        pass

 

咦?是个空方法,用百度翻译查了下 
内部清洗后进行额外清洗的内部钩。是完整的。用于模型形式的模型验证。

基本上校验逻辑处理完了

当全部校验通过

is_valid cleaned_data------- {'user': 'sdfdsf', 'pwd': 'qwe', 'rePwd': 'qwe', 'email': '2@q.com', 'phone': '13581922339'}

 

当密码field校验通过,但是2次输入不一致进入自定义校验时,以及用户名field校验通过,进入自定义校验时 
registForm.errors输出如下

errors------- --- <ul class="errorlist"><li>user<ul class="errorlist"><li>该用户已经注册</li></ul></li><li>__all__<ul class="errorlist nonfield"><li>两次密码不一致</li></ul></li></ul>

 

registForm.cleaned_data输出如下

is_not_valid clean_data {'pwd': 'werw', 'rePwd': 'sfdfsdf', 'email': '2@qq.com', 'phone': '13581922339'}

 

registForm.errors.get("__all__")输出如下:

__all__ <ul class="errorlist nonfield"><li>两次密码不一致</li></ul>

 

密码是否一致的自定义校验规则如下:

 def clean(self):
        print("clean")
        pwd = self.cleaned_data.get('pwd')
        repeat_pwd = self.cleaned_data.get('rePwd')

        if pwd == repeat_pwd:
            return self.cleaned_data
        else:
            raise ValidationError("两次密码不一致")

 

因为在_clean_form方法中,进行了赋值cleaned_data操作,该方法内部cleaned_data = self.clean()有个预留方法,可以在2个密码框通过自身field校验通过后,可以通过clean方法进入自定义校验

posted on 2018-04-08 21:29  Py行僧  阅读(149)  评论(0编辑  收藏  举报