Spec-Zone.ru › Django 5.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])

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

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

Spec-Zone.ru

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