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

Валидаторы могут использоваться для проверки значений внутри поля, давайте рассмотрим пример валидации 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_]+$. Обратитесь к разделу создания валидаторов, чтобы увидеть список доступных валидаторов и пример создания валидатора.

Очистка полей формы по умолчанию

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

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")
    ...

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

Spec-Zone.ru

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