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

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

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

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

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

  • to_python()Метод на Fieldпервый шаг в каждой проверке. Он приводит значение к правильному типу данных и повышает, ValidationErrorесли это невозможно. Этот метод принимает исходное значение из виджета и возвращает преобразованное значение. Например, a 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()это будет правильное место для этого. Вам не нужно конкретное поле (это a 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()метод вызывает a 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экземпляров с помощью codes, paramsно список строк также будет работать:

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

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

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

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

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

Поля формы (и модели) Django поддерживают использование служебных функций и классов, известных как валидаторы. Валидатор - это вызываемый объект или функция, которая принимает значение и ничего не возвращает, если значение допустимо, или вызывает a, 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поле всегда содержит адрес "[email protected]". Это проверка, специфичная для нашей формы, поэтому мы не хотим помещать ее в общий 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 "[email protected]" 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будут заполнены любыми данными, которые сохранились до сих пор. Таким образом, вам также необходимо помнить о том, что поля, которые вы хотите проверить, могли не пройти первоначальные проверки отдельных полей.

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

Copyright ©2021 All rights reserved