Формирование и валидация полей
Валидация формы происходит при очистке данных. Если вы хотите настроить этот процесс, есть различные места для внесения изменений, каждое из которых служит разным целям. Три типа методов очистки выполняются во время обработки формы. Обычно они выполняются при вызове метода 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, любые методы очистки, специфичные для поля, не вызываются. Однако методы очистки для всех оставшихся полей все равно выполняются.
Вызов ошибки валидации
Для того чтобы сообщения об ошибках были гибкими и легко переопределялись, следуйте приведенным рекомендациям:
-
Укажите описательное сообщение об ошибке
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 себе» в отображении формы. Будьте осторожны при выполнении этого действия на практике, так как это может привести к путанице в выводе формы. Мы показываем здесь возможности, оставляя за вами и вашими дизайнерами задачу определить, что работает эффективно в вашей конкретной ситуации. Новый код (заменяющий предыдущий пример) выглядит следующим образом:
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.2/ref/forms/validation/