表单和字段验证

清除数据时会进行表单验证。如果您想要定制这个过程,可以在不同的地方进行更改,每个地方都有不同的用途。表单处理过程中运行三种清洁方法。这些通常在调用 is_valid() 窗体上的方法。还有其他事情也可以触发清理和验证(访问 errors 属性或调用 full_clean() 但通常不需要。

一般来说,任何清洁方法都可以提高 ValidationError 如果正在处理的数据有问题,请将相关信息传递给 ValidationError 建造师。 See below 为了培养最佳实践 ValidationError . 如果没有 ValidationError 如果引发,则该方法应将已清理(规范化)的数据作为Python对象返回。

大多数验证可以使用 validators -可重用的辅助对象。验证器是接受单个参数并引发的函数(或可调用函数) ValidationError 输入无效。验证程序在字段的 to_pythonvalidate 方法已被调用。

表单验证分为几个步骤,可以自定义或重写:

  • 这个 to_python() A方法 Field 是每次验证的第一步。它将值强制为正确的数据类型并引发 ValidationError 如果不可能的话。此方法接受小部件的原始值并返回转换后的值。例如,A FloatField 将数据转换为python float 或提高 ValidationError .

  • 这个 validate() A方法 Field 处理不适合验证程序的特定于字段的验证。它接受一个被强制为正确数据类型的值并引发 ValidationError 任何错误。此方法不返回任何内容,不应更改值。您应该重写它来处理不能或不想放入验证器的验证逻辑。

  • 这个 run_validators() A方法 Field 运行所有字段的验证器,并将所有错误聚合为一个 ValidationError . 不需要重写此方法。

  • 这个 clean() A方法 Field 子类负责运行 to_python()validate()run_validators() 以正确的顺序传播错误。如果在任何时候,任何方法 ValidationError ,验证停止并引发该错误。此方法返回干净的数据,然后将其插入 cleaned_data 形式词典。

  • 这个 clean_<fieldname>() 方法是在窗体子类上调用的--其中 <fieldname> 替换为窗体字段属性的名称。此方法执行特定于该特定属性的任何清理,与字段类型无关。此方法未传递任何参数。您需要在中查找字段的值 self.cleaned_data 记住,此时它将是一个python对象,而不是表单中提交的原始字符串(它将 cleaned_data 因为一般领域 clean() 方法(如上所述)已经清除了数据一次)。

    例如,如果要验证 CharField 打电话 serialnumber 是独一无二的, clean_serialnumber() 是个合适的地方。你不需要一个特定的字段(它是一个 CharField ,但您需要一个特定于formField的验证,可能还需要清理/规范化数据。

    此方法的返回值将替换 cleaned_data ,因此它必须是字段的值 cleaned_data (即使此方法没有更改)或新的已清除值。

  • 窗体子类 clean() 方法可以执行需要访问多个表单字段的验证。这是您可以放入“if字段”等支票的位置。 A 提供,字段 B 必须包含有效的电子邮件地址”。如果愿意,此方法可以返回完全不同的字典,它将用作 cleaned_data .

    因为现场验证方法在 clean() 也可以访问窗体的 errors 属性,其中包含清除单个字段所引发的所有错误。

    请注意,您的 Form.clean() 覆盖将不会与任何特定字段关联。他们进入一个特殊的“领域”(叫做 __all__ ,您可以通过 non_field_errors() 方法(如果需要)。如果要将错误附加到表单中的特定字段,则需要调用 add_error() .

    还要注意,在覆盖 clean() A方法 ModelForm 子类。(见 ModelForm documentation 更多信息)

这些方法按照上面给出的顺序运行,一次运行一个字段。也就是说,对于表单中的每个字段(按照表单定义中声明的顺序), Field.clean() 方法(或其重写)运行,然后 clean_<fieldname>() . 最后,一旦对每个字段运行了这两种方法, Form.clean() 无论以前的方法是否引发错误,都将执行方法或其重写。

下面提供了每种方法的示例。

如前所述,任何这些方法都可以 ValidationError . 对于任何领域,如果 Field.clean() 方法提出 ValidationError ,不调用任何特定于字段的清理方法。但是,所有剩余字段的清理方法仍在执行。

饲养 ValidationError

为了使错误消息灵活且易于重写,请考虑以下准则:

  • 提供描述性错误 code 致施工单位:

    # Good
    ValidationError(_("Invalid value"), code="invalid")
    
    # Bad
    ValidationError(_("Invalid value"))
    
  • 不要将变量强制到消息中;使用占位符和 params 构造函数的参数::

    # Good
    ValidationError(
        _("Invalid value: %(value)s"),
        params={"value": "42"},
    )
    
    # Bad
    ValidationError(_("Invalid value: %s") % value)
    
  • 使用映射键而不是位置格式。这使得在重写消息时可以将变量按任意顺序放置或完全忽略它们:

    # Good
    ValidationError(
        _("Invalid value: %(value)s"),
        params={"value": "42"},
    )
    
    # Bad
    ValidationError(
        _("Invalid value: %s"),
        params=("42",),
    )
    
  • 用包装邮件 gettext 启用翻译:

    # Good
    ValidationError(_("Invalid value"))
    
    # Bad
    ValidationError("Invalid value")
    

综合起来:

raise ValidationError(
    _("Invalid value: %(value)s"),
    code="invalid",
    params={"value": "42"},
)

如果编写可重用的表单、表单字段和模型字段,则特别需要遵循这些准则。

虽然不推荐,但如果您处于验证链的末尾(即您的表单 clean() 方法)你知道你会 从未 需要覆盖您的错误消息,您仍然可以选择不那么详细的:

ValidationError(_("Invalid value: %s") % value)

这个 Form.errors.as_data()Form.errors.as_json() 方法从全功能中受益匪浅 ValidationError (与) code 姓名和姓名 params 字典)。

引发多个错误

如果在清理方法期间检测到多个错误,并希望将所有错误信号发送给表单提交者,则可以将错误列表传递给 ValidationError 建造师。

如上所述,建议通过 ValidationError 实例与 code S和 params 但是字符串列表也可以工作:

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

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

在实践中使用验证

前面的部分解释了表单验证的一般工作方式。由于通过查看每个正在使用的特性有时可以更容易地将事情落实到位,下面是一系列使用前面每个特性的小示例。

使用验证器

Django的表单(和模型)字段支持使用实用函数和被称为验证器的类。验证器是一个可调用的对象或函数,它接受一个值,如果值有效或引发一个 ValidationError 如果不是。这些可以通过字段的 validators 参数,或在 Fielddefault_validators 属性。

验证器可用于验证字段内的值,让我们看看Django的 SlugField ::

from django.core import validators
from django.forms import CharField


class SlugField(CharField):
    default_validators = [validators.validate_slug]

正如你所看到的, SlugField 是一个 CharField 使用一个定制的验证器来验证提交的文本是否符合某些字符规则。这也可以在字段定义上完成,因此:

slug = forms.SlugField()

等于:

slug = forms.CharField(validators=[validators.validate_slug])

可以使用Django中现有的验证程序类来处理常见的情况,例如根据电子邮件或正则表达式进行验证。例如, validators.validate_slug 是的实例 RegexValidator 构造时,第一个参数是模式: ^[-a-zA-Z0-9_]+$ . 请参见 writing validators 查看已经可用的列表以及如何编写验证器的示例。

表单域默认清理

让我们首先创建一个自定义表单域,验证其输入是否为包含逗号分隔电子邮件地址的字符串。全班同学如下:

from django import forms
from django.core.validators import validate_email


class MultiEmailField(forms.Field):
    def to_python(self, value):
        """Normalize data to a list of strings."""
        # Return an empty list if no input was given.
        if not value:
            return []
        return value.split(",")

    def validate(self, value):
        """Check if value consists only of valid emails."""
        # Use the parent's handling of required fields, etc.
        super().validate(value)
        for email in value:
            validate_email(email)

使用此字段的每个窗体都将先运行这些方法,然后才能对该字段的数据执行任何其他操作。这是一种特定于此类型字段的清理,无论随后如何使用。

让我们创建一个 ContactForm 要演示如何使用此字段:

class ContactForm(forms.Form):
    subject = forms.CharField(max_length=100)
    message = forms.CharField()
    sender = forms.EmailField()
    recipients = MultiEmailField()
    cc_myself = forms.BooleanField(required=False)

使用 MultiEmailField 像其他窗体字段一样。当 is_valid() 方法是在窗体上调用的, MultiEmailField.clean() 方法将作为清理过程的一部分运行,它将依次调用 to_python()validate() 方法。

清除特定字段属性

从前面的示例继续,假设在 ContactForm ,我们要确保 recipients 字段始终包含地址 "fred@example.com" . 这是特定于表单的验证,因此我们不想将其放入常规 MultiEmailField 类。相反,我们编写了一个清理方法,它对 recipients 领域,就像这样:

from django import forms
from django.core.exceptions import ValidationError


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

    def clean_recipients(self):
        data = self.cleaned_data["recipients"]
        if "fred@example.com" not in data:
            raise ValidationError("You have forgotten about Fred!")

        # Always return a value to use as the new cleaned data, even if
        # this method didn't change it.
        return data

清理和验证相互依赖的字段

假设我们在联系人表单中添加了另一个要求:如果 cc_myself 字段是 True , the subject 必须包含单词 "help" . 我们一次对多个字段执行验证,因此表单 clean() 方法是一个很好的方法。注意我们正在讨论 clean() 方法,而之前我们写的是 clean() 方法。在确定在哪里验证事物时,保持字段和形式差异是很重要的。字段是单个数据点,表单是字段的集合。

到表格的时间 clean() 方法,所有单独的字段清理方法都将运行(前两部分),因此 self.cleaned_data 将填充迄今为止幸存的所有数据。因此,您还需要记住,考虑到您想要验证的字段在初始单个字段检查中可能没有幸存。

报告此步骤中的任何错误有两种方法。可能最常见的方法是在表单顶部显示错误。要创建这样的错误,可以引发 ValidationErrorclean() 方法。例如::

from django import forms
from django.core.exceptions import ValidationError


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

    def clean(self):
        cleaned_data = super().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 ValidationError(
                    "Did not send for 'help' in the subject despite " "CC'ing yourself."
                )

在这段代码中,如果出现验证错误,窗体将在窗体顶部(通常)显示一条错误消息,说明问题。这些错误是非字段错误,在模板中显示为 {{{{ form.non_field_errors }}}} .

呼唤 super().clean() 在示例代码中,确保维护父类中的任何验证逻辑。如果您的窗体继承了另一个不返回 cleaned_data ITS中的字典 clean() 方法(这样做是可选的),然后不分配 cleaned_data 结果是 super() 调用与使用 self.cleaned_data 取而代之的是:

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

报告验证错误的第二种方法可能涉及将错误消息分配给其中一个字段。在本例中,让我们为表单显示中的“subject”和“cc_myself”行分配一条错误消息。在实践中这样做时要小心,因为这样做会导致表单输出混乱。我们将展示这里可能存在的情况,并让您和您的设计师来确定在您的特定情况下有效工作的内容。我们的新代码(替换前一个示例)如下所示:

from django import forms


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

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

        if cc_myself and subject and "help" not in subject:
            msg = "Must put 'help' in subject when cc'ing yourself."
            self.add_error("cc_myself", msg)
            self.add_error("subject", msg)

第二个论点 add_error() 可以是字符串,或者最好是 ValidationError . 见 饲养 ValidationError 了解更多详细信息。注意 add_error() 自动从中删除字段 cleaned_data .