Django from表单字段验证

表单的验证分为几个步骤,可以自定义或覆盖:

表单验证发生在数据验证之后。如果你需要自定义这个过程,有几个不同的地方可以修改,每个地方的目的不一样。表单处理过程中要运行三种类别的验证方法。它们通常在你调用表单的is_valid() 方法时执行。还有其它方法可以触发验证过程(访问errors 属性或直接调用full_clean() ),但是通用情况下不需要。

一般情况下,如果处理的数据有问题,每个类别的验证方法都会引发ValidationError,并将相关信息传递给ValidationError。如果没有引发ValidationError,这些方法应该返回验证后的(规整化的)数据的Python 对象。

大部分应该可以使用Validators 完成,它们可以很容易地重用。Validators 是简单的函数(或可调用对象),它们接收一个参数并对非法的输入抛出ValidationError。 Validators 在字段的to_python 和validate 方法调用之后运行。

表单的验证划分成几个步骤,它们可以定制或覆盖:

  • 字段的to_python() 方法是验证的第一步。它将值强制转换为正确的数据类型,如果不能转换则引发ValidationError。 这个方法从Widget 接收原始的值并返回转换后的值。例如,FloatField 将数据转换为Python 的float 或引发ValidationError。

  • 字段的validate() 方法处理字段特异性的验证,这种验证不适合位于validator 中。它接收一个已经转换成正确数据类型的值, 并在发现错误时引发ValidationError。这个方法不返回任何东西且不应该改变任何值。当你遇到不可以或不想放在validator 中的验证逻辑时,应该覆盖它来处理验证。

  • 字段的run_validators() 方法运行字段的所有Validator,并将所有的错误信息聚合成一个单一的ValidationError。你应该不需要覆盖这个方法。

  • Field子类的clean() 方法。它负责以正确的顺序运行to_python、validate 和 run_validators 并传播它们的错误。如果任何时刻、任何方法引发ValidationError,验证将停止并引发这个错误。这个方法返回验证后的数据,这个数据在后面将插入到表单的 cleaned_data 字典中。

  • 表单子类中的clean_<fieldname>() 方法 —— <fieldname> 通过表单中的字段名称替换。这个方法完成于特定属性相关的验证,这个验证与字段的类型无关。这个方法没有任何传入的参数。你需要查找self.cleaned_data 中该字段的值,记住此时它已经是一个Python 对象而不是表单中提交的原始字符串(它位于cleaned_data 中是因为字段的clean() 方法已经验证过一次数据)。

    例如,如果你想验证名为serialnumber 的CharField 的内容是否唯一, clean_serialnumber() 将是实现这个功能的理想之处。你需要的不是一个特别的字段(它只是一个CharField),而是一个特定于表单字段特定验证,并规整化数据。

    这个方法返回从cleaned_data 中获取的值,无论它是否修改过。

  • 表单子类的clean() 方法。这个方法可以实现需要同时访问表单多个字段的验证。这里你可以验证如果提供字段A,那么字段B 必须包含一个合法的邮件地址以及类似的功能。 这个方法可以返回一个完全不同的字典,该字典将用作cleaned_data。

    因为字段的验证方法在调用clean() 时会运行,你还可以访问表单的errors 属性,它包含验证每个字段时的所有错误。

    注意,你覆盖的Form.clean()引发的任何错误将不会与任何特定的字段关联。它们位于一个特定的“字段”(叫做__all__)中,如果需要可以通过 non_field_errors() 方法访问。如果你想添加一个特定字段的错误到表单中,需要调用 add_error()。

    还要注意,覆盖ModelForm 子类的clean() 方法需要特殊的考虑。

这些方法按以上给出的顺序执行,一次验证一个字段。也就是说,对于表单中的每个字段(按它们在表单定义中出现的顺序),先运行Field.clean() ,然后运行clean_<fieldname>()。每个字段的这两个方法都执行完之后,最后运行Form.clean()方法,无论前面的方法是否抛出过异常。

下面有上面每个方法的示例。

我们已经提到过,所有这些方法都可以抛出ValidationError。对于每个字段,如果Field.clean() 方法抛出 ValidationError,那么将不会调用该字段对应的clean_<fieldname>()方法。 但是,剩余的字段的验证方法仍然会执行。

抛出多个错误

如果在一个验证方法中检查到多个错误并且希望将它们都反馈给表单的提交者,可以传递一个错误的列表给ValidationError构造函数。

和上面一样,建议传递的列表中的ValidationError 实例都带有 code 和params,但是传递一个字符串列表也可以工作:

# Good
raise ValidationError([
    ValidationError(_('Error 1'), code='error1'),
    ValidationError(_('Error 2'), code='error2'),
])

# Bad
raise ValidationError([
    _('Error 1'),
    _('Error 2'),
])

实践验证

使用Validator

Django 的表单(以及模型)字段支持使用简单的函数和类用于验证,它们叫做Validator。Validator 是可调用对象或函数,它接收一个值,如果该值合法则什么也不返回,否则抛出ValidationError。它们可以通过字段的validators 参数传递给字段的构造函数,或者定义在Field 类的default_validators 属性中。

简单的Validator 可以用于在字段内部验证值,让我们看下Django 的SlugField:

常见的情形,例如验证邮件地址和正则表达式,可以使用Django 中已经存在的Validator 类处理。例如,validators.validate_slug 是RegexValidator 的一个实例,它构造时的第一个参数为:^[-a-zA-Z0-9_]+$。编写Validator 一节可以查到已经存在的Validator 以及如何编写Validator 的一个示例。

验证特定字段属性

 username = forms.CharField(
        label='用户名', 
        max_length=30,
        min_length=3,
        widget=forms.TextInput(attrs={'class':'form-control', 'placeholder':'请输入3-30位用户名'})
    )
    email = forms.EmailField(
        label='邮箱', 
        widget=forms.EmailInput(attrs={'class':'form-control', 'placeholder':'请输入邮箱'})
    )
    verification_code = forms.CharField(
        label='验证码',
        required=False,
        widget=forms.TextInput(
            attrs={'class':'form-control', 'placeholder':'点击“发送验证码”发送到邮箱'}
        )
    )
    password = forms.CharField(
        label='密码', 
        min_length=6,
        widget=forms.PasswordInput(attrs={'class':'form-control', 'placeholder':'请输入密码'})
    )
    password_again = forms.CharField(
        label='再输入一次密码', 
        min_length=6,
        widget=forms.PasswordInput(attrs={'class':'form-control', 'placeholder':'再输入一次密码'})
    )

    def __init__(self, *args, **kwargs):
        if 'request' in kwargs:
            self.request = kwargs.pop('request')
        super(RegForm, self).__init__(*args, **kwargs)

    def clean_username(self):
        username = self.cleaned_data['username']
        if User.objects.filter(username=username).exists():
            raise forms.ValidationError('用户名已存在')
        return username

    def clean_email(self):
        email = self.cleaned_data['email']
        if User.objects.filter(email=email).exists():
            raise forms.ValidationError('邮箱已存在')
        return email

    def clean_password_again(self):
        password = self.cleaned_data['password']
        password_again = self.cleaned_data['password_again']
        if password != password_again:
            raise forms.ValidationError('两次输入的密码不一致')
        return password_again

    def clean_verification_code(self):
        verification_code = self.cleaned_data.get('verification_code', '').strip()
        if verification_code == '':
            raise forms.ValidationError('验证码不能为空')
        return verification_code

验证相互依赖的字段

假设我们添加另外一个需求到我们的联系人表单中:如果cc_myself 字段为True,那么subject 必须包含单词"help"。我们的这个验证包含多个字段,所以表单的clean() 方法是个不错的地方。注意,我们这里讨论的是表单的clean() 方法,之前我们编写的字段的clean() 方法。区别字段和表单之间的差别非常重要。字段是单个数据,表单是字段的集合。

在调用表单clean() 方法的时候,所有字段的验证方法已经执行完(前两节),所以self.cleaned_data 填充的是目前为止已经合法的数据。所以你需要记住这个事实,你需要验证的字段可能没有通过初试的字段检查。

在这一步,有两种方法报告错误。最简单的方法是在表单的顶端显示错误。你可以在clean() 方法中抛出ValidationError 来创建错误。例如:

from django import forms

class ContactForm(forms.Form):
    # Everything as before.
    ...

    def clean(self):
        cleaned_data = super(ContactForm, self).clean()
        cc_myself = cleaned_data.get("cc_myself")
        subject = cleaned_data.get("subject")

        if cc_myself and subject:
            # Only do something if both fields are valid so far.
            if "help" not in subject:
                raise forms.ValidationError("Did not send for 'help' in "
                        "the subject despite CC'ing yourself.")

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值