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