Form and field validation¶
Form validation happens when the data is cleaned. If you want to customize
this process, there are various places to make changes, each one serving a
different purpose. Three types of cleaning methods are run during form
processing. These are normally executed when you call the is_valid()
method on a form. There are other things that can also trigger cleaning and
validation (accessing the errors attribute or calling full_clean()
directly), but normally they won’t be needed.
In general, any cleaning method can raise ValidationError if there is a
problem with the data it is processing, passing the relevant information to
the ValidationError constructor. See below
for the best practice in raising ValidationError. If no ValidationError
is raised, the method should return the cleaned (normalized) data as a Python
object.
Most validation can be done using validators — helpers that can be reused.
Validators are functions (or callables) that take a single argument and raise
ValidationError on invalid input. Validators are run after the field’s
to_python and validate methods have been called.
Validation of a form is split into several steps, which can be customized or
overridden:
-
The
to_python()method on aFieldis the first step in every
validation. It coerces the value to a correct datatype and raises
ValidationErrorif that is not possible. This method accepts the raw
value from the widget and returns the converted value. For example, a
FloatFieldwill turn the data into a Pythonfloator raise a
ValidationError. -
The
validate()method on aFieldhandles field-specific validation
that is not suitable for a validator. It takes a value that has been
coerced to a correct datatype and raisesValidationErroron any error.
This method does not return anything and shouldn’t alter the value. You
should override it to handle validation logic that you can’t or don’t
want to put in a validator. -
The
run_validators()method on aFieldruns all of the field’s
validators and aggregates all the errors into a single
ValidationError. You shouldn’t need to override this method. -
The
clean()method on aFieldsubclass is responsible for running
to_python(),validate(), andrun_validators()in the correct
order and propagating their errors. If, at any time, any of the methods
raiseValidationError, the validation stops and that error is raised.
This method returns the clean data, which is then inserted into the
cleaned_datadictionary of the form. -
The
clean_<fieldname>()method is called on a form subclass – where
<fieldname>is replaced with the name of the form field attribute.
This method does any cleaning that is specific to that particular
attribute, unrelated to the type of field that it is. This method is not
passed any parameters. You will need to look up the value of the field
inself.cleaned_dataand remember that it will be a Python object
at this point, not the original string submitted in the form (it will be
incleaned_databecause the general fieldclean()method, above,
has already cleaned the data once).For example, if you wanted to validate that the contents of a
CharFieldcalledserialnumberwas unique,
clean_serialnumber()would be the right place to do this. You don’t
need a specific field (it’s aCharField), but you want a
formfield-specific piece of validation and, possibly, cleaning/normalizing
the data.The return value of this method replaces the existing value in
cleaned_data, so it must be the field’s value fromcleaned_data(even
if this method didn’t change it) or a new cleaned value. -
The form subclass’s
clean()method can perform validation that requires
access to multiple form fields. This is where you might put in checks such as
“if fieldAis supplied, fieldBmust contain a valid email address”.
This method can return a completely different dictionary if it wishes, which
will be used as thecleaned_data.Since the field validation methods have been run by the time
clean()is
called, you also have access to the form’serrorsattribute which
contains all the errors raised by cleaning of individual fields.Note that any errors raised by your
Form.clean()override will not
be associated with any field in particular. They go into a special
“field” (called__all__), which you can access via the
non_field_errors()method if you need to. If you
want to attach errors to a specific field in the form, you need to call
add_error().Also note that there are special considerations when overriding
theclean()method of aModelFormsubclass. (see the
ModelForm documentation for more information)
These methods are run in the order given above, one field at a time. That is,
for each field in the form (in the order they are declared in the form
definition), the Field.clean() method (or its override) is run, then
clean_<fieldname>(). Finally, once those two methods are run for every
field, the Form.clean() method, or its override, is executed whether
or not the previous methods have raised errors.
Examples of each of these methods are provided below.
As mentioned, any of these methods can raise a ValidationError. For any
field, if the Field.clean() method raises a ValidationError, any
field-specific cleaning method is not called. However, the cleaning methods
for all remaining fields are still executed.
Raising ValidationError¶
In order to make error messages flexible and easy to override, consider the
following guidelines:
-
Provide a descriptive error
codeto the constructor:# Good ValidationError(_('Invalid value'), code='invalid') # Bad ValidationError(_('Invalid value'))
-
Don’t coerce variables into the message; use placeholders and the
params
argument of the constructor:# Good ValidationError( _('Invalid value: %(value)s'), params={'value': '42'}, ) # Bad ValidationError(_('Invalid value: %s') % value)
-
Use mapping keys instead of positional formatting. This enables putting
the variables in any order or omitting them altogether when rewriting the
message:# Good ValidationError( _('Invalid value: %(value)s'), params={'value': '42'}, ) # Bad ValidationError( _('Invalid value: %s'), params=('42',), )
-
Wrap the message with
gettextto enable translation:# Good ValidationError(_('Invalid value')) # Bad ValidationError('Invalid value')
Putting it all together:
raise ValidationError( _('Invalid value: %(value)s'), code='invalid', params={'value': '42'}, )
Following these guidelines is particularly necessary if you write reusable
forms, form fields, and model fields.
While not recommended, if you are at the end of the validation chain
(i.e. your form clean() method) and you know you will never need
to override your error message you can still opt for the less verbose:
ValidationError(_('Invalid value: %s') % value)
The Form.errors.as_data() and
Form.errors.as_json() methods
greatly benefit from fully featured ValidationErrors (with a code name
and a params dictionary).
Raising multiple errors¶
If you detect multiple errors during a cleaning method and wish to signal all
of them to the form submitter, it is possible to pass a list of errors to the
ValidationError constructor.
As above, it is recommended to pass a list of ValidationError instances
with codes and params but a list of strings will also work:
# Good raise ValidationError([ ValidationError(_('Error 1'), code='error1'), ValidationError(_('Error 2'), code='error2'), ]) # Bad raise ValidationError([ _('Error 1'), _('Error 2'), ])
Using validation in practice¶
The previous sections explained how validation works in general for forms.
Since it can sometimes be easier to put things into place by seeing each
feature in use, here are a series of small examples that use each of the
previous features.
Using validators¶
Django’s form (and model) fields support use of utility functions and classes
known as validators. A validator is a callable object or function that takes a
value and returns nothing if the value is valid or raises a
ValidationError if not. These can be passed to a
field’s constructor, via the field’s validators argument, or defined on the
Field class itself with the default_validators
attribute.
Validators can be used to validate values inside the field, let’s have a look
at Django’s SlugField:
from django.core import validators from django.forms import CharField class SlugField(CharField): default_validators = [validators.validate_slug]
As you can see, SlugField is a CharField with a customized validator
that validates that submitted text obeys to some character rules. This can also
be done on field definition so:
is equivalent to:
slug = forms.CharField(validators=[validators.validate_slug])
Common cases such as validating against an email or a regular expression can be
handled using existing validator classes available in Django. For example,
validators.validate_slug is an instance of
a RegexValidator constructed with the first
argument being the pattern: ^[-a-zA-Z0-9_]+$. See the section on
writing validators to see a list of what is already
available and for an example of how to write a validator.
Form field default cleaning¶
Let’s first create a custom form field that validates its input is a string
containing comma-separated email addresses. The full class looks like this:
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)
Every form that uses this field will have these methods run before anything
else can be done with the field’s data. This is cleaning that is specific to
this type of field, regardless of how it is subsequently used.
Let’s create a ContactForm to demonstrate how you’d use this field:
class ContactForm(forms.Form): subject = forms.CharField(max_length=100) message = forms.CharField() sender = forms.EmailField() recipients = MultiEmailField() cc_myself = forms.BooleanField(required=False)
Use MultiEmailField like any other form field. When the is_valid()
method is called on the form, the MultiEmailField.clean() method will be
run as part of the cleaning process and it will, in turn, call the custom
to_python() and validate() methods.
Cleaning a specific field attribute¶
Continuing on from the previous example, suppose that in our ContactForm,
we want to make sure that the recipients field always contains the address
"fred@example.com". This is validation that is specific to our form, so we
don’t want to put it into the general MultiEmailField class. Instead, we
write a cleaning method that operates on the recipients field, like so:
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
Cleaning and validating fields that depend on each other¶
Suppose we add another requirement to our contact form: if the cc_myself
field is True, the subject must contain the word "help". We are
performing validation on more than one field at a time, so the form’s
clean() method is a good spot to do this. Notice that we are
talking about the clean() method on the form here, whereas earlier we were
writing a clean() method on a field. It’s important to keep the field and
form difference clear when working out where to validate things. Fields are
single data points, forms are a collection of fields.
By the time the form’s clean() method is called, all the individual field
clean methods will have been run (the previous two sections), so
self.cleaned_data will be populated with any data that has survived so
far. So you also need to remember to allow for the fact that the fields you
are wanting to validate might not have survived the initial individual field
checks.
There are two ways to report any errors from this step. Probably the most
common method is to display the error at the top of the form. To create such
an error, you can raise a ValidationError from the clean() method. For
example:
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." )
In this code, if the validation error is raised, the form will display an
error message at the top of the form (normally) describing the problem. Such
errors are non-field errors, which are displayed in the template with
{{ form.non_field_errors }}.
The call to super().clean() in the example code ensures that any validation
logic in parent classes is maintained. If your form inherits another that
doesn’t return a cleaned_data dictionary in its clean() method (doing
so is optional), then don’t assign cleaned_data to the result of the
super() call and use self.cleaned_data instead:
def clean(self): super().clean() cc_myself = self.cleaned_data.get("cc_myself") ...
The second approach for reporting validation errors might involve assigning the
error message to one of the fields. In this case, let’s assign an error message
to both the “subject” and “cc_myself” rows in the form display. Be careful when
doing this in practice, since it can lead to confusing form output. We’re
showing what is possible here and leaving it up to you and your designers to
work out what works effectively in your particular situation. Our new code
(replacing the previous sample) looks like this:
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)
The second argument of add_error() can be a string, or preferably an
instance of ValidationError. See Raising ValidationError for more
details. Note that add_error() automatically removes the field from
cleaned_data.
I create a registation app, where users can register providing a username, email and a password. What I did is make sure that the email field is unique(as you can see in the code below). But I can’t figure out how I can show the error in case the a user enters an email address that is already in use.
View
from django.shortcuts import render
from django.shortcuts import render_to_response
from django.http import HttpResponseRedirect
from django.core.context_processors import csrf
from forms import RegistrationForm
# Create your views here.
def register_user(request):
if request.method == 'POST':
form = RegistrationForm(request.POST)
if form.is_valid():
form.save()
return HttpResponseRedirect('../../membership/register_success')
else:
return HttpResponseRedirect('../../membership/register_failed')
args = {}
args.update(csrf(request))
args['form'] = RegistrationForm()
return render(request,'registration/registration_form.html', args)
def register_success(request):
return render_to_response('registration/registration_success.html')
def register_failed(request):
return render_to_response('registration/registration_failed.html')
Form
from django import forms
from django.contrib.auth.models import User
from django.contrib.auth.forms import UserCreationForm
from django.utils.translation import ugettext_lazy as _
# forms.py
class RegistrationForm(UserCreationForm):
email = forms.EmailField(required=True)
class Meta:
model = User
fields = ('username', 'email', 'password1', 'password2')
def clean_email(self):
email = self.cleaned_data.get('email')
username = self.cleaned_data.get('username')
if email and User.objects.filter(email=email).exclude(username=username).count():
raise forms.ValidationError(_("This email address is already in use. Please supply a different email address."))
return email
def save(self, commit=True):
user = super(RegistrationForm, self).save(commit=False)
user.email = self.cleaned_data['email']
if commit:
user.save()
return user
registration.html
{% extends "base.html" %}
{% block title %}Registration{% endblock %}
{% block content %}
<h1>Registration</h1>
{% if form.errors %}
<h1>ERRORRRRRR same email again???</h1>
{% endif %}
{% if registered %}
<strong>thank you for registering!</strong>
<a href="../../">Return to the homepage.</a><br />
{% else %}
<strong>register here!</strong><br />
<form method="post" action="/membership/register/">{% csrf_token %}
{{ form }}
<input type="submit" name="submit" value="Register" />
</form>
{% endif %}
{% endblock %}
![]()
Jon Clements
136k32 gold badges243 silver badges277 bronze badges
asked Mar 18, 2014 at 4:49
You’re showing the form with {{ form }} on the template. That itself should show all the validation errors by default, but in your case, you’re redirecting to some other page if the form is invalid. So you can never show the errors unless you pass the errors with the GET parameters. You could change your view to this to get the errors on the signup page itself —
def register_user(request):
args = {}
if request.method == 'POST':
form = RegistrationForm(request.POST)
if form.is_valid():
form.save()
return HttpResponseRedirect('../../membership/register_success')
else:
form = RegistrationForm()
args['form'] = form
return render(request,'registration/registration_form.html', args)
How this works is, if the request method is POST, the form gets initiated with the POST data, then it’s validated with the is_valid() call, so the form object now has the validation error messages if it’s invalid. If it’s valid, it’s saved and redirected. If not valid, it comes to the args['form'] = form part where the form object with the error messages is set to the context and then passed to render.
If the request method is not POST, then a form object with no data is instantiated and passed to render().
Now your template should show all the error messages just below each field if there is any error.
answered Mar 19, 2014 at 17:44
Bibhas DebnathBibhas Debnath
14.3k17 gold badges68 silver badges96 bronze badges
1
forms.py
from django import forms
class RegistForm(forms.Form):
name = forms.CharField(required=True)
email = forms.EmailField(required=True)
password = forms.CharField(required=True)
views.py
from django.shortcuts import render
from django.views.generic import TemplateView
import forms
class Register(TemplateView):
def get(self, request):
return render(request, 'register.html', {})
def post(self, request):
form = forms.RegistForm(request.POST)
if form.is_valid():
print(1)
else:
print(form.errors)
content = {'form':form};
return render(request, 'register.html', content)
register.html
<form action="{% url 'register' %}" method="post">
{% csrf_token %}
<fieldset>
<label for="name">Name:</label>
<input type="text" id="name" name="name" value="">
{{ form.errors.name }}
<label for="mail">Email:</label>
<input type="text" id="mail" name="email">
{{ form.errors.email }}
<label for="password">Password:</label>
<input type="password" id="password" name="password">
{{ form.errors.password }}
</fieldset>
<button type="submit">Sign Up</button>
<p class="message">Already registered? <a href="{% url 'login' %}">Login</a></p>
</form>
** Feel free to copy code and enjoy! **
![]()
SummerEla
1,8833 gold badges24 silver badges43 bronze badges
answered Jun 11, 2016 at 19:56
![]()
Why not just do something like this:
...
if User.objects.filter(email=email):
raise forms.ValidationError(_("This email address is already in use. Please supply a different email address."))
return email
...
If the user already registered, have it raise a validation error. If you don’t want it to do this, you can do something like:
...
email_exists = User.objects.filter(email=email):
if email_exists and email_exists.username != username:
raise forms.ValidationError(_("This email address is already in use. Please supply a different email address."))
return email
...
To display the form errors, you use form.is_valid() to make sure that it passes validation. Django says the following for custom validations:
Note that any errors raised by your Form.clean() override will not be associated with any field in particular. They go into a special “field” (called __all__), which you can access via the non_field_errors() method if you need to. If you want to attach errors to a specific field in the form, you need to call add_error().
Then in your template you can use something like {{ form.non_field_errors }}, etc.
See this section in the Django docs, under Using a form in a view and Customizing the form template:
https://docs.djangoproject.com/en/dev/topics/forms/
answered Mar 18, 2014 at 4:55
antimatterantimatter
3,1322 gold badges22 silver badges33 bronze badges
11
Class based views are easier.
from django.views import generic
from .forms import RegistrationForm
class RegistrationView(generic.CreateView):
template_class = 'registration/registration_form.html'
form_class = RegistrationForm
success_url = '/success/url'
def form_valid(self, form):
# add a log after save or whatever
super(RegistrationView, self).form_valid(self, form)
The clean methods are automatic with forms and messages render as such,
class based views make life a lot easier and your code DRYer.
answered Apr 7, 2015 at 20:53
Mikeec3Mikeec3
6493 silver badges9 bronze badges
Have resolved this error by the following:
from django.core.exceptions import ValidationError
try:
except ValidationError as e:
answered Apr 28, 2022 at 12:55
![]()
WalkWalk
1,42517 silver badges21 bronze badges
Валидация форм и полей¶
Валидация формы происходит при очистке данных. Если вы хотите настроить этот процесс, есть различные места для внесения изменений, каждое из которых служит для разных целей. В процессе обработки формы выполняются три типа методов очистки. Обычно они выполняются, когда вы вызываете метод is_valid() на форме. Есть и другие вещи, которые также могут вызвать очистку и проверку (обращение к атрибуту errors или прямой вызов full_clean()), но обычно они не нужны.
В общем, любой метод очистки может поднять ValidationError, если есть проблема с данными, которые он обрабатывает, передавая соответствующую информацию конструктору ValidationError. See below для лучшей практики поднятия ValidationError. Если не поднимается ValidationError, метод должен вернуть очищенные (нормализованные) данные в виде объекта Python.
Большинство валидаций можно выполнить с помощью validators — помощников, которые можно использовать повторно. Валидаторы — это функции (или callables), которые принимают один аргумент и вызывают 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, потому что метод general fieldclean(), описанный выше, уже однажды очистил данные).Например, если вы хотите проверить, что содержимое
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 documentation для получения дополнительной информации)
Эти методы выполняются в указанном выше порядке, по одному полю за раз. То есть, для каждого поля формы (в порядке их объявления в определении формы) выполняется метод 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() значительно выигрывают от полнофункциональных ValidationErrors (с code именем и params словарем).
Возникновение множества ошибок¶
Если вы обнаружили несколько ошибок во время работы метода очистки и хотите сигнализировать обо всех из них отправителю формы, можно передать список ошибок конструктору ValidationError.
Как и выше, рекомендуется передавать список экземпляров ValidationError с codes и 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’s SlugField:
from django.core import validators from django.forms import CharField class SlugField(CharField): default_validators = [validators.validate_slug]
Как вы можете видеть, SlugField — это CharField с настроенным валидатором, который проверяет, что отправленный текст соответствует некоторым правилам символов. Это также можно сделать при определении поля так:
эквивалентно:
slug = forms.CharField(validators=[validators.validate_slug])
Обычные случаи, такие как проверка по электронной почте или регулярному выражению, могут быть обработаны с помощью существующих классов валидаторов, доступных в Django. Например, validators.validate_slug — это экземпляр RegexValidator, построенный с первым аргументом в виде шаблона: ^[-a-zA-Z0-9_]+$. Смотрите раздел writing validators, чтобы увидеть список того, что уже доступно, и пример того, как написать валидатор.
Очистка полей формы по умолчанию¶
Давайте сначала создадим поле пользовательской формы, которое проверяет, что его входные данные — это строка, содержащая адреса электронной почты, разделенные запятыми. Полный класс выглядит следующим образом:
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. Более подробную информацию смотрите в Поднятие ValidationError. Обратите внимание, что add_error() автоматически удаляет поле из cleaned_data.
In this tutorial, we will learn how to show custom validation exceptions on Django admin.
Understanding The Problem
Django admin site will raise exceptions when we try to save objects with non-acceptable values. Often we need to create our custom validators and raise custom exceptions depending on the needs of the project. Like if you want the phone number to be in a particular format or you want to make sure the first name starts with a capital letter.
There are a lot of ways to achieve this but if your goal is to ensure proper validation limited from the admin interface only, then this tutorial is for you.
Note: If creating validators that remain constant throughout the app is your goal, then you should check out this article — Creating Custom Model Validation In Django
Displaying Custom Validation Exception in Django Admin
We will use the popular blog project for this tutorial. The goal is to ensure that the title of the post is in the proper title case.

Let’s have a look at our existing models.py and admin.py file.
models.py
class Post(models.Model):
title = models.CharField(max_length=200, unique=True)
slug = models.SlugField(max_length=200, unique=True)
author = models.ForeignKey(
User, on_delete=models.CASCADE, related_name="blog_posts")
updated_on = models.DateTimeField(auto_now=True)
content = models.TextField()
created_on = models.DateTimeField(auto_now_add=True)
status = models.IntegerField(choices=STATUS, default=0)
summary = models.CharField(max_length=500, null=True, blank=True)
class Meta:
ordering = ["-created_on"]
def __str__(self):
return self.title
admin.py
@admin.register(Post)
class PostAdmin(SummernoteModelAdmin):
list_display = ('title', 'slug', 'status', 'created_on')
list_filter = ('status', 'created_on')
search_fields = ['title', 'content']
prepopulated_fields = {'slug': ('title',)}
Now to make custom validators for the admin site we need to create a custom form for our model. So in the same file or in a separate file create a form like this.
from django import forms
class PostForm(forms.ModelForm):
def clean(self):
title = self.cleaned_data['title']
if not title.istitle():
raise forms.ValidationError({'title': "Not a proper titlecased string"})
The model form comes with the clean() method that is responsible for performing validations.
Take the title value from the cleaned_data dictionary and run the validation through it, if it fails the raise the ValidationError with the exception message.
Finally. we need to link our form with the PostAdmin.
@admin.register(Post)
class PostAdmin(SummernoteModelAdmin):
form = PostForm
list_display = ('title', 'slug', 'status', 'created_on')
list_filter = ('status', 'created_on')
search_fields = ['title', 'content']
prepopulated_fields = {'slug': ('title',)}
That’s it! Save the files run the server navigate to the admin and test the flow.
DJANGO
Built-in Form Field Validations in Django Forms are the default validations that come predefined to all fields. Every field comes in with some built-in validations from Django validators. Each Field class constructor takes some fixed arguments.
The error_messages argument lets you specify manual error messages for attributes of the field. The error_messages argument lets you override the default messages that the field will raise. Pass in a dictionary with keys matching the error messages you want to override. For example, here is the default error message:
>>> from django import forms
>>> generic = forms.CharField()
>>> generic.clean('')
Traceback (most recent call last):
...
ValidationError: ['This field is required.']
And here is a custom error message:
>>> name = forms.CharField(
error_messages={
'required': 'Please enter your name'
})
>>> name.clean('')
Traceback (most recent call last):
...
ValidationError: ['Please enter your name']
Syntax
field_name = models.Field(option = value)
Django Form Field Validation error_messages Explanation
Illustration of error_messages using an Example. Consider a project named geeksforgeeks having an app named geeks.
Refer to the following articles to check how to create a project and an app in Django.
- How to Create a Basic Project using MVT in Django?
- How to Create an App in Django ?
Enter the following code into forms.py file of geeks app. We will be using CharField for experimenting for all field options.
from django import forms
class GeeksForm(forms.Form):
geeks_field = forms.CharField(
error_messages = {
'required':"Please Enter your Name"
})
Add the geeks app to INSTALLED_APPS
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'geeks',
]
Now to render this form into a view we need a view and a URL mapped to that view. Let’s create a view first in views.py of geeks app,
from django.shortcuts import render
from .forms import GeeksForm
def home_view(request):
context = {}
form = GeeksForm(request.POST or None)
context['form'] = form
if request.POST:
if form.is_valid():
temp = form.cleaned_data.get("geeks_field")
print(temp)
return render(request, "home.html", context)
Here we are importing that particular form from forms.py and creating an object of it in the view so that it can be rendered in a template.
Now, to initiate a Django form you need to create home.html where one would be designing the stuff as they like. Let’s create a form in home.html.
<form method = "POST">
{% csrf_token %}
{{ form }}
<input type = "submit" value = "Submit">
</form>
Finally, a URL to map to this view in urls.py
from django.urls import path
from .views import home_view
URLpatterns = [
path('', home_view ),
]
Let’s run the server and check what has actually happened, Run
Python manage.py runserver

Now let’s try to submit it empty and check if required error_message has been overridden.

Thus the field is displaying a custom error message for required attribute of Charfield.
More Built-in Form Validations
| Field Options | Description |
|---|---|
| required | By default, each Field class assumes the value is required, so to make it not required you need to set required=False |
| label | The label argument lets you specify the “human-friendly” label for this field. This is used when the Field is displayed in a Form. |
| label_suffix | The label_suffix argument lets you override the form’s label_suffix on a per-field basis. |
| widget | The widget argument lets you specify a Widget class to use when rendering this Field. See Widgets for more information. |
| help_text | The help_text argument lets you specify descriptive text for this Field. If you provide help_text, it will be displayed next to the Field when the Field is rendered by one of the convenience Form methods. |
| error_messages | The error_messages argument lets you override the default messages that the field will raise. Pass in a dictionary with keys matching the error messages you want to override. |
| validators | The validators argument lets you provide a list of validation functions for this field. |
| localize | The localize argument enables the localization of form data input, as well as the rendered output. |
| disabled. | The disabled boolean argument, when set to True, disables a form field using the disabled HTML attribute so that it won’t be editable by users. |
14.03.2012
Думаю, вы хотите, чтобы ваши сообщения об ошибках в заполняемых формах были на том же языке, что и сам сайт. Один из простых способов — это добавить следующий код в соответствующий forms.py. Затем формы надо будет наследовать не от forms.Form, а от MyForm (обратите внимание, ExampleForm, в примере ниже, наследуется от него).
class MyForm(forms.Form):
def __init__(self, *args, **kwargs):
super(MyForm, self).__init__(*args, **kwargs)
for k, field in self.fields.items():
if 'required' in field.error_messages:
field.error_messages['required'] = u'Это поле обязательно!'
class ExampleForm(MyForm):
title = forms.CharField(max_length=100, required=True, label=u'Название')
Полный список error_messages для различных типов полей можно увидеть, если просмотреть этот раздел: https://docs.djangoproject.com/en/1.3/ref/forms/fields/#built-in-field-classes
Вот что есть на данный момент:
required — показывается, если данное поле обязательно;
max_length — если превышено максимальное количество символов в символьном поле / в случае с файлами — длина имени файла;
min_length — если символов меньше, чем должно быть, в символьном поле;
invalid_choice — если выбран невозможный choice;
invalid — при неправильном email’е и прочем неправильном вводе данных;
max_value — если превышено числовое значение;
min_value — если значение меньше минимального числового ограничения;
max_digits — если превышено количество цифр в числе;
max_decimal_places — если превышено количество цифр после запятой;
max_whole_digits — если превышено количество цифр до запятой;
missing — если файл не найден;
empty — если файл пустой;
invalid_image — если изображение повреждено;
invalid_list — если неправильный список choice’ов;
invalid_link — для URLField — вызывается, если данного url не существует.
Основано на примере с http://stackoverflow.com/questions/1481771/django-override-default-form-error-messages
django
- Назад (en-US)
- Обзор: Django
- Далее
На этом уроке мы покажем вам процесс работы с HTML-формами в Django. В частности, продемонстрируем самый простой способ построения формы для создания, обновления и удаления экземпляров модели. При этом мы расширим сайт местной библиотеки, чтобы библиотекари могли обновлять книги, создавать, обновлять и удалять авторов, используя наши собственные формы (а не возможности приложения администратора).
| Необходимые условия: | Завершите все предыдущие учебные темы, в том числе Django руководство часть 8: Аутентификация пользователя и права доступа (en-US). |
|---|---|
| Цель: | Научиться понимать, как создавать формы, чтобы получать информацию от пользователей и обновлять базу данных. Узнать, как обобщённые классы отображения форм могут значительно упростить процесс создания форм при работе с одной моделью. |
Обзор
HTML форма — это группа из одного или нескольких полей/виджетов на веб-странице, которая используется для сбора информации от пользователей для последующей отправки на сервер. Формы являются гибким механизмом сбора пользовательских данных, поскольку имеют целый набор виджетов для ввода различных типов данных, как то: текстовые поля, флажки, переключатели, установщики дат и т. д. Формы являются относительно безопасным способом взаимодействия пользовательского клиента и сервера, поскольку они позволяют отправлять данные в POST-запросах, применяя защиту от Межсайтовой подделки запроса (Cross Site Request Forgery — CSRF)
Пока что мы не создавали каких-либо форм в этом учебнике, но мы встречались с ними в административной панели Django — например, снимок экрана ниже показывает форму для редактирования одной из наших моделей книг (Book), состоящую из нескольких списков выбора и текстовых редакторов.
Работа с формами может быть достаточно сложной! Разработчикам надо описать форму на HTML, проверить её валидность, а также, на стороне сервера, проверять введённые пользователем данные (а возможно и на стороне клиента), далее, в случае возникновения ошибок необходимо опять показать пользователю форму и, при этом, указать на то, что пошло не так, в случае же успеха проделать с данными необходимые операции и каким-то образом проинформировать об этом пользователя. Django, при работе с формами, берёт большую часть, описанной выше работы, на себя. Он предоставляет фреймворк, который позволяет вам определять форму и её поля программно, а затем использовать эти объекты и для генерации непосредственно кода HTML-формы, и для контроля за процессом валидации и других пользовательский взаимодействий с формой.
В данной части руководства мы покажем вам несколько способов создания и работы с формами и, в частности, как применение обобщённых классов работы с формой могут значительно уменьшить необходимый объем работы. Кроме того, мы расширим возможности нашего сайта LocalLibrary, путём добавления функциональности для библиотекарей, которая будет позволять им обновлять информацию — добавим страницы для создания, редактирования, удаления книг и авторов (воспроизведём и расширим стандартные возможности административной части сайта).
Формы HTML
Начнём мы с краткого обзора Форм HTML. Рассмотрим простую форму HTML, имеющую поле для ввода имени некоторой «команды» («team»), и, связанную с данным полем, текстовой меткой:
Форма описывается на языке HTML как набор элементов, расположенных внутри парных тэгов <form>...</form>. Любая форма содержит как минимум одно поле-тэг input типа type="submit".
<form action="/team_name_url/" method="post">
<label for="team_name">Enter name: </label>
<input id="team_name" type="text" name="name_field" value="Default name for team.">
<input type="submit" value="OK">
</form>
Здесь у нас только одно поле для ввода имени команды, но форма может иметь любое количество элементов ввода и, связанных с ними, текстовых меток. Атрибут элемента type определяет какого типа виджет будет показан в данной строке. Атрибуты name и id используются для однозначной идентификации данного поля в JavaScript/CSS/HTML, в то время как value содержит значение для поля (когда оно показывается в первый раз). Текстовая метка добавляется при помощи тэга label (смотрите «Enter name», в предыдущем фрагменте) и имеет атрибут for со значением идентификатора id, того поля, с которым данная текстовая метка связана.
Элемент input с type="submit" будет показана как кнопка (по умолчанию), нажав на которую, пользователь отправляет введённые им данные на сервер (в данном случае только значение поля с идентификатором team_name). Атрибуты формы определяют каким методом будут отправлены данные на сервер (атрибут method) и куда (атрибут action):
action: Это ресурс/URL-адрес куда будут отправлены данные для обработки. Если значение не установлено (то есть, значением поля является пустая строка), тогда данные будут отправлены в отображение (функцию, или класс), которое сформировало текущую страницу.method: HTTP-метод, используемый для отправки данных: post, или get.- Метод
POSTдолжен всегда использоваться если отправка данных приведёт к внесению изменений в базе данных на сервере. Применение данного метода должно повысить уровень защиты от CSRF. - Метод
GETдолжен применяться только для форм, действия с которыми не приводят к изменению базы данных (например для поисковых запросов). Кроме того, данный метод рекомендуется применять для создания внешних ссылок на ресурсы сайта.
- Метод
Ролью сервера в первую очередь является отрисовка начального состояния формы — либо содержащей пустые поля, либо с установленными начальными значениями. После того как пользователь нажмёт на кнопку, сервер получит все данные формы, а затем должен провести их валидацию. В том случае, если форма содержит неверные данные, сервер должен снова отрисовать форму, показав при этом поля с правильными данными, а также сообщения, описывающие «что именно пошло не так». В тот момент, когда сервер получит запрос с «правильными» данными он должен выполнить все необходимые действия (например, сохранение данных, возврат результата поиска, загрузка файла и тому подобное), а затем, в случае необходимости, проинформировать пользователя.
Как вы видите, создание HTML-формы, валидация и возврат данных, переотрисовка введённых значений, при необходимости, а также выполнение желаемых действий с «правильными данными», в целом, может потребовать довольно больших усилий для того, чтобы все «заработало». Django делает этот процесс намного проще, беря на себя некоторые «тяжёлые» и повторяющиеся участки кода!
Процесс управления формой в Django
Управление формами в Django использует те же самые техники, которые мы изучали в предыдущих частях руководства (при показе информации из наших моделей): отображение получает запрос, выполняет необходимые действия, включающие в себя чтение данных из моделей, генерацию и возврат страницы HTML (из шаблона, в который передаётся контекст, содержащий данные, которые и будут показаны). Что делает данный процесс более сложным, так это то, что серверной части надо дополнительно обработать данные, предоставленные пользователем и, в случае возникновения ошибок, снова перерисовать страницу.
Диаграмма, представленная ниже, демонстрирует процесс работы с формой в Django, начиная с запроса страницы, содержащей форму (выделено зелёным цветом).
В соответствии с данной диаграммой, главными моментами, которые берут на себя формы Django являются:
- Показ формы по умолчанию при первом запросе со стороны пользователя.
- Форма может содержать пустые поля (например, если вы создаёте новую запись в базе данных), или они (поля) могут иметь начальные значения (например, если вы изменяете запись, или хотите заполнить её каким-либо начальным значением).
- Форма в данный момент является несвязанной, потому что она не ассоциируется с какими-либо введёнными пользователем данными (хотя и может иметь начальные значения).
- Получение данных из формы (из HTML-формы) со стороны клиента и связывание их с формой (классом формы) на стороне сервера.
- Связывание данных с формой означает, что данные, введённые пользователем, а также возможные ошибки, при переотрисовке в дальнейшем, будут относиться именно к данной форме, а не к какой-либо ещё.
- Очистка и валидация данных.
- Очистка данных — это их проверка на наличие возможных значений, или вставок в поля ввода (то есть очистка — это удаление неправильных символов, которые потенциально могут использоваться для отправки вредоносного содержимого на сервер), с последующей конвертацией очищенных данных в подходящие типы данных Python.
- Валидация проверяет, значения полей (например, правильность введённых дат, их диапазон и так далее)
- Если какие-либо данные являются неверными, то выполнение перерисовки формы, но на этот раз, с уже введёнными пользователем данными и сообщениями об ошибках, описывающих возникшие проблемы.
- Если все данные верны, то исполнение необходимых действий (например, сохранение данных, отправка писем, возврат результата поиска, загрузка файла и так далее)
- Когда все действия были успешно завершены, то перенаправление пользователя на другую страницу.
Django предоставляет несколько инструментов и приёмов, которые помогают вам во время выполнения задач, описанных выше. Наиболее фундаментальным из них является класс Form, который упрощает генерацию HTML-формы и очистку/валидацию её данных. В следующем разделе мы опишем процесс работы с формами при помощи практического примера по созданию страницы, которая позволит библиотекарям обновлять информацию о книгах.
Примечание: Понимание того, как используется класс Form поможет вам когда мы будем рассматривать классы фреймворка Django, для работы с формами более «высокого уровня».
HTML-форма обновления книги. Класс Form и функция отображения
Данная глава будет посвящена процессу создания страницы, которая позволит библиотекарям обновлять информацию о книгах (в частности, вводить дату возврата книги). Для того, чтобы сделать это мы создадим форму, которая позволит пользователям вводить значение дат. Мы проинициализируем поле датой, равной 3 неделям, начиная с текущего дня, и, для того, чтобы библиотекарь не имел возможность ввести «неправильную» дату, мы добавим валидацию введённых значений, которая будет проверять, чтобы введённая дата не относилась к прошлому, или к слишком далёкому будущему. Когда будет получена «правильная» дата мы запишем её значение в поле BookInstance.due_back.
Данный пример будет использовать отображение на основе функции, а также продемонстрирует работу с классом Form. Следующие разделы покажут изменения, которые вам надо сделать, чтобы продемонстрировать работу форм в проекте LocalLibrary.
Класс Form
Класс Form является сердцем системы Django при работе с формами. Он определяет поля формы, их расположение, показ виджетов, текстовых меток, начальных значений, валидацию значений и сообщения об ошибках для «неправильных» полей (если таковые имеются). Данный класс, кроме того, предоставляет методы для отрисовки самого себя в шаблоне при помощи предопределённых форматов (таблицы, списки и так далее), или для получения значения любого элемента (позволяя выполнять более точную отрисовку).
Объявление класса формы Form
Синтаксис объявления для класса формы Form очень похож на объявление класса модели Model, он даже использует те же типы полей (и некоторые похожие параметры). Это существенный момент, поскольку в обоих случаях нам надо убедиться, что каждое поле управляет правильным типом данных, соответствует нужному диапазону (или другому критерию) и имеет необходимое описание для показа/документации.
Для того, чтобы создать класс с возможностями базового класса Form мы должны импортировать библиотеку forms, наследовать наш класс от класса Form, а затем объявить поля формы. Таким образом, самый простой класс формы в нашем случае будет иметь вид, показанный ниже:
from django import forms
class RenewBookForm(forms.Form):
renewal_date = forms.DateField(help_text="Enter a date between now and 4 weeks (default 3).")
Поля формы
В нашем случае мы имеем одно поле типа DateField, которое служит для ввода обновлённой даты возврата книги, которое будет отрендерено в HTML с пустым значением и текстовой меткой «Renewal date:«, а также текстовым описанием: «Enter a date between now and 4 weeks (default 3 weeks).» Так как никаких дополнительных опций мы не определяем, то поле будет «получать» даты в следующем формате input_formats: YYYY-MM-DD (2016-11-06), MM/DD/YYYY (02/26/2016), MM/DD/YY (10/25/16), а для отрисовки по умолчанию, будет использовать виджет: DateInput.
Существует множество других типов полей для класса формы, которые по своей функциональности подобны соответствующим им эквивалентам типов полей для классов моделей: BooleanField, CharField, ChoiceField, TypedChoiceField, DateField, DateTimeField, DecimalField, DurationField, EmailField, FileField, FilePathField, FloatField, ImageField, IntegerField, GenericIPAddressField, MultipleChoiceField, TypedMultipleChoiceField, NullBooleanField, RegexField, SlugField, TimeField, URLField, UUIDField, ComboField, MultiValueField, SplitDateTimeField, ModelMultipleChoiceField, ModelChoiceField.
Общие аргументы для большинства полей перечислены ниже:
- required: Если
True, то данное поле не может быть пустым, или иметь значениеNone. Данное значение установлено по умолчанию. - label: Текстовая метка, используемая для рендеринга поля в HTML-код. Если label не определена, то Django попытается создать её значение при помощи имени поля, переводя первый символ в верхний регистр, а также заменяя символы подчёркивания пробелами (например, для переменной с именем renewal_date, будет создан следующий текст метки: Renewal date).
- label_suffix: По умолчанию показывает двоеточие после текста метки (например, Renewal date**:**). Данный параметр позволяет вам указать любой суффикс по вашему желанию.
- initial: Начальное значение для поля при показе формы.
- widget: Применяемый виджет для поля.
- help_text (как показано в примере выше): Дополнительный текст, который может быть показан на форме, для описания того, как использовать поле.
- error_messages: Список сообщений об ошибках для данного поля. Вы можете переопределить его своими сообщениями, при необходимости.
- validators: Список функций, которые будут вызваны для валидации, введённого в поле значения.
- localize: Позволяет осуществить локализацию данных поля формы (например, формат ввода числовых значений, или дат).
- disabled: Если установлено в
True, то поле показывается, но его значение изменить нельзя. По умолчанию равноFalse.
Валидация
Django предоставляет несколько мест где вы можете осуществить валидацию ваших данных. Простейшим способом проверки значения одиночного поля является переопределение метода clean_<fieldname>() (здесь, <fieldname> это имя поля, которое вы хотите проверить). Например, мы хотим проверить, что введённое значение renewal_date находится между текущей датой и 4 неделями в будущем. Для этого мы создаём метод clean_renewal_date(), как показано ниже:
from django import forms
from django.core.exceptions import ValidationError
from django.utils.translation import ugettext_lazy as _
import datetime #for checking renewal date range.
class RenewBookForm(forms.Form):
renewal_date = forms.DateField(help_text="Enter a date between now and 4 weeks (default 3).")
def clean_renewal_date(self):
data = self.cleaned_data['renewal_date']
#Проверка того, что дата не выходит за "нижнюю" границу (не в прошлом).
if data < datetime.date.today():
raise ValidationError(_('Invalid date - renewal in past'))
#Проверка того, то дата не выходит за "верхнюю" границу (+4 недели).
if data > datetime.date.today() + datetime.timedelta(weeks=4):
raise ValidationError(_('Invalid date - renewal more than 4 weeks ahead'))
# Помните, что всегда надо возвращать "очищенные" данные.
return data
Необходимо отметить два важных момента. Первый это то, что мы получаем наши данные при помощи словаря self.cleaned_data['renewal_date'], а затем в конце возвращаем полученное значение, для проведения необходимых проверок. Данный шаг позволяет нам, при помощи валидаторов, получить «очищенные», проверенные, а затем, приведённые к стандартным типам, данные (в нашем случае к типу Python datetime.datetime).
Второй момент касается того случая, когда наше значение «выпадает за рамки» и мы «выкидываем» исключение ValidationError, в котором указываем текст, который мы хотим показать на форме, для случая когда были введены неправильные данные. Пример, показанный выше, оборачивает данный текст при помощи функции перевода Django ugettext_lazy() (импортируемую через _()), которая может вам пригодиться, если вы планируете перевести ваш сайт в будущем.
Примечание: Существует множество других методов и примеров валидации различных форм, которые можно найти в Формы и валидация поля (Django docs). Например, в случае, если у вас имеется много полей, которые зависят один от другого, вы можете переопределить функцию Form.clean() и, при необходимости, «выкинуть» ValidationError.
В целом, это все, что нам понадобится для создания формы в данном примере!
Копирование класса формы
Создайте и откройте файл locallibrary/catalog/forms.py, а затем скопируйте в него весь код, указанный в предыдущем фрагменте.
Конфигурация URL-адресов
Перед созданием отображения давайте добавим соответствующую конфигурацию URL-адреса для страницы обновления книг. Скопируйте следующий фрагмент в нижнюю часть файла locallibrary/catalog/urls.py.
urlpatterns += [
url(r'^book/(?P<pk>[-w]+)/renew/$', views.renew_book_librarian, name='renew-book-librarian'),
]
Данная конфигурация перенаправит запросы с адресов формата /catalog/book/<bookinstance id>/renew/ в функции с именем renew_book_librarian() в views.py, туда же передаст идентификатор id записи BookInstance в качестве параметра с именем pk. Шаблон соответствует только если pk это правильно отформатированный uiid.
Примечание: Вместо имени «pk» мы можем использовать любое другое, по нашему желанию, потому что мы имеем полный контроль над функцией отображения (которого у нас нет в случае использования встроенного обобщённого класса отображения, который ожидает параметр с определённым именем). Тем не менее имя pk является понятным сокращением от «primary key», поэтому мы его тут и используем!
Отображение
Как было отмечено в разделе Процесс управление формой в Django, отображение должно отрендерить форму по умолчанию, когда она вызывается в первый раз и, затем, перерендерить её, в том случае, если возникли какие-либо ошибки при работе с её полями. В случае же успеха, после обработки «правильных» данных отображение перенаправляет пользователя на новую (другую) страницу. Для того чтобы выполнить все эти действия, отображение должно знать вызвано ли оно в первый раз для отрисовки формы по умолчанию, а если это не так, то провести валидацию полученных данных.
Для форм, которые используют POST-запрос при отправке информации на сервер, наиболее общей схемой проверки данного факта является следующая строка кода if request.method == 'POST':. GET-запросу, а также первому запросу формы, в таком случае соответствует блок else. Если вы хотите отправлять свои данные в виде GET-запроса, то в таком случае приёмом проверки того факта, что данный запрос первый (или последующий), является получение значения какого-либо поля формы (например, если значение скрытого поля формы пустое, то данный вызов является первым).
Процесс обновления книги приводит к изменению информации в базе данных, таким образом, в соответствии с нашими соглашениями, в таком случае мы должны применять запрос типа POST. Фрагмент кода, представленный ниже, показывает (наиболее общую) схему работы для таких запросов.
from django.shortcuts import get_object_or_404
from django.http import HttpResponseRedirect
from django.urls import reverse
import datetime
from .forms import RenewBookForm
def renew_book_librarian(request, pk):
book_inst = get_object_or_404(BookInstance, pk=pk)
# Если данный запрос типа POST, тогда
if request.method == 'POST':
# Создаём экземпляр формы и заполняем данными из запроса (связывание, binding):
form = RenewBookForm(request.POST)
# Проверка валидности данных формы:
if form.is_valid():
# Обработка данных из form.cleaned_data
#(здесь мы просто присваиваем их полю due_back)
book_inst.due_back = form.cleaned_data['renewal_date']
book_inst.save()
# Переход по адресу 'all-borrowed':
return HttpResponseRedirect(reverse('all-borrowed') )
# Если это GET (или какой-либо ещё), создать форму по умолчанию.
else:
proposed_renewal_date = datetime.date.today() + datetime.timedelta(weeks=3)
form = RenewBookForm(initial={'renewal_date': proposed_renewal_date,})
return render(request, 'catalog/book_renew_librarian.html', {'form': form, 'bookinst':book_inst})
В первую очередь мы импортируем наш класс формы (RenewBookForm), а также другие необходимые объекты и методы:
get_object_or_404(): Возвращает определённый объект из модели в зависимости от значения его первичного ключа, или выбрасывает исключениеHttp404, если данной записи не существует.HttpResponseRedirect: Данный класс перенаправляет на другой адрес (HTTP код статуса 302).reverse(): Данная функция генерирует URL-адрес при помощи соответствующего имени URL конфигурации/преобразования и дополнительных аргументов. Это эквивалент Python тэгуurl, которые мы использовали в наших шаблонах.datetime: Библиотека Python для работы с датами и временим.
В отображении аргумент pk мы используем в функции get_object_or_404() для получения текущего объекта типа BookInstance (если его не существует, то функция, а следом и наше отображение прервут своё выполнение, а на странице пользователя отобразится сообщение об ошибке: «объект не найден»). Если запрос вызова отображения не является POST-запросом, то мы переходим к условному блоку else, в котором мы создаём форму по умолчанию и передаём ей начальное значения initial для поля renewal_date (выделено жирным ниже, — 3 недели, начиная с текущей даты).
book_inst = get_object_or_404(BookInstance, pk=pk)
# Если это GET (или другой метод), тогда создаём форму по умолчанию
else:
proposed_renewal_date = datetime.date.today() + datetime.timedelta(weeks=3)
form = RenewBookForm(initial={'renewal_date': proposed_renewal_date,})
return render(request, 'catalog/book_renew_librarian.html', {'form': form, 'bookinst':book_inst})
После создания формы мы вызываем функцию render(), чтобы создать HTML страницу; передаём ей в качестве параметров шаблон и контекст, который содержит объект формы. Кроме того, контекст содержит объект типа BookInstance, который мы будем использовать в шаблоне, для получения информации об обновляемой книге.
Если все таки у нас POST-запрос, тогда мы создаём объект с именем form и заполняем его данными, полученными из запроса. Данный процесс называется связыванием (или, биндингом, от англ. «binding») и позволяет нам провести валидацию данных. Далее осуществляется валидация формы, при этом проверяются все поля формы — для этого используются как код обобщённого класса, так и пользовательских функций, в частности нашей функции проверки введённых дат clean_renewal_date().
book_inst = get_object_or_404(BookInstance, pk=pk)
# Если данный запрос типа POST, тогда
if request.method == 'POST':
# Создаём экземпляр формы и заполняем данными из запроса (связывание, binding):
form = RenewBookForm(request.POST)
# Проверка валидности формы:
if form.is_valid():
# process the data in form.cleaned_data as required (here we just write it to the model due_back field)
book_inst.due_back = form.cleaned_data['renewal_date']
book_inst.save()
# redirect to a new URL:
return HttpResponseRedirect(reverse('all-borrowed') )
return render(request, 'catalog/book_renew_librarian.html', {'form': form, 'bookinst':book_inst})
Если формы не прошла валидацию, то мы снова вызываем функцию render(), но на этот раз форма будет содержать сообщения об ошибках.
Если форма прошла валидацию, тогда мы можем начать использовать данные, получая их из атрибута формы form.cleaned_data (то есть, data = form.cleaned_data['renewal_date']). Здесь мы просто сохраняем данные в поле due_back, соответствующего объекта BookInstance.
Предупреждение: Важно: Хотя вы также можете получить доступ к данным формы непосредственно через запрос (например request.POST['renewal_date'], или request.GET['renewal_date'] (в случае GET-запроса), это НЕ рекомендуется. Очищенные данные проверены на вредоносность и преобразованы в типы, совместимые с Python.
Последним шагом в части обработки формы представления является перенаправление на другую страницу, обычно страницу «Успех». В нашем случае мы используем объект класса HttpResponseRedirect и функцию reverse() для перехода к отображению с именем 'all-borrowed' (это было домашним заданием в Руководство часть 8: Аутентификация и разграничение доступа (en-US)). Если вы не создали данную страницу, то просто укажите переход на домашнюю страницу сайта по адресу ‘/’).
Все это необходимо для управления формой как таковой, но нам нужно как-то ограничить доступ к отображению (открыть доступ только библиотекарям). Мы могли бы создать новое разрешение (permission) в классе BookInstance («can_renew«), но мы пойдём простым путём и воспользуемся функцией-декоратором @permission_required вместе с нашим существующим разрешением can_mark_returned.
Окончательный вид отображения показан ниже. Пожалуйста, скопируйте данный текст в нижнюю часть файла locallibrary/catalog/views.py.
from django.contrib.auth.decorators import permission_required
from django.shortcuts import get_object_or_404
from django.http import HttpResponseRedirect
from django.urls import reverse
import datetime
from .forms import RenewBookForm
@permission_required('catalog.can_mark_returned')
def renew_book_librarian(request, pk):
"""
View function for renewing a specific BookInstance by librarian
"""
book_inst = get_object_or_404(BookInstance, pk=pk)
# If this is a POST request then process the Form data
if request.method == 'POST':
# Create a form instance and populate it with data from the request (binding):
form = RenewBookForm(request.POST)
# Check if the form is valid:
if form.is_valid():
# process the data in form.cleaned_data as required (here we just write it to the model due_back field)
book_inst.due_back = form.cleaned_data['renewal_date']
book_inst.save()
# redirect to a new URL:
return HttpResponseRedirect(reverse('all-borrowed') )
# If this is a GET (or any other method) create the default form.
else:
proposed_renewal_date = datetime.date.today() + datetime.timedelta(weeks=3)
form = RenewBookForm(initial={'renewal_date': proposed_renewal_date,})
return render(request, 'catalog/book_renew_librarian.html', {'form': form, 'bookinst':book_inst})
Шаблон
Создайте шаблон, на который ссылается наше отображение (/catalog/templates/catalog/book_renew_librarian.html) и скопируйте в него код, указанный ниже:
{% extends "base_generic.html" %}
{% block content %}
<h1>Renew: {{bookinst.book.title}}</h1>
<p>Borrower: {{bookinst.borrower}}</p>
<p{% if bookinst.is_overdue %} class="text-danger"{% endif %}>Due date: {{bookinst.due_back}}</p>
<form action="" method="post">
{% csrf_token %}
<table>
{{ form }}
</table>
<input type="submit" value="Submit" />
</form>
{% endblock %}
Большая его часть вам знакома из предыдущих частей руководства. Мы расширяем базовый шаблон, а затем замещаем блок содержимого content. У нас имеется возможность ссылаться на переменную {{bookinst}} (и её поля) поскольку мы передали её в объект контекста при вызове функции render(). Здесь мы используем данный объект для вывода заголовка книги, дат её получения и возврата.
Код формы относительно прост. В первую очередь мы объявляем тэг form, затем определяем куда будут отправлены данные (action) и каким способом (method, в данном случае «HTTP POST») — если обратитесь к обзору раздела Формы HTML в верхней части данной страницы, то найдёте там замещение, что пустое значение атрибута action, означает, что данные из формы будут переданы обратно по текущему URL-адресу данной страницы (чего мы и хотим!). Внутри тэга формы мы объявляем кнопку submit при помощи которой мы можем отправить наши данные. Блок {% csrf_token %}, добавленный первой строкой внутри блока формы, является частью фреймворка Django и служит для борьбы с CSRF.
Примечание: Добавляйте {% csrf_token %} в каждый шаблон Django, в котором вы создаёте форму для отправки данных методом POST. Это поможет уменьшить вероятность взлома вашего сайта злоумышленниками.
Все что осталось, это указать переменную {{form}}, которую мы передали в шаблон в словаре контекста. Возможно это вас не удивит, но таким образом мы предоставим возможность форме отрендерить свои поля с их метками, виджетами и дополнительными текстами, и в результате мы получим следующее:
<tr>
<th><label for="id_renewal_date">Renewal date:</label></th>
<td>
<input id="id_renewal_date" name="renewal_date" type="text" value="2016-11-08" required />
<br />
<span class="helptext">Enter date between now and 4 weeks (default 3 weeks).</span>
</td>
</tr>
Примечание: Возможно это не очевидно, поскольку наша форма содержит только одно поле, но по умолчанию каждое поле формы помещается в её собственную строку таблицы (поэтому переменная {{form}} находится внутри тэга table. Тот же результат можно получить, если воспользоваться следующим вызовом {{ form.as_table }}.
Если вы ввели неправильную дату, то на странице вы должны получить список сообщений об ошибках (показано жирным ниже).
<tr>
<th><label for="id_renewal_date">Renewal date:</label></th>
<td>
<ul class="errorlist">
<li>Invalid date - renewal in past</li>
</ul>
<input id="id_renewal_date" name="renewal_date" type="text" value="2015-11-08" required />
<br />
<span class="helptext">Enter date between now and 4 weeks (default 3 weeks).</span>
</td>
</tr>
Другие варианты применения переменной шаблона form
В простом случае применения {{form}} как показано выше, каждое поле рендерится в виде отдельной строки таблицы. Кроме того, вы можете отрендерить каждое поле как список элементов ({{form.as_ul}} ), или как параграф ({{form.as_p}}).
Что ещё больше вдохновляет, так это то, что вы можете полностью контролировать процесс рендеринга любой части формы, используя для этого дот-нотацию (точку). Например, мы можем получить доступ к следующим полям поля формы renewal_date:
{{form.renewal_date}}:само поле.{{form.renewal_date.errors}}: Список ошибок.{{form.renewal_date.id_for_label}}: Идентификатор текстовой метки.{{form.renewal_date.help_text}}: Дополнительный текст.- и так далее!
Примеры того как вручную отрендерить формы в шаблонах, а также пробежать циклом по шаблонным полям, смотрите Работы с формами > Ручная работа с формами (Django docs).
Тестирование страницы
Если вы выполнили задание в Django руководство часть 8: Аутентификация и разрешение доступа (en-US), то у вас должна быть страница со списком всех книг в наличии библиотеки и данный список (страница) должен быть доступен только её сотрудникам. На данной странице в каждом пункте (для каждой книги) мы можем добавить ссылку на нашу новую страницу обновления книги.
{% if perms.catalog.can_mark_returned %}- <a href="{% url 'renew-book-librarian' bookinst.id %}">Renew</a> {% endif %}
Примечание: Помните что, для того чтобы перейти на страницу обновления книги, ваш тестовый логин должен иметь разрешение доступа типа «catalog.can_mark_returned«(возможно надо воспользоваться вашим аккаунтом для суперпользователя).
Вы можете попробовать вручную создать URL-адрес для тестирования, например — http://127.0.0.1:8000/catalog/book/<bookinstance_id>/renew/ (правильный идентификатор записи id для bookinstance можно получить, если перейти на страницу детальной информации книги и скопировать поле id).
Как теперь все это выглядит?
Если все получилось как надо, то форма по умолчанию должна выглядеть следующим образом:
А такой наша форма будет в случае ввода неправильной даты:
Список всех книг с ссылками на страницу обновления данных:
Класс ModelForm
Создание класса формы Form при помощи примера, описанного выше, является довольно гибким способом, позволяющим вам создавать формы любой структуры которую вы пожелаете, в связке с любой моделью, или моделями.
Тем не менее, если вам просто нужна форма для отображения полей одиночной модели, тогда эта самая модель уже содержит большую часть информации, которая вам нужна для построения формы: сами поля, текстовые метки, дополнительный текст и так далее. И чтобы не воспроизводить информацию из модели для вашей формы, проще воспользоваться классом ModelForm, который помогает создавать формы непосредственно из модели. Класс ModelForm может применяться в ваших отображениях точно таким же образом как и «классический» класс формы Form.
Базовая реализация ModelForm содержит тоже поле как и ваш предыдущий класс формы RenewBookForm, что и показано ниже. Все что вам необходимо сделать, — внутри вашего нового класса добавить класс Meta и связать его с моделью model (BookInstance), а затем перечислить поля модели в поле fields которые должны быть включены в форму (вы можете включить все поля при помощи fields = '__all__', или можно воспользоваться полем exclude (вместо fields), чтобы определить поля модели, которые не нужно включать).
from django.forms import ModelForm
from .models import BookInstance
class RenewBookModelForm(ModelForm):
class Meta:
model = BookInstance
fields = ['due_back',]
Примечание: Это не выглядит сильно проще, чем просто использовать класс Form (и это действительно так, поскольку мы используем только одно поле). Тем не менее, если вы хотите иметь много полей, то такой способ построения формы может значительно уменьшить количество кода и ускорить разработку!
Оставшаяся часть информации касается объявления полей модели (то есть, текстовых меток, виджетов, текстов, сообщений об ошибках). Если они недостаточно «правильные», то тогда мы можем переопределить их в нашем классе Meta при помощи словаря, содержащего поле, которое надо изменить и его новое значение. Например, в нашей форме мы могли бы поменять текст метки для поля «Renewal date» (вместо того, чтобы оставить текст по умолчанию: Due date), а кроме того мы хотим написать другой вспомогательный текст. Класс Meta, представленный ниже, показывает вам, как переопределить данные поля. Кроме того, при необходимости, вы можете установить значения для виджетов widgets и сообщений об ошибках error_messages.
class Meta:
model = BookInstance
fields = ['due_back',]
labels = { 'due_back': _('Renewal date'), }
help_texts = { 'due_back': _('Enter a date between now and 4 weeks (default 3).'), }
Чтобы добавить валидацию, вы можете использовать тот же способ как и для класса Form — вы определяете функцию с именем clean_field_name() из которой выбрасываете исключение ValidationError, если это необходимо. Единственным отличием от нашей оригинальной формы будет являться то, что поле модели имеет имя due_back, а не «renewal_date«.
from django.forms import ModelForm
from .models import BookInstance
class RenewBookModelForm(ModelForm):
def clean_due_back(self):
data = self.cleaned_data['due_back']
#Проверка того, что дата не в прошлом
if data < datetime.date.today():
raise ValidationError(_('Invalid date - renewal in past'))
#Check date is in range librarian allowed to change (+4 weeks)
if data > datetime.date.today() + datetime.timedelta(weeks=4):
raise ValidationError(_('Invalid date - renewal more than 4 weeks ahead'))
# Не забывайте всегда возвращать очищенные данные
return data
class Meta:
model = BookInstance
fields = ['due_back',]
labels = { 'due_back': _('Renewal date'), }
help_texts = { 'due_back': _('Enter a date between now and 4 weeks (default 3).'), }
Теперь класс RenewBookModelForm является функциональным эквивалентом нашему предыдущему классу RenewBookForm. Вы можете импортировать и использовать его в тех же местах, где и RenewBookForm.
Обобщённые классы отображения для редактирования
Алгоритм управления формой, который мы использовали в нашей функции отображения, является примером достаточно общего подхода к работе с формой. Django старается абстрагировать и упростить большую часть данной работы, путём широкого применения обобщённых классов отображений, которые служат для создания, редактирования и удаления отображений на основе моделей. Они не только управляют поведением отображения, но, кроме того, они из вашей модели автоматически создают класс формы (ModelForm).
**Примечание:**В дополнение к отображениям для редактирования, описываемых здесь, существует также класс FormView, который по своему предназначению находится где-то между «простой» функцией отображения и другими обобщёнными отображениями, то есть в каком-то смысле, в диапазоне: «гибкость» против «усилия при программировании». Применяя
FormView,вы все ещё нуждаетесь в создании классаForm, но вам не нужно реализовывать всю «стандартную» функциональность работы с формой. Вместо этого, вы должны просто реализовать функцию, которая будет вызвана в тот момент, когда станет понятно, что получаемые из формы данные, «правильные» (валидны).
В данном разделе мы собираемся использовать обобщённые классы для редактирования, для того, чтобы создать страницы, который добавляют функциональность создания, редактирования и удаления записей типа Author из нашей библиотеки — предоставляя базовую функциональность некоторых частей административной части сайта (это может быть полезно для случаев, когда вам нужно создать административную часть сайта, которая, в отличие от стандартной, была бы более гибкой).
Отображения
Откройте файл отображений (locallibrary/catalog/views.py) и добавьте следующий код в его нижнюю часть:
from django.views.generic.edit import CreateView, UpdateView, DeleteView
from django.urls import reverse_lazy
from .models import Author
class AuthorCreate(CreateView):
model = Author
fields = '__all__'
initial={'date_of_death':'12/10/2016',}
class AuthorUpdate(UpdateView):
model = Author
fields = ['first_name','last_name','date_of_birth','date_of_death']
class AuthorDelete(DeleteView):
model = Author
success_url = reverse_lazy('authors')
Как вы видите, для создания отображений вам надо наследоваться от следующих классов CreateView, UpdateView и DeleteView (соответственно), а затем связать их с соответствующей моделью.
Для случаев «создать» и «обновить» вам также понадобится определить поля для показа на форме (применяя тот же синтаксис, что и для ModelForm). В этом случае мы демонстрируем синтаксис и для показа «всех» полей, и перечисление их по отдельности. Также вы можете указать начальные значения для каждого поля, применяя словарь пар имя_поля/значение (в целях демонстрации, в нашем примере мы явно указываем дату смерти — если хотите, то вы можете удалить это поле). По умолчанию отображения перенаправляют пользователя на страницу «успеха», показывая только что созданные/отредактированные данные (записи в модели). В нашем случае это, созданная в предыдущей части руководства, подробная информация об авторе. Вы можете указать альтернативное перенаправление при помощи параметра success_url (как в примере с классом AuthorDelete).
Классу AuthorDelete не нужно показывать каких либо полей, таким образом их не нужно и декларировать. Тем не менее, вам нужно указать success_url, потому что, в данном случае, для Django не очевидно что делать после успешного выполнения операции удаления записи. Мы используем функцию reverse_lazy() для перехода на страницу списка авторов после удаления одного из них — reverse_lazy() это более «ленивая» версия reverse().
Шаблоны
Отображения «создать» и «обновить» используют шаблоны с именем model_name_form.html, по умолчанию: (вы можете поменять суффикс на что-нибудь другое, при помощи поля template_name_suffix в вашем отображении, например, template_name_suffix = '_other_suffix')
Создайте файл шаблона locallibrary/catalog/templates/catalog/author_form.html и скопируйте в него следующий текст.
{% extends "base_generic.html" %}
{% block content %}
<form action="" method="post">
{% csrf_token %}
<table>
{{ form.as_table }}
</table>
<input type="submit" value="Submit" />
</form>
{% endblock %}
Это напоминает наши предыдущие формы и рендер полей при помощи таблицы. Заметьте, что мы снова используем{% csrf_token %}.
Отображения «удалить» ожидает «найти» шаблон с именем формата model_name_confirm_delete.html (и снова, вы можете изменить суффикс при помощи поля отображения template_name_suffix). Создайте файл шаблона locallibrary/catalog/templates/catalog/author_confirm_delete.html и скопируйте в него текст, указанный ниже.
{% extends "base_generic.html" %}
{% block content %}
<h1>Delete Author</h1>
<p>Are you sure you want to delete the author: {{ author }}?</p>
<form action="" method="POST">
{% csrf_token %}
<input type="submit" value="Yes, delete." />
</form>
{% endblock %}
Настройки URL-адресов
Откройте файл конфигураций URL-адресов (locallibrary/catalog/urls.py) и добавьте в его нижнюю часть следующие настройки:
urlpatterns += [
url(r'^author/create/$', views.AuthorCreate.as_view(), name='author_create'),
url(r'^author/(?P<pk>d+)/update/$', views.AuthorUpdate.as_view(), name='author_update'),
url(r'^author/(?P<pk>d+)/delete/$', views.AuthorDelete.as_view(), name='author_delete'),
]
Здесь нет ничего нового! Как вы видите отображения являются классами и следовательно должны вызываться через метод .as_view(). Паттерны URL-адресов для каждого случая должны быть вам понятны. Мы обязаны использовать pk как имя для «захваченного» значения первичного ключа, так как параметр именно с таким именем ожидается классами отображения.
Страницы создания, обновления и удаления автора теперь готовы к тестированию (мы не будем создавать на них ссылки в отдельном меню, но вы, если хотите, можете их сделать).
Примечание: Наблюдательные пользователи могли заметить, что мы ничего не делаем, чтобы предотвратить несанкционированный доступ к страницам! Мы оставили это в качестве упражнения для вас (подсказка: вы можете использовать PermissionRequiredMixin и, либо создать новое разрешение, или воспользоваться нашим прежним can_mark_returned).
Тестирование страницы
Залогиньтесь на сайте с аккаунтом, который позволит вам получить доступ к страницам редактирования данных (и записей) автора.
Затем перейдите на страницу создания новой записи автора: http://127.0.0.1:8000/catalog/author/create/, которая должна быть похожей на следующий скриншот.
Введите в поля значения и нажмите на кнопку Submit, чтобы сохранить новую запись об авторе. После этого, вы должны были перейти на страницу редактирования только что созданного автора, имеющий адрес, похожий на следующий http://127.0.0.1:8000/catalog/author/10.
У вас есть возможность редактирования записей при помощи добавления /update/ в конец адреса подробной информации (то есть, http://127.0.0.1:8000/catalog/author/10/update/) — мы не показываем скриншот, потому что он выглядит в точности также как страница «создать»!
И последнее, мы можем удалить страницу, добавляя строку /delete/ в конец адреса подробной информации автора (то есть, http://127.0.0.1:8000/catalog/author/10/delete/). Django должен показать страницу, которая похожа на представленную ниже. Нажмите Yes, delete., чтобы удалить запись и перейти на страницу со списком авторов.
Проверьте себя
Создайте несколько форм создания, редактирования и удаления записей в модели Book. При желании, вы можете использовать тоже структуры как и в случае с моделью Authors. Если ваш шаблон book_form.html является просто копией шаблона author_form.html, тогда новая страница «create book» будет выглядеть как на следующем скриншоте:
Итоги
Создание и управление формами может быть достаточно сложным! Django делает этот процесс намного проще, предоставляя прикладные механизмы объявления, рендеринга и проверки форм. Более того, Django предоставляет обобщённые классы редактирования форм, которые могут выполнять практически любую работу по созданию, редактированию и удалению записей, связанных с одиночной моделью.
Существует много чего ещё, что можно делать с формами (ознакомьтесь со списком ниже), но теперь вы должны понимать как добавлять базовые формы и создавать код управления формой на вашем сайте.
Смотрите также
In this post we’ll learn to create user-defined functions, displaying validation errors in the template for Django Form Validations.
Table Of Contents
- Introduction
- Creating Form
- Rendering Form
- Saving Form
- Form Validation User-Defined Functions
- Conclusion
Introduction
The forms are a Django Module which deals with all form-related functions from binding POST data to form, Validating form and rendering HTML field in the template.
We’ll be using below models.py file as an example to demonstrate form validations.
from django.db import models
from django.contrib.auth.models import User
from datetime import datetime
class AuthUserProfile(models.Model):
user_profile_id = models.AutoField(primary_key=True)
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='auth_user_profile')
dob = models.DateField(blank=True, null=True)
is_deleted = models.PositiveSmallIntegerField(default=0)
created_at = models.DateTimeField(auto_now=datetime.now(), null=True)
updated_at = models.DateTimeField(auto_now=datetime.now(), null=True)
class Meta():
db_table = 'auth_user_profile'
verbose_name = 'User Profile'
verbose_name_plural = 'User Profiles'
def __str__(self):
return self.user
Create a form which has these fields (first_name, last_name, username, password, email) from User models and field (dob) in AuthUserProfile Model and also will add custom field and non-field level validation.
Creating Form
In forms.py file import forms from Django. Inherit forms.Form to UserForm and add attributes to the field.
from django import forms
from datetime import datetime
from django.contrib.auth.models import User
class UserForm(forms.Form):
first_name = forms.CharField(label="First Name*",widget=forms.TextInput(attrs={'required':True,'class':"form-control"}))
last_name = forms.CharField(label="Last Name*",widget=forms.TextInput(attrs={'required':True,'class':"form-control"}))
username = forms.CharField(label="User Name*",widget=forms.TextInput(attrs={'required':True,'class':"form-control"}))
email = forms.CharField(label="Email",widget=forms.TextInput(attrs={'type':'email','required':False,'class':"form-control"}))
date_of_birth = forms.CharField(label="Date of Birth",widget=forms.TextInput(attrs={'type':'date','required':True,'class':"form-control"}))
password = forms.CharField(label="Password*",widget=forms.TextInput(attrs={'required':True,'class':"form-control", 'type' : "password"}))
confirm_password = forms.CharField(label="Confirm Password*",widget=forms.TextInput(attrs={'required':True,'class':"form-control", 'type' : "password"}))
def clean(self):
# user age must be above 18 to register
if self.cleaned_data.get('date_of_birth'):
dob = datetime.strptime(self.cleaned_data.get('date_of_birth'),"%Y-%m-%d")
now = datetime.now()
diff = now.year-dob.year
if diff < 18: msg="User must be atleast 18 years old" self.add_error(None, msg) #check if user name is unique username_count = User.objects.filter(username=self.cleaned_data.get('username')).count() if username_count>0:
msg="Username '{}' has already been used.".format(self.cleaned_data.get('username'))
self.add_error(None, msg)
def clean_confirm_password(self):
password = self.cleaned_data.get('password')
confirm_password = self.cleaned_data.get('confirm_password')
if confirm_password!=password:
msg = "Password and Confirm Passwords must match."
self.add_error('confirm_password', msg)
You may notice clean() and clean_confirm_password() methods in UserForm the form they are validation methods.
The clean() the method is form level validation this can also be used to perform field-level validation.
And the clean_confirm_password() is a field-level validation for confirm_password the field it checks if confirm_password!=password then adds error to a then particular field.
Rendering Form
Rendering of forms is an easy part we must pass the form object as an argument to render function.
In views.py create a function user_profile_create which will display rendered form.
from django.contrib.auth.models import User
from users.models import AuthUserProfile
from forms.forms import UserForm
from django.contrib.auth.hashers import make_password
from django.contrib import messages
def user_profile_create(request):
form = UserForm()
template="forms/user_profile_create_form.html"
return render(request,template,{"form":form})
form = UserForm() creates form object of UserForm and is passed as an argument to the render() function.
In urls.py file add routes to view.
urlpatterns = [
path('user/profile/create', views.user_profile_create, name='user-profile-create'),
]
Create an HTML file in your apps template folder naming user_profile_create_form.html.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Django Form Validation</title>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.0/css/bootstrap.min.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.0/js/bootstrap.min.js"></script>
<style>
.error{
color:red;
}
</style>
</head>
<body>
<div class="container">
{% if messages %}
{% for message in messages %}
{% if message.level == DEFAULT_MESSAGE_LEVELS.SUCCESS %}
<div class="alert alert-success"
role="alert">
<div id="primary-notification-div">
{{ message }}
</div>
</div>
{% endif %}
{% endfor %}
{% endif %}
<h1>User Profile</h1>
<form action="{% url 'forms:user-profile-save' %}" method="post">
{% csrf_token %}
{% if form.errors %}
{% for error in form.non_field_errors %}
<div class="alert alert-danger">
<strong>{{ error|escape }}</strong>
</div>
{% endfor %}
{% endif %}
<div class="row">
<div class="col-md-3">
{{form.first_name.label}}
{{form.first_name}}
{% if form.errors.first_name %}
<label for="" class="error">{{form.errors.first_name|striptags}}</label>
{% endif %}
</div>
<div class="col-md-3">
{{form.last_name.label}}
{{form.last_name}}
{% if form.errors.last_name %}
<label for="" class="error">{{form.errors.last_name|striptags}}</label>
{% endif %}
</div>
<div class="col-md-3">
{{form.username.label}}
{{form.username}}
{% if form.errors.username %}
<label for="" class="error">{{form.errors.username|striptags}}</label>
{% endif %}
</div>
<div class="col-md-3">
{{form.email.label}}
{{form.email}}
{% if form.errors.email %}
<label for="" class="error">{{form.errors.email|striptags}}</label>
{% endif %}
</div>
<div class="col-md-3">
{{form.date_of_birth.label}}
{{form.date_of_birth}}
{% if form.errors.date_of_birth %}
<label for="" class="error">{{form.errors.date_of_birth|striptags}}</label>
{% endif %}
</div>
</div>
<div class="row" style="margin-top: 25px;">
<div class="col-md-3">
{{form.password.label}}
{{form.password}}
{% if form.errors.password %}
<label for="" class="error">{{form.errors.password|striptags}}</label>
{% endif %}
</div>
</div>
<div class="row" style="margin-top: 25px;">
<div class="col-md-3">
{{form.confirm_password.label}}
{{form.confirm_password}}
{% if form.errors.confirm_password %}
<label for="" class="error">{{form.errors.confirm_password|striptags}}</label>
{% endif %}
</div>
<div class="col-md-12" style="margin-top: 25px;">
<input type="submit" class="btn btn-sm btn-primary" value="submit">
</div>
</div>
</form>
</div>
</body>
</html>
This is how our form will look when we go to route user/profile/create.

- The
messagedisplays success message oncemessages.add_message(request, messages.SUCCESS, ".....")is called it is just like the flash message. - The
form.errorsis called on validation has failed. - The
form.non_field_errorsdisplay errors on top of form this errors are not associated to a particular field. - The
form.errors.displays the error of the particular field.
This is how errors are displayed in the form.

Saving Form
In urls.py file add routes to save the form.
urlpatterns = [
path('user/profile/save', views.user_profile_create, name='user-profile-save'),
]
In views.py file add function user_profile_save() to save form data.
def user_profile_save(request):
form = UserForm(request.POST)
if form.is_valid():
query = {
"first_name" : form.cleaned_data.get('first_name'),
"last_name" : form.cleaned_data.get('last_name'),
"username" : form.cleaned_data.get('username'),
"password" : make_password(form.cleaned_data.get('password')),
"email" : form.cleaned_data.get('email'),
"is_superuser" : 0,
"is_staff" : 1,
"is_active" : 1,
}
user = User.objects.create(**query)
query={
"user_id" : user.id,
"dob" : form.cleaned_data.get('dob'),
}
AuthUserProfile.objects.create(**query)
messages.add_message(request, messages.SUCCESS, "User Profile created successfully.")
return HttpResponseRedirect(reverse('forms:user-profile-create'))
template="forms/user_profile_create_form.html"
return render(request,template,{"form":form})
The request.POST is passed to UserForm(request.POST) this binds the submitted data to Form Class.
The form.is_valid() returns a Boolean value if True then the form is clean and if False then there may be validation error.
To view validation errors after .is_valid() method we can print form.errors to view validation errors.
Calling form.cleaned_data.get('') gives use of the sanitized value to that field. Inside .is_valid() we have called model methods to save form data.

Form Validation User-Defined functions
To defined a custom validation function in Form Class name function with prefix clean followed by underscore and field name which must be validated.
Example
def clean_first_name(self):
pass #this validates field first_name
def clean_username(self):
pass #this validates field username
If the value of the field is not as expected that you can raise validation error or add error by mentioning field name self.add_error('field_name', "Error Message").
If you want to raise non-field error than set the first argument of add_error() method None followed by the message you want to be displayed.
self.add_error(None, msg) #this creates a non-field error
Conclusion
We have come to the end of our post on Django Form Validation.
If you have any doubts or suggestions please mention in the comments section and we’ll reach you soon and we would also love to hear requests and your recommendations for new tutorials/posts.
Related Posts
- Python Django Forms | Creating, Rendering, Validating and Saving Forms
- Django – Multiple Files Validation and Uploads
Summary
Review Date
2020-06-15
Reviewed Item
Django Forms | Custom Form Validations
Author Rating
5




Software Name
Django Web Framework
Software Name
Windows Os, Mac Os, Ubuntu Os
Software Category
Web Development
Проверка форм и полей формы¶
Проверка формы происходит при нормализации её данных. При возникновении необходимости вмешаться в этот процесс, есть много мест, где можно это сделать и которые влияют на разные этапы проверки. Во время обработки формы вызываются три типа методов для нормализации данных. Процесс проверки запускается при вызове метода 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_<fieldname>(), где <fieldname> заменяется на имя поля. Этот метод выполняет проверку значения. Метод не принимает аргументы. Для получения значения поля обращайтесь к словарю
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_<fieldname>(). После того, как пара этих методов будет вызвана для каждого поля формы, наступает очередь метода Form.clean() формы. Он будет вызыван в любом случае, даже если предыдущие методы вызывали ошибку.
Примеры для каждого из этих методов показаны ниже.
Как упоминалось ранее, любой из этих методов может вызвать исключение ValidationError. Для любого поля, если его метод 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 с 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.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.