Form组件的功能有校验输入内容和生成HTML代码,校验的一些基础功能,诸如:
- 自定义一个继承form.Form的类;
- 实例化类,传入前端
- 获得返回数据进行判断
obj.is_valid()
- 自定制输入的错误类型,返回给前端
这些基础校验只是在我们进行简单地input的时候进行使用的,然而真正的校验,应该是和数据库联系起来,如何才能校验用户输入的数据与数据库里的数据是否重复,诸如此类的检验呢?
django自然给我们提供了这些简单的接口,在基础功能中,我们校验成功(obj.is_valid()
判断成功)就会给我们返回obj.cleaned_data
,这个cleaned_data中便是我们可以下功夫的东西,顺着cleaned_data所在的文件寻找我们发现,django通过执行full_clean
方法里面的各种函数,来给我们返回cleaned_data这个字典:
def full_clean(self):
"""
Clean all of self.data and populate self._errors and 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()
首先会执行self._clean_fields()这个方法,我们再来看这个方法的代码:
def _clean_fields(self):
#循环我们自己定义的form类中的每一个字段
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)
#从HTML中获得数据,然后将值添加到cleaned_data这个字典中
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)
我们发现在给字典赋值这个操作是在try中的,如果出错则会返回错误信息,大概猜测,我们在自己定义的类中的错误信息是在这个excep中被赋值的。在try中,赋值完之后,又进行了一步if判断,
if hasattr(self, 'clean_%s' % name)
,在找了源码下面的方法发现并没有’clean_%s’这个方法,说明,这个if就是留给程序员进行扩展的接口,如果我们自己定义了这个clean_%s’方法,那么我们就可以对数据进一步校验并返回结果,那么刚才赋值的’cleaned_data只能算是一个临时的数据。
那么我们就可以在我们自己的form类中添加上这个’clean_%s’方法(注意,由于整个操作都在for循环中,所以这里的方法,一次只能针对一个字段进行校验,不能校验你的全部字段)
#定义clean方法,这里的name对应自己的字段名
def clean_name(self):
#首先我们要拿到之前临时传入字典里的那个值
res = self.cleaned_data['name']
#我们将这个值放入数据库中,进行查找,.count()方法,查看数据条数,如果只有一个返回1
if Members.objects.filter(name=res).count():
#如果返回了1说明我们这个用户名已经在数据库中存在了,我们就不能让用户输入,那么我们如何才能反馈错误呢,我们注意到之前有个except ValidationError,那么我们猜测只要我们在这里raise 这个错误,然后自定义错误加入error字典中,即可添加返回给用户额
raise ValidationError('用户名已经存在')
else:
#如果没有就可以返回这个数据
return res
这样我们再让用户注册时,就不能再注册相同的用户名了。当然不局限于查重这个功能,实现具体的方法,要看自己的需求。
上面这个方法只能针对单独字段进行校验,那么当我们需要校验form中的所有的值的时候,就不能用上面这个方法,当然从方法名字也能看出上面的方法是从_clean_fields中衍生出来的,_clean_fields顾名思义就是校验字段的。
再来看源码:
self._clean_fields()
self._clean_form()
self._post_clean()
在_clean_fields()后面还有2个函数需要执行,查找这2个函数:
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
def _post_clean(self):
"""
An internal hook for performing additional cleaning after form cleaning
is complete. Used for model validation in model forms.
"""
pass
我们发现_clean_form()是一个半定制方法,而_post_clean()是一个全部需要自己定制的方法,在一般情况下,在能力不够的情况下,不要轻易修改源码,我们使用_clean_form()就足够了,在这个方法中,try了一个self.clean()的方法,将值赋给了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__'.
"""
return self.cleaned_data
除了给了一个返回的cleaned_data其他也需要定制,那么我们就可以将这个方法,加到我们自己的类里去了:
def clean(self):
valuedict = self.cleaned_data
name=valuedict.get('name')
height=valuedict.get('height')
if name == 'abc' and height == 180:
raise ValidationError('出错')
else:
return valuedict
这里需要特别注意一个问题,当时在这里,我用另一种写法,如果上面的校验出现了错误,那么整个网页就会报错,我的第一种写法是:
name = self.cleaned_data['name']
height = self.cleaned_data['height']
print(name == 'abc', height == 180)
if name == 'abc' and height == 180:
raise ValidationError('这个名字已经被锁定了。')
else:
print('ok')
return self.cleaned_data
看似这2个写法并没有什么问题,实则差距很大,在python中取字典中的值的时候一般会有两种可选的方法——get() 方法和 [key] 方法,一般来说没什么太大区别,但是在取没有在字典中出现的值时,用get方法不会报错,会返回一个none,而[key]方法,会直接报错:KeyError,中止代码。如果你在校验字段时,已经raise出错误,那么cleaned_data这个字典里不会出现这个字段名,如果这时候到了这一步,用[key]取值,由于没有这个字段就会报错,导致程序中止,而用get只会返回一个None,程序会继续运行,一个小细节问题。
以上就是对Form的自定义校验的方法!