Django表单验证
- 普通表单验证;
- 模型表单验证;
一、普通表单验证
普通表单验证其实就是针对不同的Field字段进行针对性的验证。一般都是分为几个步骤:
- Field的to_python方法;表单value转换为python对象,每个Field子类都重构了该方法;
- Field的validate方法;对于这个方法,官网给出的说法是: 当你遇到不可以或不想放在validator 中的验证逻辑时,应该覆盖它来处理验证。
- Field的run_validator方法; 这步骤主要是验证各种验证器
Field的clean方法会按顺序执行这三个方法:
def to_python(self, value):
return value
def validate(self, value):
if value in self.empty_values and self.required:
raise ValidationError(self.error_messages['required'], code='required')
def run_validators(self, value):
if value in self.empty_values:
return
errors = []
for v in self.validators:
try:
v(value)
except ValidationError as e:
if hasattr(e, 'code') and e.code in self.error_messages:
e.message = self.error_messages[e.code]
errors.extend(e.error_list)
if errors:
raise ValidationError(errors)
def clean(self, value):
"""
Validates the given value and returns its "cleaned" value as an
appropriate Python object.
Raises ValidationError for any errors.
"""
value = self.to_python(value)
self.validate(value)
self.run_validators(value)
return value
BaseForm实现了is_valid基本方法:
@html_safe
@python_2_unicode_compatible
class BaseForm(object):
..........
@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
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_valid访问self.errors,该方法是被描述符property修饰的,它会调用full_clean法,看代码:
class BaseForm:
def full_clean(self):
self._errors = ErrorDict()
self.cleaned_data = {}
self._clean_fields()
self._clean_form()
self._post_clean()
form的_clean_field最终会调用Field的clean方法:
def _clean_fields(self):
try:
value = field.clean(value)
self.cleaned_data[name] = value
except ValidationError as e:
self.add_error(name, e)
对于开发者,我们可以重构_post_clean等方法,或者自定义验证器等。
综述:代码调用情况:当form调用is_valid的时候最终就会调用Field的clean方法。调用顺序is_valid-->errors-->full_clean-->clean_fields。
其实到这,表单验证过程,还没有完呢,还有一个表单的clean_fieldname方法。该 方法完成于特定属性相关的验证,这个验证与字段的类型无关 。我们在下面讲。
二、模型表单验证
模型表单验证主要经历两个过程,一是和普通表单一样的表单验证,然后是模型验证。
普通表单在上面已经说了,主要说模型验证。
验证一个模型,要经历三个步骤:
- 验证模型的字段 Models.clean_fields();
- 验证模型的完整性 Models.clean();
- 验证模型的唯一性 Models.validate_unique()。
验证模型的字段,包括required,invalid,max_length等等是否有效;
验证唯一性是如果有些字段是唯一健,或者主键,那么如果填写字段颖存在了,就会抛出错误,看代码:
def unique_error_message(self, model_class, unique_check):
opts = model_class._meta
params = {
'model': self,
'model_class': model_class,
'model_name': six.text_type(capfirst(opts.verbose_name)),
'unique_check': unique_check,
}
# A unique field
if len(unique_check) == 1:
field = opts.get_field(unique_check[0])
params['field_label'] = six.text_type(capfirst(field.verbose_name))
return ValidationError(
message=field.error_messages['unique'],
code='unique',
params=params,
)
如果字段值已经出现过,那么就会抛出一个验证错误,看上面代码,返回的信息是field字段的errormessage,因此你完全可以自定义此类消息的内容。我自己在项目中也定义了。当用户名已经存在的时候,就会抛出错误信息:
class CustomUserCreationForm(UserCreationForm):
error_messages = {
'password_mismatch': u"两次新密码不匹配",
}
class Meta(UserCreationForm.Meta):
model = User
fields = UserCreationForm.Meta.fields + ('email',)
error_messages = {
'username': {
'unique': u"用户名已经注册,请更改用户名",
},
}
当你填写一个已经存在的用户名的时候,虽然表单验证通过了,但模型验证未通过,这时就会抛出错误信息了。
在上面的普通表单验证中,我们提到了类方法调用关系,当在视图类中调用form.is_valid之后,就会调用form的full_clean方法,然后该方法调用了_post_clean,ModelForm对此进行了重构,看下源代码:
ModelForm继承自BaseForm,,ModelForm对此方法进行了重构:
class BaseModelForm:
def _post_clean(self):
opts = self._meta
try:
self.instance.full_clean(exclude=exclude, validate_unique=False)
except ValidationError as e:
self._update_errors(e)
Bingo!这段代码显示调用了模型实例的full_clean方法,full_clean方法将会执行clean_fields,clean,validate_unique等方法进行验证。:
来吧,让我们看下Model的代码吧:
Class Model:
def full_clean(self, exclude=None, validate_unique=True):
"""
Calls clean_fields, clean, and validate_unique, on the model,
and raises a ``ValidationError`` for any errors that occurred.
"""
errors = {}
if exclude is None:
exclude = []
else:
exclude = list(exclude)
try:
self.clean_fields(exclude=exclude)
except ValidationError as e:
errors = e.update_error_dict(errors)
# Form.clean() is run even if other validation fails, so do the
# same with Model.clean() for consistency.
try:
self.clean()
except ValidationError as e:
errors = e.update_error_dict(errors)
# Run unique checks, but only for fields that passed validation.
if validate_unique:
for name in errors.keys():
if name != NON_FIELD_ERRORS and name not in exclude:
exclude.append(name)
try:
self.validate_unique(exclude=exclude)
except ValidationError as e:
errors = e.update_error_dict(errors)
if errors:
raise ValidationError(errors)
上面讲了一大堆关于表单验证和模型验证的,细心的人可能会注意到,你一般都是对单个字段的验证,如果我还想验证和字段本身类型无关,而是字段间某种关联性的时候,该怎么办呢?
就比如说,上面的密码,两次密码要一直,不一致就会抛出错误,那到底是怎么实现的呢?其实就是用到了在第一小节提到的clean_fieldname方法。
在UserCreattionForm有一个方法是clean_password2方法:
class UserCreationForm(forms.ModelForm):
def clean_password2(self):
password1 = self.cleaned_data.get("password1")
password2 = self.cleaned_data.get("password2")
if password1 and password2 and password1 != password2:
raise forms.ValidationError(
self.error_messages['password_mismatch'],
code='password_mismatch',
)
该方法是用来验证密码是否一致,如果不一致就抛出错误。那该方法是如何调用的呢?
在上面我们说到,form.is_valid()会调用_clean_field,除了处理单个字段,还有一段代码:
def _clean_fields(self):
try:
for name, field in self.fields.items():
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)
看到没?hasattr(self,'clean_%s'%name)
如果存在clean_fieldname,就会执行该方法进行验证。牛逼吧?!!!
Django真的是太牛逼了!!!
再说下User的密码验证。Django提供了内置的验证器,具体配置在settings.py中:
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
User在save时,会根据配置的密码验证器来验证密码,看一下源代码:
def get_password_validators(validator_config):
validators = []
for validator in validator_config:
try:
klass = import_string(validator['NAME'])
except ImportError:
msg = "The module in NAME could not be imported: %s. Check your AUTH_PASSWORD_VALIDATORS setting."
raise ImproperlyConfigured(msg % validator['NAME'])
validators.append(klass(**validator.get('OPTIONS', {})))
return validators
def validate_password(password, user=None, password_validators=None):
"""
Validate whether the password meets all validator requirements.
If the password is valid, return ``None``.
If the password is invalid, raise ValidationError with all error messages.
"""
errors = []
if password_validators is None:
password_validators = get_default_password_validators()
for validator in password_validators:
try:
validator.validate(password, user)
except ValidationError as error:
errors.append(error)
if errors:
raise ValidationError(errors)
内置的密码验证器,包括长度最短验证等:
-
UserAttributeSimilarityValidator
, 验证和别的用户的相似性 -
MinimumLengthValidator
, 验证最短长度,默认是9. -
CommonPasswordValidator
,验证师傅出现在常见的比较差的密码列表中。 -
NumericPasswordValidator
,验证是否都是数字,不能裙式数字
你自己完全可以自定义的。我写了一个MinimumLengthValidator的子类,就是我想用汉语。
from django.contrib.auth.password_validation import MinimumLengthValidator
from django.core.exceptions import ImproperlyConfigured, ValidationError
class MinLenValidator(MinimumLengthValidator):
def validate(self, password, user=None):
if len(password) < self.min_length:
raise ValidationError(
'密码长度太短,至少要8个字符!',
code='password_too_short',
params={'min_length': self.min_length},
)
其实很简单,除了Django内置的验证器,你可以自定义任何你想要的验证器。
微信分享/微信扫码阅读