django表单form的is_valid源码分析
一、is_valid的作用
验证表单数据是否正确,如果正确返回True,否则False
二、is_valid的验证流程
- 首先验证is_bound和errors,意思为如果表单有数据,且无错误,则返回True,否则 False.
- 我们分析self.errors方法,此方法返回一个错误字典,里面包含所有填入表单的错误信息,例如:少填了字段错误, 字段超长,值的格式不符等等。类似 {‘username’:[‘This field is required’], ‘sender’:[‘Enter a valid Email Adress.’] }
- 接下来是full_clean方法,作用是"清洗"表单中的数据,并把正确的数据放入self.cleaned_data字典,错误的放放self._errors中,具体实现是4,5,6三个方法
- self._clean_fields(),循环验证(field.clean方法验证)每个字段中的数据是否是合法的。我们可以看到在每一次循环的最后还设置了hook钩子,clean_字段(如果有的话!)来验证,所以你可以subclass来扩展自己的钩子函数
- self._clean_form(),这个方法实际上是调用self.clean钩子函数
- self._post_clean(),这个也是钩子函数
验证完成!
三、源码带注释
def is_valid(self):
"""Return True if the form has no errors, or False otherwise.
如果表单没有错误返回True,否则返回False
"""
# self.is_bound = data if data is not None or files is not None
#is_bound 只要前端往表单中填入了数据,is_bound就是True
#self.errors 只要self.errors中有值,那么not self.errors返回False,否则True
return self.is_bound and not self.errors
@property
def errors(self):
"""Return an ErrorDict for the data provided for the form.
如果填入表单中的值有错误,则返回一个错误字典
"""
if self._errors is None:
self.full_clean()
return self._errors
def full_clean(self):
"""
Clean all of self.data and populate self._errors and self.cleaned_data.
清洗填入表单中的数据,并把有错误的放入到self._errors字典中,合法的放入self.cleaned_data中。
"""
self._errors = ErrorDict()
if not self.is_bound: # Stop further processing.
return
self.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() #钩子方法
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:
# 如果字段是disabled禁用状态,则获取该字段的初始化的值
value = self.get_initial_for_field(field, name)
else:
# 否则获取该字段的的值,并赋值给value变量
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:
#这里是真正的验证字段的方法field.clean!!!,具体根据validators验证器规则来验证,还可以添加自己的验证器!具体为自己写个验证函数,然后将其放入到字段类的validators变量中。类似even_field = models.IntegerField(validators=[validate_even])
value = field.clean(value)
self.cleaned_data[name] = value
#每用field.clean验证完后,再寻找用户自定义的clean_字段的钩子函数继续验证!!
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)
四、一个自定义的hook钩子
class LoginForm(forms.Form):
Email = forms.EmailField(max_length=100)
# 如果填入的邮箱不是以@qq.com结尾的则报错
def clean_Email(self):
if not self.cleaned_data['username'].endswith('@qq.com'):
raise ValidationError('email address must be ends with @qq.com')
else:
return self.cleaned_data['Email']
五、总结
is_valid的验证顺序为:self._clean_fields(每验证完一个字段,再执行用户的clean_字段方法,如果有的话) --> self._clean_form --> self._post_clean