这篇博客,我们就来细致的分析下,校验的一步步流程,包含局部钩子、全局钩子等
当我们使用继承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()
每一个元素的键值,每一个元素数据格式如下(分别为name
,field
)
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
方法进入自定义校验