Spec-Zone.ru › Django 3.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_]+$. См. раздел о создании валидаторов для получения списка доступных валидаторов и примера написания валидатора.

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

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

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/3.2/ref/forms/validation/

Spec-Zone.ru

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