Spec-Zone.ru › Django 6.0

Проверка форм и полей

Проверка формы выполняется при очистке данных. Если вы хотите настроить этот процесс, изменить его можно в разных местах, каждое из которых служит своей цели. При обработке формы выполняются три типа методов очистки. Обычно они выполняются при вызове метода is_valid() формы. Очистку и проверку могут запускать и другие действия (обращение к атрибуту errors или непосредственный вызов full_clean()), однако обычно они не требуются.

В общем случае любой метод очистки может вызвать ValidationError, если возникла проблема с обрабатываемыми данными, передав соответствующую информацию конструктору ValidationError. См. ниже, чтобы узнать о рекомендуемом способе вызова ValidationError. Если исключение ValidationError не вызвано, метод должен вернуть очищенные (нормализованные) данные в виде объекта Python.

Большинство проверок можно выполнить с помощью валидаторов — вспомогательных средств, которые можно использовать повторно. Валидаторы — это функции (или вызываемые объекты), принимающие один аргумент и вызывающие ValidationError при недопустимом вводе. Валидаторы выполняются после вызова методов to_python и validate поля.

Проверка формы состоит из нескольких этапов, которые можно настроить или переопределить:

  • Метод to_python() класса Field — первый шаг каждой проверки. Он преобразует значение в правильный тип данных и вызывает ValidationError, если это невозможно. Метод принимает исходное значение из виджета и возвращает преобразованное значение. Например, FloatField преобразует данные в объект Python типа float или вызывает ValidationError.
  • Метод validate() класса Field выполняет проверки, относящиеся к конкретному полю и не подходящие для валидатора. Он принимает значение, преобразованное в правильный тип данных, и вызывает ValidationError при любой ошибке. Этот метод ничего не возвращает и не должен изменять значение. Переопределите его, чтобы реализовать логику проверки, которую нельзя или не следует помещать в валидатор.
  • Метод run_validators() класса Field выполняет все валидаторы поля и объединяет все ошибки в одно исключение ValidationError. Обычно переопределять этот метод не требуется.
  • Метод clean() подкласса Field отвечает за вызов to_python(), validate() и run_validators() в правильном порядке и передачу их ошибок. Если какой-либо из этих методов вызывает ValidationError, проверка прекращается и это исключение вызывается. Метод возвращает очищенные данные, которые затем помещаются в словарь cleaned_data формы.
  • Метод clean_<fieldname>() вызывается для подкласса формы — вместо <fieldname> указывается имя атрибута поля формы. Этот метод выполняет очистку, относящуюся к конкретному атрибуту, независимо от типа поля. Метод не принимает параметров. Значение поля нужно получить из self.cleaned_data; имейте в виду, что на этом этапе это будет объект Python, а не исходная строка, отправленная в форме (она будет находиться в cleaned_data, поскольку общий метод clean() поля, описанный выше, уже очистил данные).

    Например, если вы хотите проверить, что содержимое CharField с именем serialnumber уникально, для этого подойдет clean_serialnumber(). Особое поле вам не понадобится (это CharField), но потребуется проверка, относящаяся к полю формы, и, возможно, очистка или нормализация данных.

    Возвращаемое этим методом значение заменяет текущее значение в cleaned_data, поэтому это должно быть значение поля из cleaned_data (даже если метод его не изменил) или новое очищенное значение.

  • Метод clean() подкласса формы может выполнять проверки, для которых требуется доступ к нескольким полям формы. Здесь можно реализовать, например, проверку вида «если поле A заполнено, поле B должно содержать действительный адрес электронной почты». При необходимости этот метод может вернуть совершенно другой словарь, который будет использоваться в качестве cleaned_data.

    Поскольку к моменту вызова clean() методы проверки полей уже выполнены, вам также доступен атрибут errors формы, содержащий все ошибки, возникшие при очистке отдельных полей.

    Обратите внимание: ошибки, вызванные переопределенным методом Form.clean(), не будут связаны с каким-либо конкретным полем. Они попадут в специальное «поле» (называемое __all__), к которому при необходимости можно обратиться с помощью метода non_field_errors(). Чтобы связать ошибки с определенным полем формы, нужно вызвать add_error().

    Также обратите внимание: при переопределении метода clean() подкласса ModelForm следует учитывать особые моменты. (Подробнее см. в документации по ModelForm.)

Эти методы выполняются в указанном выше порядке, по одному полю за раз. То есть для каждого поля формы (в порядке их объявления в определении формы) выполняется метод 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 и params, но подойдет и список строк:

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

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

Практическое применение проверок

В предыдущих разделах объяснялось, как в целом устроена проверка форм. Поскольку иногда проще понять, как применять каждую возможность, на конкретных примерах, ниже приведены несколько небольших примеров, демонстрирующих описанные выше возможности.

Использование валидаторов

Поля форм (и моделей) Django поддерживают вспомогательные функции и классы, называемые валидаторами. Валидатор — это вызываемый объект или функция, которая принимает значение и ничего не возвращает, если оно допустимо, либо вызывает ValidationError, если значение недопустимо. Валидаторы можно передать конструктору поля через аргумент validators или определить непосредственно в классе Field с помощью атрибута default_validators.

Валидаторы можно использовать для проверки значений внутри поля. Рассмотрим SlugField в Django:

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_]+\Z. Список доступных валидаторов и пример создания собственного валидатора приведены в разделе создание валидаторов.

Очистка данных поля формы по умолчанию

Для начала создадим пользовательское поле формы, проверяющее, что его значение — строка с адресами электронной почты, разделенными запятыми. Полный класс выглядит так:

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, поле subject должно содержать слово "help". Мы проверяем сразу несколько полей, поэтому для этого подойдет метод clean() формы. Обратите внимание: здесь речь идет о методе clean() формы, тогда как ранее мы писали метод clean() для поля. При выборе места для проверок важно различать поля и формы. Поля — это отдельные элементы данных, а формы представляют собой набор полей.

К моменту вызова метода clean() формы будут выполнены все методы очистки отдельных полей (описанные в двух предыдущих разделах), поэтому self.cleaned_data будет содержать все данные, прошедшие предыдущие проверки. Поэтому следует учитывать, что поля, которые нужно проверить, могли не пройти начальные проверки отдельных полей.

Есть два способа сообщить об ошибках на этом этапе. Чаще всего ошибку отображают в верхней части формы. Чтобы создать такую ошибку, можно вызвать ValidationError из метода clean(). Например:

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() в примере гарантирует, что логика проверки родительских классов сохранится. Если ваша форма наследуется от другой формы, метод clean() которой не возвращает словарь cleaned_data (это необязательно), не присваивайте 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.

© Django Software Foundation and individual contributors
Licensed under the BSD License.
https://docs.djangoproject.com/en/6.0/ref/forms/validation/

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API