Формирование и валидация полей
Валидация форм происходит при очистке данных. Если вы хотите настроить этот процесс, есть несколько мест для внесения изменений, каждое из которых служит различной цели. Во время обработки формы выполняются три типа методов очистки. Обычно они выполняются при вызове метода 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, любые специфичные для поля методы очистки не вызываются. Однако методы очистки для всех остальных полей все равно выполняются.
Вызов 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.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 всегда содержит адрес "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().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().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.
© Django Software Foundation and individual contributors
Licensed under the BSD License.
https://docs.djangoproject.com/en/2.1/ref/forms/validation/