Формирование и валидация полей
Валидация форм происходит при очистке данных. Если вы хотите настроить этот процесс, существуют различные места для внесения изменений, каждое из которых служит разной цели. Во время обработки формы выполняются три типа методов очистки. Они обычно выполняются при вызове метода is_valid() формы. Существуют и другие действия, которые также могут вызывать очистку и валидацию (доступ к атрибуту errors или вызов full_clean() напрямую), но обычно они не понадобятся.
В общем случае любой метод очистки может вызвать ValidationError, если возникла проблема с данными, которые он обрабатывает, передав соответствующую информацию конструктору ValidationError. См. ниже лучшие рекомендации по поднятию ValidationError. Если ValidationError не вызывается, метод должен возвращать очищенные (нормализованные) данные в виде объекта Python.
Большинство валидаций можно выполнить с помощью валидаторов — простых помощников, которые легко повторно использовать. Валидаторы — это простые функции (или вызываемые объекты), которые принимают один аргумент и вызывают ValidationError при некорректном вводе. Валидаторы выполняются после вызова методов to_python и validate поля.
Валидация формы разделена на несколько шагов, которые можно настраивать или переопределять:
- Метод
to_python()поляFieldявляется первым шагом каждой валидации. Он приводит значение к правильному типу данных и вызываетValidationError, если это невозможно. Этот метод принимает исходное значение из виджета и возвращает преобразованное значение. Например, полеFloatFieldпреобразует данные в объект Pythonfloatили вызывает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, любой метод очистки, специфичный для этого поля, не вызывается. Однако методы очистки для всех остальных полей все еще выполняются.
Вызов ошибки валидации
Для того, чтобы сообщения об ошибках были гибкими и легко переопределяемыми, придерживайтесь следующих рекомендаций:
-
Предоставьте описательное сообщение об ошибке конструктору:
# 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 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
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. См. Поднятие ошибки валидации для получения дополнительной информации. Обратите внимание, что add_error() автоматически удаляет поле из cleaned_data.
© Django Software Foundation and individual contributors
Licensed under the BSD License.
https://docs.djangoproject.com/en/1.10/ref/forms/validation/