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

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

  • Метод clean() полевого подкласса отвечает за выполнение to_python() , validate() и run_validators() в правильном порядке, и распространение ошибок. Если в любой момент какой-либо из этих методов вызывает исключение ValidationError , проверка прекращается, и эта ошибка распространяется. Этот метод возвращает очищенные данные, которые затем вставляются в словарь cleaned_data формы.

  • Метод clean_<nom_du_champ>() вызывается для подкласса формы - где <nom_du_champ> заменяется именем атрибута поля формы. Этот метод заботится о любой очистке, относящейся к этому атрибуту, независимо от типа задействованного поля. Этот метод не получает параметров. Вам нужно найти значение поля в 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_<nom_du_champ>() . Наконец, после того, как эти два метода были выполнены для каждого поля, метод 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 .

Валидаторы могут использоваться для проверки значений поля; Например, давайте посмотрим на поле SlugField Django:

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 они будут заполнены любыми данными, прошедшими проверку до этого момента. Поэтому вы также должны помнить о том, что поля, которые вы хотите проверить, возможно, не прошли предварительный этап проверки на уровне отдельного поля.

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