Spec-Zone.ru › Django 1.8

Формирование и валидация полей

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

Простые валидаторы можно использовать для валидации значений внутри поля, давайте рассмотрим SlugField Django:

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

В предыдущих версиях Django, form.clean() требовалось возвращать словарь cleaned_data. Этот метод может по-прежнему возвращать словарь данных для использования, но это больше не требуется.

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

Вызов 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.8/ref/forms/validation/

Spec-Zone.ru

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