从前端提交的各种数据可能存缺少必要字段以及包含非法数据等问题, 并且通常需要进行类型转换后才可以交由业务逻辑处理.
我们当然可以在控制器(Django的views函数)中完成这些工作, 但是这样会使控制器变得非常臃肿降低可维护性.
django.forms.Form
是表单校验器的基类, 内置了必备的转换和校验逻辑并且可以让我们方便的自定义校验逻辑.
再说明如何自定义校验逻辑之前, 先介绍下Form的校验逻辑:
调用各字段的to_python()
方法: 将请求参数转换成对应的python数据类型.若转换失败则抛出ValidationError.
# django.forms.CharField.to_python
def to_python(self, value):
"Returns a Unicode object."
if value in self.empty_values:
return ''
value = force_text(value)
if self.strip:
value = value.strip()
return value
调用各字段的validate()
方法: 进行长度, 正则匹配等内置校验规则, 若不满足校验条件则抛出ValidationError.必要时可以重载该方法.
# django.forms.Form.validate
def validate(self, value):
if value in self.empty_values and self.required:
raise ValidationError(self.error_messages['required'], code='required')
调用各字段的run_validators()
方法运行字段的所有自定义Validator, 并将所有的错误信息聚合成一个单一的ValidationError, 通常不需要重载该方法.
# django.forms.Form.run_validators
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)
各字段的clean()
方法负责控制上述流程的执行, 并将清洗过的数据放入表单中名为cleaned_data
的字典中:
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
最后调用表单子类中各字段的自定义校验器.自定义校验器可以从Form.cleaned_data
中取到经过上述步骤处理过的数据.
下面将展示如何编写一个注册表单校验器:
from django import forms
class RegisterForm(forms.Form):
username = forms.RegexField(
required=True,
max_length=30,
regex='^[w|d]+$',
error_messages={
'required': ‘ERROR_USERNAME_REQUIRED’,
'max_length': 'ERRROR_FIELD_TOO_LONG',
'invalid': 'ERROT_INVALID_USERNAME',
},
label='username')
password1 = forms.CharField(
required=True,
max_length=30,
error_messages={
'required': 'ERROR_PASSWD_REQUIRED',
'max_length': 'ERRROR_FIELD_TOO_LONG',
},
label='password1', widget=forms.PasswordInput)
password2 = forms.CharField(
required=True,
max_length=30,
error_messages={
'required': 'ERROR_PASSWD_REQUIRED',
'max_length': 'ERRROR_FIELD_TOO_LONG',
},
label='password2', widget=forms.PasswordInput)
email = forms.EmailField(required=False)
gender = forms.ChoiceField(required=True, choices=[0, 1]
def clean_username(self):
username = self.cleaned_data['username']
exist = User.objects.filter(username=username).count()
if exist:
raise forms.ValidationError('ERROR_ACCOUNT_EXISTED')
else:
self.cleaned_data['username'] = username
return self.cleaned_data['username']
def clean_password1(self):
if self.cleaned_data['password2'] != self.cleaned_data['password1']:
raise forms.ValidationError('ERROR_PASSWD_NOT_SAME')
self.cleaned_data['password1'] = encrypt(self.cleaned_data['password1'])
return self.cleaned_data['password1']
required
是每个字段都有的校验规则, 其为True
时字段值不允许为空.
error_messages
是一个字典, 当某条规则未满足时就会抛出其对应的错误信息.
我们也可以在字段的构造器中使用validators=[]
关键字参数添加校验器, 它们由run_validators
方法控制调用并汇总错误信息.
label
用可读性较高的方式描述字段, widget
则指定输入组件.
上述代码中展示了部分字段, 我们常用的字段类型有:
下面的示例展示了如何在views函数中使用表单:
def register(request):
if request.method != 'POST':
raise Http404
form = RegisterForm(data=request.POST)
if not form.is_valid():
raise MyInvalidFormError(form.errors)
username = form.cleaned_data['username']
passsword = form.cleaned_data['password1']
if form.cleaned_data.get('email'):
email = form.cleaned_data['email']
else:
email = default_email
user = User.objects.create_data(username, email, password)
user.save()
return render(request, 'success.html')
在构造表单时使用data关键字参数将原始数据交给表单进行处理, 调用表单的is_valid()
方法或访问errors
属性时将触发Form的校验流程.
我们可以在errors
属性获得校验中产生的所有错误信息, 校验完成后可以在cleaned_data
属性中访问处理完的数据.