Django目录:https://blog.csdn.net/qq_41106844/article/details/105554082
我们在做钩子的源码解析之前,先分析一下forms组件使用的过程:
定义一个forms类
class UserForm(forms.Form):
name=forms.CharField(max_length=5)
pwd=forms.IntegerField()
email=forms.EmailField()
向类中传递数据
form=UserForm(request.POST)
校验
form.is_valid()
保存校验正确的数据的字典
form.cleaned_data
保存校验错误的数据的字典
form.errors
分析一下我们先定义好模型,在传递数据,在校验,最后好的数据放一起,坏的数据放一起。
也就是说,最核心的是校验。
我们从校验的函数入手:
注意:我们是从form.is_valid进入的函数,也就是说,self是UserForm。
def is_valid(self):
"""Return True if the form has no errors, or False otherwise."""
return self.is_bound and not self.errors
分析:
return 的返回值是一个布尔值,既当self.is_bound 与 not self.errors 都为True时,返回值为True,也就是校验成功,没有报错。
再进一步:
self.is_bound:
源码为
self.is_bound = data is not None or files is not None
他判断了两个事情,
第一个事情是 data is not None
第二个事情是 files is not None。
也就是只要这两个有一个为True, self.is_bound就为True。
而这两件事情都是再说:传入的数据不是空的就行。
not self.errors:
self.errors 是用来保存报错的数据,如果没有报错的数据,也就是 self.errors 为 空,则 not self.errors 为True。
源码为
@property
def errors(self):
"""Return an ErrorDict for the data provided for the form."""
if self._errors is None: #这里的errors 为 None
self.full_clean()
return self._errors
errors中执行了一个方法:self.full_clean()
再进一步:
self.full_clean():
源码为
def full_clean(self):
"""
Clean all of self.data and populate self._errors and self.cleaned_data.
"""
self._errors = ErrorDict() #拿到errors的初始变量。
if not self.is_bound: # Stop further processing.
return
self.cleaned_data = {} #拿到cleaned_data的初始变量
# If the form is permitted to be empty, and none of the form data has
# changed from the initial data, short circuit any validation.
if self.empty_permitted and not self.has_changed():
return
#核心的三个方法
self._clean_fields() #局部钩子
self._clean_form() #全局钩子
self._post_clean()
我们在full_clean里面拿到了 errors 和 cleaned_data 的初始变量。同时对我们传入的所有数据进行遍历校验,将正确的和错误的分别保存。
再进一步:
self._clean_fields(): 这里是真正干活的方法
def _clean_fields(self):
for name, field in self.fields.items(): #self.fields 是保存表单数据的变量。 self.fields.items()就是取出我们的表单数据按照k-y 准备遍历。
#name是字段字符串,field是规则对象(表单数据),例如传入用户名 alex,密码 123 这里的第一次循环就是 'name',name 第二次循环就是 'pwd',pwd
# 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))
# 不管残不残缺,最后获取的 value 就是我们 name代表的值 也就是 ‘alex’
try:
if isinstance(field, FileField): #因为我们的是CharField类型,执行else之后的。
initial = self.get_initial_for_field(field, name)
value = field.clean(value, initial)
else:
#这里是按照我们的规则对象校验我们的值
#我们定义的规则是长度不能超过5位, 显然我们的值符合规则。
#要是校验失败了,会抛出一个 ValidationError 异常,就会执行最后一句。
value = field.clean(value)
#校验通过的话,将name和value 以k-y的形式添加到 cleaned_data中。
self.cleaned_data[name] = value
#正常校验到上面就结束了,下面是我们的钩子。
#这里会去我们的类里面找 有没有 以clean_开头,以我们的name结尾的方法,这就是我们的自定义钩子方法。
if hasattr(self, 'clean_%s' % name):
#有的话,就提出我们的自定义钩子,然后执行以下。
value = getattr(self, 'clean_%s' % name)()
#同上面的意思,校验成功的添加进来,失败了跳转最后一句。
self.cleaned_data[name] = value
except ValidationError as e:
#将报错的name 与 相对应的错误信息添加到 errors 中。
self.add_error(name, e)
到这里为止,好像我们的源码结束了,但是仔细想一下:
假如我们正常校验过了,但是自定义钩子校验没过,这样的话cleaned_data和errors中都有我们的name信息,这显然是不符合常理的,接着我们来一下 self.add_error :
def add_error(self, field, error):
'''
这里删除了一些与我们分析无关的东西
'''
for field, error_list in error.items():
if field not in self.errors:
if field != NON_FIELD_ERRORS and field not in self.fields:
raise ValueError(
"'%s' has no field named '%s'." % (self.__class__.__name__, field))
if field == NON_FIELD_ERRORS:
self._errors[field] = self.error_class(error_class='nonfield')
else:
self._errors[field] = self.error_class()
self._errors[field].extend(error_list)
#在这里执行了一句,如果我们的规则对象也在cleaned_data,那就删掉他。
if field in self.cleaned_data:
del self.cleaned_data[field]
我们再过一下全局钩子的源码
def _clean_form(self):
try:
#看这一句的clean()方法
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
def clean(self):
"""
Hook for doing any extra form-wide cleaning after Field.clean() has been
called on every field. Any ValidationError raised by this method will
not be associated with a particular field; it will have a special-case
association with the field named '__all__'.
"""
#clean()方法直接把 cleaned_data 返回了回去。
return self.cleaned_data