Валидация форм и полей
Валидация формы происходит при очистке данных. Если вы хотите настроить этот процесс, существуют различные места для внесения изменений, каждое из которых служит различной цели. Во время обработки формы выполняются три типа методов очистки. Они обычно выполняются при вызове метода 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_]+\Z. См. раздел создание валидаторов, чтобы увидеть список доступных валидаторов и пример создания валидатора.
Очистка полей формы по умолчанию
Давайте сначала создадим пользовательское поле формы, которое проверяет, что ввод является строкой, содержащей адреса электронной почты, разделенные запятыми. Полный класс выглядит следующим образом:
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
from django.core.exceptions import ValidationError
class ContactForm(forms.Form):
# Everything as before.
...
def clean_recipients(self):
data = self.cleaned_data["recipients"]
if "fred@example.com" not in data:
raise 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
from django.core.exceptions import ValidationError
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 ValidationError(
"Did not send for 'help' in the subject despite CC'ing yourself."
)
В этом коде, если ошибка валидации возникает, форма отобразит сообщение об ошибке в верхней части формы (обычно), описывающее проблему. Такие ошибки — это ошибки не полей, которые отображаются в шаблоне с помощью {{ form.non_field_errors }}.
Вызов 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. См. Поднятие ошибки валидации для получения более подробной информации. Обратите внимание, что add_error() автоматически удаляет поле из cleaned_data.
© Django Software Foundation and individual contributors
Licensed under the BSD License.
https://docs.djangoproject.com/en/5.2/ref/forms/validation/