Spec-Zone.ru › Django 1.9

Формирование и проверка полей

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

  • Метод 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.forms import CharField
from django.core import validators

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(MultiEmailField, self).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

class ContactForm(forms.Form):
    # Everything as before.
    ...

    def clean_recipients(self):
        data = self.cleaned_data['recipients']
        if "fred@example.com" not in data:
            raise forms.ValidationError("You have forgotten about Fred!")

        # Always return the cleaned data, whether you have changed it or
        # not.
        return data

Очистка и валидация полей, зависящих друг от друга

Предположим, мы добавим ещё одно требование к нашей форме связи: если поле cc_myself имеет значение True, поле subject должно содержать слово "help". Мы выполняем валидацию более чем одного поля одновременно, поэтому метод формы clean() является подходящим местом для этого. Обратите внимание, что мы говорим о методе clean() формы, в то время как ранее мы писали метод clean() для поля. Важно чётко различать поля и формы при определении места валидации. Поля — это отдельные данные, формы — это набор полей.

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

Существует два способа сообщить об ошибках на этом этапе. Наиболее распространённый способ — отобразить ошибку вверху формы. Для создания такой ошибки можно вызвать исключение ValidationError из метода clean(). Например:

from django import forms

class ContactForm(forms.Form):
    # Everything as before.
    ...

    def clean(self):
        cleaned_data = super(ContactForm, self).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 forms.ValidationError(
                    "Did not send for 'help' in the subject despite "
                    "CC'ing yourself."
                )

В этом коде, если ошибка валидации будет вызвана, форма отобразит сообщение об ошибке в верхней части формы (обычно) с описанием проблемы.

Вызов super(ContactForm, self).clean() в примере кода гарантирует, что любая логика валидации в родительских классах сохраняется. Если ваша форма наследуется от другой, которая не возвращает словарь cleaned_data в своём методе clean() (это необязательно), то не присваивайте cleaned_data результату вызова super() и используйте self.cleaned_data вместо этого:

def clean(self):
    super(ContactForm, self).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(ContactForm, self).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/1.9/ref/forms/validation/

Spec-Zone.ru

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