Метод clean() в Django ModelForm для предотвращения дублирования записей создает еще один экземпляр при обновлении данных. И даже не сохраняет новый экземпляр.
У меня есть несколько моделей, две из которых следующие:
class Receivables(models.Model): patient=models.ForeignKey(Patient, on_delete=CASCADE) pattern = RegexValidator(r'(RT|rt|rT|Rt)\/[0-9]\/[0-9]\/[0-9]', 'Enter RT Number properly!') rt_number=models.CharField(max_length=15, validators=[pattern]) discount=models.DecimalField(max_digits=9, decimal_places=2, default=0) approved_package=models.DecimalField(max_digits=10, decimal_places=2, default=0) approval_date=models.DateField(default=None) proposed_fractions=models.IntegerField() done_fractions=models.IntegerField() base_value=models.DecimalField(max_digits=10, decimal_places=2, blank=True) expected_value=models.DecimalField(max_digits=10, decimal_places=2, blank=True) class Discharge(models.Model): patient=models.ForeignKey(Patient, on_delete=CASCADE) date_of_discharge=models.DateField(default=None) mould_charges=models.DecimalField(max_digits=7, decimal_places=2, default=0, blank=True) ct_charges=models.DecimalField(max_digits=7, decimal_places=2, default=0, blank=True) discharge_updated=models.BooleanField(default=False)
Представления для сохранения нового экземпляра и обновления существующего, соответственно, следующие:
def discharge_view(request): if request.method=='POST': fm_discharge=DischargeForm(request.POST, request=request) if fm_discharge.is_valid(): discharge=fm_discharge.save() ipd=IpdReport.objects.create(patient=discharge.patient, package=Package.objects.filter(patient=discharge.patient).order_by('-id').first(), receivables=Receivables.objects.filter(patient=discharge.patient).order_by('-id').first(), discharge=discharge) if discharge is not None: OngoingReport.objects.filter(ipdreport__patient=discharge.patient).delete() package=Package.objects.filter(patient=discharge.patient).order_by('-id').first().patient_type.patient_type if discharge.discharge_updated==False and package!='CASH': UnclaimedPendingCases.objects.create(ipdreport=ipd) elif discharge.discharge_updated==True and package!='CASH': ClaimedPendingCases.objects.create(ipdreport=ipd) fm_discharge=DischargeForm(request=request) return render(request, 'account/discharge.html', ) else: fm_discharge=DischargeForm(request=request) return render(request, 'account/discharge.html', )
def update_discharge_view(request, id): di1=Discharge.objects.get(pk=id) fm1=di1.discharge_updated if request.method=='POST': print(request.POST) di=Discharge.objects.get(pk=id) form=DischargeForm(request.POST, instance=di, request=request) if form.is_valid(): discharge=form.save() else: di=Discharge.objects.get(pk=id) form=DischargeForm(instance=di, request=request) return render(request, 'account/update_discharge.html', )
Форма модели выглядит следующим образом:
class DischargeForm(ModelForm): class Meta: model=Discharge fields='__all__' widgets=< 'date_of_discharge': DateInput(attrs=), > def __init__(self, *args, **kwargs): self.request=kwargs.pop('request') self.instance=kwargs.pop('instance') super(DischargeForm, self).__init__(*args, **kwargs) def clean(self): super().clean() pt=self.request.POST.get('patient') if not self.instance: rec=Receivables.objects.filter(patient__pk=pt).order_by('-id').first() if Discharge.objects.filter(patient__pk=pt, date_of_discharge__gt=rec.approval_date).exists(): raise ValidationError('The patient has already been discharged!')
Я хочу, чтобы выписка сохранялась только один раз, для каждого случая, когда пациент получает лечение. Хотя она может быть обновлена. Ранее я писал это так:
class DischargeForm(ModelForm): class Meta: model=Discharge fields='__all__' widgets=< 'date_of_discharge': DateInput(attrs=), > def clean(self): super().clean() pt=self.cleaned_data['patient'] rec=Receivables.objects.filter(patient__pk=pt).order_by('-id').first() if Discharge.objects.filter(patient__pk=pt, date_of_discharge__gt=rec.approval_date).exists(): raise ValidationError('The patient has already been discharged!')
без передачи kwargs запроса fm_discharge=DischargeForm() в views.py
и он работал нормально для новых создаваемых экземпляров. Но для экземпляра, поступающего для обновления, он выбрасывал тот же ValidationError , потому что, очевидно, экземпляр discharge уже существует в базе данных для того же пациента. Затем, когда я добавил метод init и получил доступ к запросу и экземпляру, чтобы решить эту проблему, возникли две проблемы:
- It created a new instance of the data which was supposed to be just updated.
- As the new entry does not have an instance already, the init threw a KeyError for instance.
Что я могу здесь сделать? Как обрабатывать различные сценарии, подобные этому, в ModelForm?
Чтобы исправить это, не вставляйте instance в __init__ , так как это приведет к тому, что вызов super() сообщит ModelForm , что он будет работать над созданием нового объекта. По сути, это будет выглядеть так, как будто экземпляр вообще не был передан в форму.
Для вызова clean просто добавьте флаг, что проверка должна выполняться только при создании экземпляра (а не при обновлении) с помощью self.instance.pk , так:
def clean(self): super().clean() if not self.instance.pk: pt=self.cleaned_data['patient'] rec=Receivables.objects.filter(patient__pk=pt).order_by('-id').first() if Discharge.objects.filter(patient__pk=pt, date_of_discharge__gt=rec.approval_date).exists(): raise ValidationError('The patient has already been discharged!')
Проверка форм и полей формы¶
Проверка формы происходит при нормализации её данных. При возникновении необходимости вмешаться в этот процесс, есть много мест, где можно это сделать и которые влияют на разные этапы проверки. Во время обработки формы вызываются три типа методов для нормализации данных. Процесс проверки запускается при вызове метода is_valid() формы. Существуют ситуации, которые запускают нормализацию и проверку данных (обращение к свойству errors или прямой вызов метода full_clean() ), но они возникают достаточно редко.
В общем случае, любой нормализующий метод может вызвать исключение ValidationError при наличии проблем с данными, передавая соответствующее сообщение об ошибке в конструктор исключения. Смотрите ниже примеры, как правильно вызывать ValidationError . Если проблем не выявлено, то метод должен возвращать нормализованное значение в виде объекта языка Python.
Большая часть проверок может быть выполнена с помощью validators, которые являются простыми в использовании вспомогательными объектами. Валидатор — это простая функция (или вызываемый объект, callable), которая принимает единственный аргумент и вызывает исключение ValidationError в случае проблем с полученным значением. Валидаторы запускаются после вызова методов поля: to_python и validate .
Проверка формы состоит из нескольких этапов, каждый из которых может быть настроен или переопределён:
- Вызов метода поля to_python() является первым этапом каждой проверки. Он приводит значение к соответствующему типу данных или вызывает исключение ValidationError , если это невозможно. Метод принимает сырое значение от виджета и возвращает нормализованное значение. Например, поле типа FloatField преобразовывает данные в тип float языка Python или вызывает исключение ValidationError .
- Метод validate() поля выполняет специфическую для поля проверку данных и приводит значение к правильному типу данных, или вызывает исключение ValidationError на любую ошибку. Этот метод не возвращает значение и не должен изменять проверяемые данные. Если вам надо обеспечить логику, которую невозможно или нежелательно выносить в валидатор, то вам следует переопределить этот метод.
- Метод поля run_validators() запускает все валидаторы и аккумулирует все возникающие ошибки в одно исключение ValidationError . Вам не стоит переопределять этот метод.
- Метод clean() поля отвечает за вызов методов to_python() , validate() и run_validators() в правильном порядке и передачу их ошибок. Как только любой из этих методов вызовет исключение ValidationError , процесс проверки прекращается и ошибка передаётся выше. Этот метод возвращает проверенные данные, которые затем помещаются в словарь cleaned_data формы.
- Для проверки значения поля используется метод clean_() , где заменяется на имя поля. Этот метод выполняет проверку значения. Метод не принимает аргументы. Для получения значения поля обращайтесь к словарю self.cleaned_data и помните, что там будет объект языка Python, а не строка, переданная формой (значение находится в cleaned_data т.к. уже была выполнена проверка методом clean() поля). Например, если требуется проверить, что содержимое CharField поля с именем serialnumber является уникальным, то метод clean_serialnumber() будет правильным местом для такого функционала. Вам не нужно специальное поле (пусть будет CharField ), но требуется хитрая проверка данных и, возможно, очистка/нормализация данных. Этот метод должен возвращать очищенное значение, полученное из cleaned_data независимо, изменилось оно или нет.
- Метод clean() потомка формы. Этот метод может выполнять любую проверку, которая нуждается в одновременном доступе к данным нескольких полей. Именно здесь вы можете проверять, что если поле A заполнено, то поле B должно содержать правильный адрес электронной почты и так далее. Данные, которые возвращает этот метод, помещаются в свойство cleaned_data формы. Так как валидация полей выполняется перед вызовом clean() , вы можете получить доступ к атрибуту формы errors , который содержит уже полученные ошибки валидации. Следует отметить, что любая ошибка, вызванная методом Form.clean() формы, не будет ассоциирована ни с каким полем. Такие ошибки привязываются к «особому» полю ( __all__ ), доступ к которому можно получить через метод non_field_errors(). Если вам потребуется добавить ошибки к определённому полю формы, используйте add_error(). Также следует отметить, что существует ряд соглашений, которым необходимо следовать при переопределении метода clean() в вашем классе ModelForm . (Обратитесь к документации на ModelForm для получения подробностей.)
Эти методы вызываются в порядке, указанном выше, по одному полю за раз. Для каждого поля формы (в порядке их определения в классе формы) вызывается сначала метод Field.clean() , затем вызывается метод clean_() . После того, как пара этих методов будет вызвана для каждого поля формы, наступает очередь метода Form.clean() формы. Он будет вызыван в любом случае, даже если предыдущие методы вызывали ошибку.
Примеры для каждого из этих методов показаны ниже.
Как упоминалось ранее, любой из этих методов может вызвать исключение ValidationError . Для любого поля, если его метод clean() вызвал исключение ValidationError , то следующий метод для этого поля не вызывается. Тем не менее, методы для остальных полей отрабатывают в штатном режиме.
Вызов ValidationError ¶
Для удобной работы с ошибками валидации используйте следующие правила:
-
Передайте при создании код ошибки через аргумент code :
# Good ValidationError(_('Invalid value'), code='invalid') # Bad ValidationError(_('Invalid value'))
# 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',), )
# 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)
Вызов нескольких ошибок¶
При обнаружении нескольких ошибок в процессе нормализации поля и при наличии желания отобразить их одновременно на форме, следует передать их в виде списка в конструктор исключения.
Рекомендуется использовать список объектов 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 .
Простые валидаторы могут использоваться для проверки значений внутри полей. Давайте рассмотрим SlugField :
from django.forms import CharField from django.core import validators class SlugField(CharField): default_validators = [validators.validate_slug]
Как можно увидеть SlugField — это обычное поле CharField , которое имеет валидатор, проверяющий вводимое значение на допустимые символы. Все это можно указать при определении поля:
slug = forms.SlugField()
slug = forms.CharField(validators=[validators.validate_slug])
Обычные проверки, такие как проверка email или по регулярному выражению, можно выполнить используя существующие валидаторы 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(MultiEmailField, self).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 the cleaned data, whether you have changed it or # not. return data
Очистка и проверка полей, которые зависят друг от друга¶
Допустим, что мы добавили ещё одно требование для нашей формы: если поле cc_myself равно True , то поле subject должно содержать слово "help" . Раз мы выполняем проверку нескольких полей, то метод формы clean() будет правильным местом для нашего кода. Обратите внимание, мы сейчас говорим о методе 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(ContactForm, self).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(ContactForm, self).clean() обеспечивает проверку данных в родительском классе. Если ваша форма наследуется от класса, который не возвращает словарь cleaned_data из метода clean() (это не обязательно), не записывайте в cleaned_data результат вызова super() и используйте вместо этого self.cleaned_data :
def clean(self): super(ContactForm, self).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(ContactForm, self).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 .
Надо ли использовать cleaned_data при сохранении формы?
1. Объясните, пожалуйста, зачем нужен form.cleaned_data, если объект form уже заполнен из request.POST ?
2. В request.POST в итоге получаются безопасные данные для сохранения данных прямым вызовом метода save()?
3. Есть предположение что cleaned_data в данном случае может понадобиться например когда данные из формы нужно отправить по емэилу или сохранить в поля другой модели?
- Вопрос задан более трёх лет назад
- 1461 просмотр
Проверка форм и полей ¶
Проверка формы происходит при очистке данных. Если вы хотите настроить этот процесс, есть несколько точек входа, где можно внести изменения, каждая из которых преследует разные цели. При обработке формы используются три метода очистки. Обычно они выполняются при is_valid() вызове метода формы. Существуют и другие операции, которые также могут вызывать очистку и проверку (доступ к атрибуту errors или прямой вызов full_clean() ), но в принципе это не является частью обычного процесса.
В общем, любой метод очистки может вызвать исключение, ValidationError если есть проблема с обработкой значений, передавая соответствующую информацию конструктору ValidationError . Ниже приведены примеры передовой практики генерации ValidationError . Если исключение не ValidationError возникает, метод должен вернуть очищенное (нормализованное) значение как объект Python.
Большую часть проверки можно выполнить с помощью валидаторов , служебных функций, которые можно использовать повторно. Это функции (или исполняемые объекты), принимающие один параметр и генерирующие исключение ValidationError в случае недопустимой записи. Валидаторы выполняются после вызова методов to_python и validate поля.
Проверка формы разделена на несколько шагов, которые можно персонализировать или перегружать:
- Метод to_python() поля Field - это первый шаг в любой проверке. Он преобразует значение в правильный тип данных и генерирует, ValidationError если это невозможно. Этот метод принимает исходное значение от компонента и возвращает преобразованное значение. Например, поле FloatField преобразует данные в объект Python float или генерирует исключение ValidationError .
- Метод validate() поля Field заботится о конкретной проверке поля, что не подходит для валидатора. Он принимает значение, которое уже было преобразовано в правильный тип, и генерирует исключение в ValidationError случае возникновения ошибки. Этот метод ничего не возвращает и не должен изменять значение. Его можно перегружать для обработки логики проверки, которую вы не можете или не хотите добавлять в валидатор.
- Метод run_validators() поля запускает все валидаторы для поля и собирает все ошибки в одно исключение ValidationError . В принципе, отменять этот метод бесполезно.
- Метод clean() полевого подкласса отвечает за выполнение to_python() , validate() и run_validators() в правильном порядке, и распространение ошибок. Если в любой момент какой-либо из этих методов вызывает исключение ValidationError , проверка прекращается, и эта ошибка распространяется. Этот метод возвращает очищенные данные, которые затем вставляются в словарь cleaned_data формы.
- Метод clean_() вызывается для подкласса формы - где заменяется именем атрибута поля формы. Этот метод заботится о любой очистке, относящейся к этому атрибуту, независимо от типа задействованного поля. Этот метод не получает параметров. Вам нужно найти значение поля в 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_() . Наконец, после того, как эти два метода были выполнены для каждого поля, метод Form.clean() или его перегруженная версия выполняется во всех случаях, даже если предыдущие методы генерировали ошибки.
Примеры для каждого из этих методов показаны ниже.
Как уже упоминалось, каждый из этих методов может генерировать исключение ValidationError . Для каждого поля, если метод Field.clean() генерирует ошибку ValidationError , специфичный для поля метод очистки не вызывается. Однако методы очистки для всех других полей по-прежнему выполняются.
Генерация ValidationError ¶
Для большей гибкости сообщений об ошибках и облегчения их переопределения вот несколько рекомендаций:
-
Предоставьте конструктору code описательную ошибку:
# Good ValidationError(_('Invalid value'), code='invalid') # Bad ValidationError(_('Invalid value'))
# 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',), )
# 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 .
Валидаторы могут использоваться для проверки значений поля; Например, давайте посмотрим на поле SlugField Django:
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 все еще содержит адрес "[email protected]" . Это проверка, специфичная для нашей формы, поэтому мы не хотели помещать ее в общий класс 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 "[email protected]" 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." )
В этом коде, если возникает ошибка проверки, в верхней части формы отображается сообщение об ошибке (обычно), описывающее проблему. Такие ошибки являются неполевыми ошибками, которые отображаются в шаблоне с помощью . >
Вызов super().clean() в примере кода обеспечивает сохранение любой логики проверки в родительских классах. Если форма наследует от другого , который не возвращает словарь cleaned_data в методе clean() (это необязательно), не присвоить результат вызова super() к cleaned_data и вместо этого использовать 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 .