Валидация форм и полей
Вадидация форм происходит при очистке данных. Если вы хотите настроить этот процесс, существуют различные места для внесения изменений, каждое из которых служит разной цели. Во время обработки форм выполняются три типа методов очистки. Они обычно выполняются при вызове метода 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")
...
Второй подход к отслеживанию ошибок валидации может заключаться в присвоении сообщения об ошибке одному из полей. В этом случае давайте присвоим сообщение об ошибке как строкам «предмет» и «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.0/ref/forms/validation/