Spec-Zone.ru › Django 4.2

Формирование и валидация полей

Валидация формы происходит при очистке данных. Если вы хотите настроить этот процесс, есть различные места для внесения изменений, каждое из которых служит разным целям. Во время обработки формы выполняются три типа методов очистки. Обычно они выполняются при вызове метода 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.

Валидаторы могут использоваться для валидации значений внутри поля, давайте рассмотрим валидатор 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])

Общие случаи, такие как проверка на соответствие email или регулярному выражению, можно обрабатывать с помощью существующих классов валидаторов, доступных в Django. Например, validators.validate_slug — это экземпляр RegexValidator, созданный с шаблоном в качестве первого аргумента: ^[-a-zA-Z0-9_]+$. См. раздел создание валидаторов, чтобы увидеть список доступных валидаторов и пример создания валидатора.

Стандартная очистка полей формы

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

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

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

Второй подход для сообщения об ошибках валидации может заключаться в присвоении сообщения об ошибке одному из полей. В этом случае давайте назначим сообщение об ошибке для строк «тема» и «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/4.2/ref/forms/validation/

Spec-Zone.ru

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