I often find myself using a ModelForm in views to display and translate views. I have no trouble displaying the form in the template. My problem is that when I am working with these, the forms often don’t validate with the is_valid method. The problem is that I don’t know what is causing the validation error.
Here is a basic example in views:
def submitrawtext(request):
if request.method == "POST":
form = SubmittedTextFileForm()
if form.is_valid():
form.save()
return render(request, 'upload_comlete.html')
return render(request, 'failed.html')
else:
form = SubmiittedTextFileForm()
return render(request, 'inputtest.html', {'form': form})
I know that the form is not validating because I am redirected to the failed.html template, but I never know why .is_valid is false. How can I set this up to show me the form validation errors?
asked Nov 24, 2013 at 1:33
Couple of things:
-
You are not taking the
POST
being sent to the POST. -
To see the error message, you need to render back to the same template.
Try this:
def submitrawtext(request):
if request.method == "POST":
form = SubmittedTextFileForm(request.POST)
if form.is_valid():
form.save()
return render(request, 'upload_comlete.html')
else:
print form.errors #To see the form errors in the console.
else:
form = SubmittedTextFileForm()
# If form is not valid, this would re-render inputtest.html with the errors in the form.
return render(request, 'inputtest.html', {'form': form})
answered Nov 24, 2013 at 2:13
karthikrkarthikr
97.4k26 gold badges197 silver badges188 bronze badges
0
I faced the same annoying problem and solved it by returning the form.errors.values()
back with HttpResponse. Here is the code:
@csrf_exempt
def post(request):
form = UserForm(request.POST)
if form.is_valid():
return HttpResponse('All Good!')
else:
return HttpResponse(form.errors.values()) # Validation failed
In my case it returned:
<ul class="errorlist"><li>This field is required.</li></ul>
<ul class="errorlist"><li>This field is required.</li></ul>
<ul class="errorlist"><li>This field is required.</li></ul>
<ul class="errorlist"><li>This field is required.</li></ul>
It doesn’t provide much information, but it is enough to give you an idea.
answered Feb 21, 2021 at 12:36
This is my view:
def main_page(request):
if request.method == 'POST':
form = RegistrationForm(request.POST)
if form.is_valid():
user = User.objects.create_user(
username=form.clean_data['username'],
password=form.clean_data['password1'],
email=form.clean_data['email']
)
return HttpResponseRedirect('/')
else:
form = RegistrationForm()
variables = {
'form': form
}
return render(request, 'main_page.html', variables)
and this is my main_page.html:
{% if form.errors %}
<p>NOT VALID</p>
{% for errors in form.errors %}
{{ errors }}
{% endfor %}
{% endif %}
<form method="post" action="/">{% csrf_token %}
<p><label for="id_username">Username:</label>{{ form.username }}</p>
<p><label for="id_email">Email Address:</label>{{ form.email }}</p>
<p><label for="id_password">Password:</label>{{ form.password1 }}</p>
<p><label for="id_retypePassword">Retype Password:</label>{{ form.password2 }}</p>
<input type="hidden" name="next" />
<input type="submit" value="Register" />
</form>
When I go to the url which uses the main_page view, it just displays the form. When I submit the form with errors (with blank fields and without a proper email address) it just redirects me to the same page and doesn’t display any errors. It doesn’t even say «NOT VALID».
When I change
{% if form.errors %}
to
{% if not form.is_valid %}
it always says «NOT VALID» (even if it is the first time going to the url and even if I didn’t submit anything yet).
This is my RegistrationForm:
from django import forms
import re
from django.contrib.auth.models import User
from django.core.exceptions import ObjectDoesNotExist
class RegistrationForm(forms.Form):
username = forms.CharField(label='Username', max_length=30)
email = forms.EmailField(label='Email')
password1 = forms.CharField(label='Password', widget=forms.PasswordInput())
password2 = forms.CharField(label='Password (Again)', widget=forms.PasswordInput())
def clean_password2(self):
if 'password1' in self.cleaned_data:
password1 = self.cleaned_data['password1']
password2 = self.cleaned_data['password2']
if password1 == password2:
return password2
raise forms.ValidationError('Passwords do not match.')
def clean_username(self):
username = self.cleaned_data['username']
if not re.search(r'^\w+$', username): #checks if all the characters in username are in the regex. If they aren't, it returns None
raise forms.ValidationError('Username can only contain alphanumeric characters and the underscore.')
try:
User.objects.get(username=username) #this raises an ObjectDoesNotExist exception if it doesn't find a user with that username
except ObjectDoesNotExist:
return username #if username doesn't exist, this is good. We can create the username
raise forms.ValidationError('Username is already taken.')
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 aField
is the first step in every
validation. It coerces the value to a correct datatype and raises
ValidationError
if that is not possible. This method accepts the raw
value from the widget and returns the converted value. For example, a
FloatField
will turn the data into a Pythonfloat
or raise a
ValidationError
. -
The
validate()
method on aField
handles field-specific validation
that is not suitable for a validator. It takes a value that has been
coerced to a correct datatype and raisesValidationError
on 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 aField
runs 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 aField
subclass 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_data
dictionary 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_data
and remember that it will be a Python object
at this point, not the original string submitted in the form (it will be
incleaned_data
because the general fieldclean()
method, above,
has already cleaned the data once).For example, if you wanted to validate that the contents of a
CharField
calledserialnumber
was 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 fieldA
is supplied, fieldB
must 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’serrors
attribute 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 aModelForm
subclass. (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
code
to 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
gettext
to 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 ValidationError
s (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 code
s 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
.
Связанные и несвязанные формы¶
Экземпляр Form
либо связан с набором данных, либо не связан.
- Если он связан с набором данных, он способен проверить эти данные и вывести форму в виде HTML с отображением данных в HTML.
- Если это unbound, он не может выполнить валидацию (потому что нет данных для валидации!), но он все еще может отобразить пустую форму как HTML.
-
class
Form
[исходный код]¶
Чтобы создать несвязанный экземпляр Form
, инстанцируйте класс:
Чтобы привязать данные к форме, передайте их в виде словаря в качестве первого параметра в конструктор вашего класса Form
:
>>> data = {'subject': 'hello', ... 'message': 'Hi there', ... 'sender': 'foo@example.com', ... 'cc_myself': True} >>> f = ContactForm(data)
В этом словаре ключами являются имена полей, которые соответствуют атрибутам в вашем классе Form
. Значения — это данные, которые вы пытаетесь проверить. Обычно это строки, но нет требования, чтобы они были строками; тип передаваемых данных зависит от Field
, как мы увидим через некоторое время.
-
Form.
is_bound
¶
Если вам необходимо различать связанные и несвязанные экземпляры формы во время выполнения, проверьте значение атрибута формы is_bound
:
>>> f = ContactForm() >>> f.is_bound False >>> f = ContactForm({'subject': 'hello'}) >>> f.is_bound True
Обратите внимание, что передача пустого словаря создает связанную форму с пустыми данными:
>>> f = ContactForm({}) >>> f.is_bound True
Если у вас есть связанный экземпляр Form
и вы хотите как-то изменить данные, или если вы хотите связать несвязанный экземпляр Form
с какими-то данными, создайте еще один экземпляр Form
. Не существует способа изменить данные в экземпляре Form
. После создания экземпляра Form
следует считать его данные неизменяемыми, независимо от того, есть у него данные или нет.
Использование форм для проверки данных¶
-
Form.
clean
()¶
Реализуйте метод clean()
на вашем Form
, когда вам необходимо добавить пользовательскую валидацию для полей, которые являются взаимозависимыми. Пример использования см. в Очистка и проверка полей, которые зависят друг от друга.
-
Form.
is_valid
()¶
Основная задача объекта Form
заключается в проверке данных. При наличии связанного экземпляра Form
вызовите метод is_valid()
, чтобы запустить проверку и вернуть булево значение, указывающее, были ли данные достоверными:
>>> data = {'subject': 'hello', ... 'message': 'Hi there', ... 'sender': 'foo@example.com', ... 'cc_myself': True} >>> f = ContactForm(data) >>> f.is_valid() True
Давайте попробуем с недействительными данными. В данном случае subject
пуст (ошибка, так как по умолчанию все поля обязательны для заполнения), а sender
не является действительным адресом электронной почты:
>>> data = {'subject': '', ... 'message': 'Hi there', ... 'sender': 'invalid email address', ... 'cc_myself': True} >>> f = ContactForm(data) >>> f.is_valid() False
-
Form.
errors
¶
Обратитесь к атрибуту errors
, чтобы получить словарь сообщений об ошибках:
>>> f.errors {'sender': ['Enter a valid email address.'], 'subject': ['This field is required.']}
В этом словаре ключами являются имена полей, а значениями — списки строк, представляющих сообщения об ошибках. Сообщения об ошибках хранятся в виде списков, поскольку одно поле может иметь несколько сообщений об ошибках.
Вы можете получить доступ к errors
без необходимости сначала вызывать is_valid()
. Данные формы будут проверены при первом вызове is_valid()
или обращении к errors
.
Процедура валидации будет вызвана только один раз, независимо от того, сколько раз вы обращаетесь к errors
или вызываете is_valid()
. Это означает, что если валидация имеет побочные эффекты, то эти побочные эффекты будут вызваны только один раз.
-
Form.errors.
as_data
()¶
Возвращает dict
, который отображает поля на их исходные ValidationError
экземпляры.
>>> f.errors.as_data() {'sender': [ValidationError(['Enter a valid email address.'])], 'subject': [ValidationError(['This field is required.'])]}
Используйте этот метод в любое время, когда вам нужно идентифицировать ошибку по ее code
. Это позволяет, например, переписать сообщение об ошибке или написать пользовательскую логику в представлении при наличии данной ошибки. Его также можно использовать для сериализации ошибок в пользовательском формате (например, XML); например, as_json()
зависит от as_data()
.
Необходимость метода as_data()
обусловлена обратной совместимостью. Ранее экземпляры ValidationError
терялись, как только их рендеренные сообщения об ошибках добавлялись в словарь Form.errors
. В идеале Form.errors
должен был хранить экземпляры ValidationError
, а методы с префиксом as_
могли бы их рендерить, но пришлось сделать наоборот, чтобы не ломать код, ожидающий рендеринга сообщений об ошибках в Form.errors
.
-
Form.errors.
as_json
(escape_html=False)¶
Возвращает ошибки, сериализованные в виде JSON.
>>> f.errors.as_json() {"sender": [{"message": "Enter a valid email address.", "code": "invalid"}], "subject": [{"message": "This field is required.", "code": "required"}]}
По умолчанию as_json()
не экранирует свой вывод. Если вы используете его для чего-то вроде AJAX-запросов к форме представления, где клиент интерпретирует ответ и вставляет ошибки на страницу, вы захотите убедиться, что результаты на стороне клиента экранированы, чтобы избежать возможности межсайтовой скриптинг-атаки. Вы можете сделать это в JavaScript с помощью element.textContent = errorText
или с помощью функции jQuery $(el).text(errorText)
(а не ее функции .html()
).
Если по какой-то причине вы не хотите использовать экранирование на стороне клиента, вы также можете установить escape_html=True
, и сообщения об ошибках будут экранированы, чтобы вы могли использовать их непосредственно в HTML.
-
Form.errors.
get_json_data
(escape_html=False)¶
Возвращает ошибки в виде словаря, пригодного для сериализации в JSON. Form.errors.as_json()
возвращает сериализованный JSON, в то время как это возвращает данные об ошибках до их сериализации.
Параметр escape_html
ведет себя так, как описано в Form.errors.as_json()
.
-
Form.
add_error
(field, error)¶
Этот метод позволяет добавлять ошибки в определенные поля из метода Form.clean()
или вообще извне формы, например, из представления.
Аргумент field
является именем поля, к которому должны быть добавлены ошибки. Если его значение равно None
, то ошибка будет рассматриваться как неполевая ошибка, возвращаемая Form.non_field_errors()
.
Аргумент error
может быть строкой или, предпочтительно, экземпляром ValidationError
. См. раздел Поднятие ValidationError о лучших практиках при определении ошибок формы.
Обратите внимание, что Form.add_error()
автоматически удаляет соответствующее поле из cleaned_data
.
-
Form.
has_error
(field, code=None)¶
Этот метод возвращает булево число, обозначающее, есть ли в поле ошибка с конкретной ошибкой code
. Если code
— None
, то он вернет True
, если поле вообще содержит какие-либо ошибки.
Для проверки ошибок, не связанных с полем, используйте NON_FIELD_ERRORS
в качестве параметра field
.
-
Form.
non_field_errors
()¶
Этот метод возвращает список ошибок из Form.errors
, которые не связаны с конкретным полем. Сюда входят ValidationError
ошибки, возникающие в Form.clean()
и ошибки, добавленные с помощью Form.add_error(None, "...")
.
Поведение несвязанных форм¶
Бессмысленно проверять форму без данных, но, для справки, вот что происходит с несвязанными формами:
>>> f = ContactForm() >>> f.is_valid() False >>> f.errors {}
Начальные значения формы¶
-
Form.
initial
¶
Используйте initial
для объявления начального значения полей формы во время выполнения. Например, вы можете захотеть заполнить поле username
именем пользователя текущей сессии.
Чтобы добиться этого, используйте аргумент initial
для Form
. Этот аргумент, если он задан, должен представлять собой словарь, отображающий имена полей на начальные значения. Включите только те поля, для которых вы указываете начальное значение; нет необходимости включать каждое поле в вашу форму. Например:
>>> f = ContactForm(initial={'subject': 'Hi there!'})
Эти значения отображаются только для несвязанных форм, и они не используются в качестве резервных значений, если конкретное значение не предоставлено.
Если Field
определяет initial
и вы включаете initial
при инстанцировании Form
, то последний initial
будет иметь приоритет. В данном примере initial
предоставляется как на уровне поля, так и на уровне экземпляра формы, и последний имеет приоритет:
>>> from django import forms >>> class CommentForm(forms.Form): ... name = forms.CharField(initial='class') ... url = forms.URLField() ... comment = forms.CharField() >>> f = CommentForm(initial={'name': 'instance'}, auto_id=False) >>> print(f) <tr><th>Name:</th><td><input type="text" name="name" value="instance" required></td></tr> <tr><th>Url:</th><td><input type="url" name="url" required></td></tr> <tr><th>Comment:</th><td><input type="text" name="comment" required></td></tr>
-
Form.
get_initial_for_field
(field, field_name)¶
Возвращает исходные данные для поля формы. Он извлекает данные из Form.initial
, если они присутствуют, в противном случае пробует Field.initial
. Вызываемые значения оцениваются.
Рекомендуется использовать BoundField.initial
вместо get_initial_for_field()
, поскольку BoundField.initial
имеет более простой интерфейс. Кроме того, в отличие от get_initial_for_field()
, BoundField.initial
кэширует свои значения. Это особенно полезно при работе с callables, возвращаемые значения которых могут меняться (например, datetime.now
или uuid.uuid4
):
>>> import uuid >>> class UUIDCommentForm(CommentForm): ... identifier = forms.UUIDField(initial=uuid.uuid4) >>> f = UUIDCommentForm() >>> f.get_initial_for_field(f.fields['identifier'], 'identifier') UUID('972ca9e4-7bfe-4f5b-af7d-07b3aa306334') >>> f.get_initial_for_field(f.fields['identifier'], 'identifier') UUID('1b411fab-844e-4dec-bd4f-e9b0495f04d0') >>> # Using BoundField.initial, for comparison >>> f['identifier'].initial UUID('28a09c59-5f00-4ed9-9179-a3b074fa9c30') >>> f['identifier'].initial UUID('28a09c59-5f00-4ed9-9179-a3b074fa9c30')
Проверка того, какие данные формы были изменены¶
-
Form.
has_changed
()¶
Используйте метод has_changed()
на вашем Form
, когда вам нужно проверить, были ли данные формы изменены по сравнению с начальными данными.
>>> data = {'subject': 'hello', ... 'message': 'Hi there', ... 'sender': 'foo@example.com', ... 'cc_myself': True} >>> f = ContactForm(data, initial=data) >>> f.has_changed() False
Когда форма отправлена, мы реконструируем ее и предоставляем исходные данные, чтобы можно было провести сравнение:
>>> f = ContactForm(request.POST, initial=data) >>> f.has_changed()
has_changed()
будет True
, если данные из request.POST
отличаются от тех, что были предоставлены в initial
или False
в противном случае. Результат вычисляется путем вызова Field.has_changed()
для каждого поля формы.
-
Form.
changed_data
¶
Атрибут changed_data
возвращает список имен полей, значения которых в связанных данных формы (обычно request.POST
) отличаются от того, что было предоставлено в initial
. Он возвращает пустой список, если данные не отличаются.
>>> f = ContactForm(request.POST, initial=data) >>> if f.has_changed(): ... print("The following fields changed: %s" % ", ".join(f.changed_data)) >>> f.changed_data ['subject', 'message']
Доступ к полям из формы¶
-
Form.
fields
¶
Вы можете получить доступ к полям экземпляра Form
из его атрибута fields
:
>>> for row in f.fields.values(): print(row) ... <django.forms.fields.CharField object at 0x7ffaac632510> <django.forms.fields.URLField object at 0x7ffaac632f90> <django.forms.fields.CharField object at 0x7ffaac3aa050> >>> f.fields['name'] <django.forms.fields.CharField object at 0x7ffaac6324d0>
You can alter the field and BoundField
of Form
instance to
change the way it is presented in the form:
>>> f.as_div().split("</div>")[0] '<div><label for="id_subject">Subject:</label><input type="text" name="subject" maxlength="100" required id="id_subject">' >>> f["subject"].label = "Topic" >>> f.as_div().split("</div>")[0] '<div><label for="id_subject">Topic:</label><input type="text" name="subject" maxlength="100" required id="id_subject">'
Остерегайтесь изменять атрибут base_fields
, поскольку это изменение повлияет на все последующие ContactForm
экземпляры в рамках одного процесса Python:
>>> f.base_fields["subject"].label_suffix = "?" >>> another_f = CommentForm(auto_id=False) >>> f.as_div().split("</div>")[0] '<div><label for="id_subject">Subject?</label><input type="text" name="subject" maxlength="100" required id="id_subject">'
Доступ к «чистым» данным¶
-
Form.
cleaned_data
¶
Каждое поле в классе Form
отвечает не только за проверку данных, но и за их «очистку» — нормализацию до согласованного формата. Это хорошая особенность, поскольку она позволяет вводить данные для конкретного поля различными способами, всегда приводя к согласованному результату.
Например, DateField
нормализует входные данные в объект Python datetime.date
. Независимо от того, передаете ли вы ему строку в формате '1994-07-15'
, объект datetime.date
или ряд других форматов, DateField
всегда нормализует ее в объект datetime.date
, пока он действителен.
После создания экземпляра Form
с набором данных и его проверки, вы можете получить доступ к чистым данным через его cleaned_data
атрибут:
>>> data = {'subject': 'hello', ... 'message': 'Hi there', ... 'sender': 'foo@example.com', ... 'cc_myself': True} >>> f = ContactForm(data) >>> f.is_valid() True >>> f.cleaned_data {'cc_myself': True, 'message': 'Hi there', 'sender': 'foo@example.com', 'subject': 'hello'}
Обратите внимание, что любое текстовое поле — такое как CharField
или EmailField
— всегда очищает вводимые данные в строку. Мы рассмотрим последствия кодирования позже в этом документе.
Если ваши данные не проходят валидацию, словарь cleaned_data
содержит только валидные поля:
>>> data = {'subject': '', ... 'message': 'Hi there', ... 'sender': 'invalid email address', ... 'cc_myself': True} >>> f = ContactForm(data) >>> f.is_valid() False >>> f.cleaned_data {'cc_myself': True, 'message': 'Hi there'}
cleaned_data
всегда только будет содержать ключ для полей, определенных в Form
, даже если вы передадите дополнительные данные при определении Form
. В этом примере мы передаем кучу дополнительных полей в конструктор ContactForm
, но cleaned_data
содержит только поля формы:
>>> data = {'subject': 'hello', ... 'message': 'Hi there', ... 'sender': 'foo@example.com', ... 'cc_myself': True, ... 'extra_field_1': 'foo', ... 'extra_field_2': 'bar', ... 'extra_field_3': 'baz'} >>> f = ContactForm(data) >>> f.is_valid() True >>> f.cleaned_data # Doesn't contain extra_field_1, etc. {'cc_myself': True, 'message': 'Hi there', 'sender': 'foo@example.com', 'subject': 'hello'}
Когда Form
действителен, cleaned_data
будет включать ключ и значение для всех своих полей, даже если данные не включали значение для некоторых необязательных полей. В этом примере словарь данных не включает значение для поля nick_name
, но cleaned_data
включает его, с пустым значением:
>>> from django import forms >>> class OptionalPersonForm(forms.Form): ... first_name = forms.CharField() ... last_name = forms.CharField() ... nick_name = forms.CharField(required=False) >>> data = {'first_name': 'John', 'last_name': 'Lennon'} >>> f = OptionalPersonForm(data) >>> f.is_valid() True >>> f.cleaned_data {'nick_name': '', 'first_name': 'John', 'last_name': 'Lennon'}
В этом примере значение cleaned_data
для nick_name
установлено в пустую строку, потому что nick_name
— это CharField
, а CharField
s рассматривает пустые значения как пустую строку. Каждый тип поля знает, что такое «пустое» значение — например, для DateField
это None
вместо пустой строки. Для получения подробной информации о поведении каждого поля в этом случае, смотрите примечание «Пустое значение» для каждого поля в разделе «Встроенные классы Field
» ниже.
Вы можете написать код для выполнения валидации для отдельных полей формы (на основе их названия) или для формы в целом (учитывая комбинации различных полей). Более подробная информация об этом содержится в Валидация форм и полей.
Вывод форм в формате HTML¶
Вторая задача объекта Form
— отобразить себя в виде HTML. Чтобы сделать это, print
его:
>>> f = ContactForm() >>> print(f) <tr><th><label for="id_subject">Subject:</label></th><td><input id="id_subject" type="text" name="subject" maxlength="100" required></td></tr> <tr><th><label for="id_message">Message:</label></th><td><input type="text" name="message" id="id_message" required></td></tr> <tr><th><label for="id_sender">Sender:</label></th><td><input type="email" name="sender" id="id_sender" required></td></tr> <tr><th><label for="id_cc_myself">Cc myself:</label></th><td><input type="checkbox" name="cc_myself" id="id_cc_myself"></td></tr>
Если форма связана с данными, то HTML-вывод будет включать эти данные соответствующим образом. Например, если поле представлено символом <input type="text">
, то данные будут находиться в атрибуте value
. Если поле представлено символом <input type="checkbox">
, то HTML-вывод будет включать checked
, если это уместно:
>>> data = {'subject': 'hello', ... 'message': 'Hi there', ... 'sender': 'foo@example.com', ... 'cc_myself': True} >>> f = ContactForm(data) >>> print(f) <tr><th><label for="id_subject">Subject:</label></th><td><input id="id_subject" type="text" name="subject" maxlength="100" value="hello" required></td></tr> <tr><th><label for="id_message">Message:</label></th><td><input type="text" name="message" id="id_message" value="Hi there" required></td></tr> <tr><th><label for="id_sender">Sender:</label></th><td><input type="email" name="sender" id="id_sender" value="foo@example.com" required></td></tr> <tr><th><label for="id_cc_myself">Cc myself:</label></th><td><input type="checkbox" name="cc_myself" id="id_cc_myself" checked></td></tr>
Этот вывод по умолчанию представляет собой двухколоночную HTML-таблицу, с <tr>
для каждого поля. Обратите внимание на следующее:
- Для гибкости вывод не включает теги
<table>
и</table>
, а также теги<form>
и</form>
или тег<input type="submit">
. Это ваша работа, чтобы сделать это. - Каждый тип поля имеет HTML-представление по умолчанию.
CharField
представляется символом<input type="text">
, аEmailField
—<input type="email">
.BooleanField(null=False)
представляется символом<input type="checkbox">
. Обратите внимание, что это всего лишь разумные значения по умолчанию; вы можете указать, какой HTML использовать для данного поля, используя виджеты, о которых мы расскажем в ближайшее время. - HTML
name
для каждого тега берется непосредственно из имени его атрибута в классеContactForm
. - Текстовая метка для каждого поля — например,
'Subject:'
,'Message:'
и'Cc myself:'
— генерируется из имени поля путем преобразования всех знаков подчеркивания в пробелы и перевода первой буквы в верхний регистр. Еще раз отметим, что это всего лишь разумные значения по умолчанию; вы также можете задать метки вручную. - Каждая текстовая метка окружена тегом HTML
<label>
, который указывает на соответствующее поле формы через егоid
. Егоid
, в свою очередь, формируется путем добавления'id_'
к имени поля. Атрибутыid
и теги<label>
включаются в вывод по умолчанию, чтобы следовать лучшим практикам, но вы можете изменить это поведение. - При выводе используется синтаксис HTML5, ориентированный на
<!DOCTYPE html>
. Например, он использует булевы атрибуты, такие какchecked
, а не стиль XHTMLchecked='checked'
.
Хотя вывод <table>
является стилем вывода по умолчанию, когда вы print
форму, доступны и другие стили вывода. Каждый стиль доступен как метод на объекте формы, и каждый метод вывода возвращает строку.
Рендеринг по умолчанию¶
Рендеринг по умолчанию при print
форме использует следующие методы и атрибуты.
template_name
¶
New in Django 4.0.
-
Form.
template_name
¶
Имя шаблона, отображаемого при преобразовании формы в строку, например, через print(form)
или в шаблоне через {{ form }}
.
По умолчанию свойство, возвращающее значение form_template_name
рендерера. Вы можете задать его как строковое имя шаблона, чтобы переопределить его для конкретного класса формы.
Changed in Django 4.1:
В старых версиях template_name
по умолчанию использовалось строковое значение 'django/forms/default.html'
.
render()
¶
New in Django 4.0.
-
Form.
render
(template_name=None, context=None, renderer=None)¶
Метод render вызывается методом __str__
, а также методами Form.as_table()
, Form.as_p()
и Form.as_ul()
. Все аргументы являются необязательными и используются по умолчанию:
template_name
:Form.template_name
context
: Значение, возвращаемоеForm.get_context()
renderer
: Значение, возвращаемоеForm.default_renderer
Передавая template_name
, вы можете настроить шаблон, используемый только для одного вызова.
get_context()
¶
New in Django 4.0.
-
Form.
get_context
()¶
Возвращает контекст шаблона для рендеринга формы.
Доступный контекст:
form
: Связанная форма.fields
: Все связанные поля, кроме скрытых.hidden_fields
: Все скрытые связанные поля.errors
: Все ошибки формы, не связанные с полями или скрытыми полями.
template_name_label
¶
New in Django 4.0.
-
Form.
template_name_label
¶
Шаблон, используемый для отображения <label>
поля, используемый при вызове BoundField.label_tag()
/legend_tag()
. Может быть изменен для каждой формы путем переопределения этого атрибута или, в более общем случае, путем переопределения шаблона по умолчанию, см. также Переопределение встроенных шаблонов форм.
Стили вывода¶
Помимо непосредственного отображения формы, например, в шаблоне с {{ form }}
, следующие вспомогательные функции служат в качестве прокси для Form.render()
, передавая определенное значение template_name
.
Эти помощники наиболее полезны в шаблоне, когда вам нужно переопределить рендерер формы или значение, предоставляемое формой, но вы не можете передать дополнительный параметр в render()
. Например, вы можете отобразить форму в виде неупорядоченного списка с помощью {{ form.as_ul }}
.
Каждый помощник сопрягает метод формы с атрибутом, задающим соответствующее имя шаблона.
as_div()
¶
-
Form.
template_name_div
¶
New in Django 4.1.
Шаблон, используемый as_div()
. По умолчанию: 'django/forms/div.html'
.
-
Form.
as_div
()¶
New in Django 4.1.
as_div()
отображает форму как серию элементов <div>
, каждый из которых <div>
содержит одно поле, например:
>>> f = ContactForm() >>> f.as_div()
… дает HTML как:
<div> <label for="id_subject">Subject:</label> <input type="text" name="subject" maxlength="100" required id="id_subject"> </div> <div> <label for="id_message">Message:</label> <input type="text" name="message" required id="id_message"> </div> <div> <label for="id_sender">Sender:</label> <input type="email" name="sender" required id="id_sender"> </div> <div> <label for="id_cc_myself">Cc myself:</label> <input type="checkbox" name="cc_myself" id="id_cc_myself"> </div>
Примечание
Из шаблонов и стилей вывода, предоставляемых каркасом, рекомендуется as_div()
, а не as_p()
, as_table()
и as_ul()
, поскольку в этом шаблоне реализованы <fieldset>
и <legend>
для группировки связанных вводимых данных, и в нем легче ориентироваться пользователям программы чтения с экрана.
as_p()
¶
-
Form.
template_name_p
¶
Шаблон, используемый as_p()
. По умолчанию: 'django/forms/p.html'
.
-
Form.
as_p
()¶
as_p()
отображает форму как серию тегов <p>
, каждый <p>
содержит одно поле:
>>> f = ContactForm() >>> f.as_p() '<p><label for="id_subject">Subject:</label> <input id="id_subject" type="text" name="subject" maxlength="100" required></p>n<p><label for="id_message">Message:</label> <input type="text" name="message" id="id_message" required></p>n<p><label for="id_sender">Sender:</label> <input type="text" name="sender" id="id_sender" required></p>n<p><label for="id_cc_myself">Cc myself:</label> <input type="checkbox" name="cc_myself" id="id_cc_myself"></p>' >>> print(f.as_p()) <p><label for="id_subject">Subject:</label> <input id="id_subject" type="text" name="subject" maxlength="100" required></p> <p><label for="id_message">Message:</label> <input type="text" name="message" id="id_message" required></p> <p><label for="id_sender">Sender:</label> <input type="email" name="sender" id="id_sender" required></p> <p><label for="id_cc_myself">Cc myself:</label> <input type="checkbox" name="cc_myself" id="id_cc_myself"></p>
as_ul()
¶
-
Form.
template_name_ul
¶
Шаблон, используемый as_ul()
. По умолчанию: 'django/forms/ul.html'
.
-
Form.
as_ul
()¶
as_ul()
отображает форму как серию тегов <li>
, каждый <li>
содержит одно поле. Он не включает <ul>
или </ul>
, так что вы можете указать любые HTML атрибуты на <ul>
для гибкости:
>>> f = ContactForm() >>> f.as_ul() '<li><label for="id_subject">Subject:</label> <input id="id_subject" type="text" name="subject" maxlength="100" required></li>n<li><label for="id_message">Message:</label> <input type="text" name="message" id="id_message" required></li>n<li><label for="id_sender">Sender:</label> <input type="email" name="sender" id="id_sender" required></li>n<li><label for="id_cc_myself">Cc myself:</label> <input type="checkbox" name="cc_myself" id="id_cc_myself"></li>' >>> print(f.as_ul()) <li><label for="id_subject">Subject:</label> <input id="id_subject" type="text" name="subject" maxlength="100" required></li> <li><label for="id_message">Message:</label> <input type="text" name="message" id="id_message" required></li> <li><label for="id_sender">Sender:</label> <input type="email" name="sender" id="id_sender" required></li> <li><label for="id_cc_myself">Cc myself:</label> <input type="checkbox" name="cc_myself" id="id_cc_myself"></li>
as_table()
¶
-
Form.
template_name_table
¶
Шаблон, используемый as_table()
. По умолчанию: 'django/forms/table.html'
.
-
Form.
as_table
()¶
as_table()
отображает форму в виде HTML <table>
:
>>> f = ContactForm() >>> f.as_table() '<tr><th><label for="id_subject">Subject:</label></th><td><input id="id_subject" type="text" name="subject" maxlength="100" required></td></tr>n<tr><th><label for="id_message">Message:</label></th><td><input type="text" name="message" id="id_message" required></td></tr>n<tr><th><label for="id_sender">Sender:</label></th><td><input type="email" name="sender" id="id_sender" required></td></tr>n<tr><th><label for="id_cc_myself">Cc myself:</label></th><td><input type="checkbox" name="cc_myself" id="id_cc_myself"></td></tr>' >>> print(f) <tr><th><label for="id_subject">Subject:</label></th><td><input id="id_subject" type="text" name="subject" maxlength="100" required></td></tr> <tr><th><label for="id_message">Message:</label></th><td><input type="text" name="message" id="id_message" required></td></tr> <tr><th><label for="id_sender">Sender:</label></th><td><input type="email" name="sender" id="id_sender" required></td></tr> <tr><th><label for="id_cc_myself">Cc myself:</label></th><td><input type="checkbox" name="cc_myself" id="id_cc_myself"></td></tr>
Стилизация требуемых или ошибочных строк формы¶
-
Form.
error_css_class
¶
-
Form.
required_css_class
¶
Довольно часто требуется стилизовать строки формы и поля, которые являются обязательными или содержат ошибки. Например, вы можете выделить обязательные строки формы жирным шрифтом, а ошибки — красным.
Класс Form
имеет пару крючков, которые можно использовать для добавления атрибутов class
к необходимым строкам или к строкам с ошибками: установите атрибуты Form.error_css_class
и/или Form.required_css_class
:
from django import forms class ContactForm(forms.Form): error_css_class = 'error' required_css_class = 'required' # ... and the rest of your fields here
После этого строкам будут присвоены классы "error"
и/или "required"
, по мере необходимости. HTML будет выглядеть примерно так:
>>> f = ContactForm(data) >>> print(f.as_table()) <tr class="required"><th><label class="required" for="id_subject">Subject:</label> ... <tr class="required"><th><label class="required" for="id_message">Message:</label> ... <tr class="required error"><th><label class="required" for="id_sender">Sender:</label> ... <tr><th><label for="id_cc_myself">Cc myself:<label> ... >>> f['subject'].label_tag() <label class="required" for="id_subject">Subject:</label> >>> f['subject'].legend_tag() <legend class="required" for="id_subject">Subject:</legend> >>> f['subject'].label_tag(attrs={'class': 'foo'}) <label for="id_subject" class="foo required">Subject:</label> >>> f['subject'].legend_tag(attrs={'class': 'foo'}) <legend for="id_subject" class="foo required">Subject:</legend>
Примечания по заказу полевых работ¶
В ярлыках as_p()
, as_ul()
и as_table()
поля отображаются в том порядке, в котором вы определили их в классе формы. Например, в примере ContactForm
поля определены в порядке subject
, message
, sender
, cc_myself
. Чтобы изменить порядок вывода HTML, измените порядок, в котором эти поля перечислены в классе.
Существует еще несколько способов индивидуализации заказа:
-
Form.
field_order
¶
По умолчанию Form.field_order=None
, что сохраняет порядок, в котором вы определили поля в классе формы. Если field_order
является списком имен полей, то поля упорядочиваются так, как указано в списке, а оставшиеся поля добавляются в соответствии с порядком по умолчанию. Неизвестные имена полей в списке игнорируются. Это позволяет отключить поле в подклассе, установив его в None
без необходимости переопределять порядок.
Вы также можете использовать аргумент Form.field_order
в Form
для переопределения порядка полей. Если Form
определяет field_order
и вы включаете field_order
при инстанцировании Form
, то последний field_order
будет иметь приоритет.
-
Form.
order_fields
(field_order)¶
Вы можете переставлять поля в любое время, используя order_fields()
со списком имен полей, как в field_order
.
Как отображаются ошибки¶
Если вы выводите связанный объект Form
, то при выводе автоматически выполняется валидация формы, если она еще не выполнена, и в HTML-вывод включаются ошибки валидации в виде <ul class="errorlist">
рядом с полем. Конкретное расположение сообщений об ошибках зависит от используемого метода вывода:
>>> data = {'subject': '', ... 'message': 'Hi there', ... 'sender': 'invalid email address', ... 'cc_myself': True} >>> f = ContactForm(data, auto_id=False) >>> print(f.as_div()) <div>Subject:<ul class="errorlist"><li>This field is required.</li></ul><input type="text" name="subject" maxlength="100" required></div> <div>Message:<textarea name="message" cols="40" rows="10" required>Hi there</textarea></div> <div>Sender:<ul class="errorlist"><li>Enter a valid email address.</li></ul><input type="email" name="sender" value="invalid email address" required></div> <div>Cc myself:<input type="checkbox" name="cc_myself" checked></div> >>> print(f.as_table()) <tr><th>Subject:</th><td><ul class="errorlist"><li>This field is required.</li></ul><input type="text" name="subject" maxlength="100" required></td></tr> <tr><th>Message:</th><td><textarea name="message" cols="40" rows="10" required></textarea></td></tr> <tr><th>Sender:</th><td><ul class="errorlist"><li>Enter a valid email address.</li></ul><input type="email" name="sender" value="invalid email address" required></td></tr> <tr><th>Cc myself:</th><td><input checked type="checkbox" name="cc_myself"></td></tr> >>> print(f.as_ul()) <li><ul class="errorlist"><li>This field is required.</li></ul>Subject: <input type="text" name="subject" maxlength="100" required></li> <li>Message: <textarea name="message" cols="40" rows="10" required></textarea></li> <li><ul class="errorlist"><li>Enter a valid email address.</li></ul>Sender: <input type="email" name="sender" value="invalid email address" required></li> <li>Cc myself: <input checked type="checkbox" name="cc_myself"></li> >>> print(f.as_p()) <p><ul class="errorlist"><li>This field is required.</li></ul></p> <p>Subject: <input type="text" name="subject" maxlength="100" required></p> <p>Message: <textarea name="message" cols="40" rows="10" required></textarea></p> <p><ul class="errorlist"><li>Enter a valid email address.</li></ul></p> <p>Sender: <input type="email" name="sender" value="invalid email address" required></p> <p>Cc myself: <input checked type="checkbox" name="cc_myself"></p>
Настройка формата списка ошибок¶
-
class
ErrorList
(initlist=None, error_class=None, renderer=None)¶ -
По умолчанию формы используют
django.forms.utils.ErrorList
для форматирования ошибок валидации.ErrorList
представляет собой объект типа списка, гдеinitlist
— это список ошибок. Кроме того, этот класс имеет следующие атрибуты и методы.-
error_class
¶ -
Классы CSS, которые будут использоваться при отображении списка ошибок. Любые предоставленные классы добавляются к классу по умолчанию
errorlist
.
-
renderer
¶ -
New in Django 4.0.
Определяет renderer, который будет использоваться для
ErrorList
. По умолчанию используетсяNone
, что означает использование рендерера по умолчанию, заданного параметромFORM_RENDERER
.
-
template_name
¶ -
New in Django 4.0.
Имя шаблона, используемого при вызове
__str__
илиrender()
. По умолчанию это'django/forms/errors/list/default.html'
, который является прокси для шаблона'ul.html'
.
-
template_name_text
¶ -
New in Django 4.0.
Имя шаблона, используемого при вызове
as_text()
. По умолчанию это'django/forms/errors/list/text.html'
. Этот шаблон отображает ошибки в виде списка пунктов.
-
template_name_ul
¶ -
New in Django 4.0.
Имя шаблона, используемого при вызове
as_ul()
. По умолчанию это'django/forms/errors/list/ul.html'
. Этот шаблон отображает ошибки в тегах<li>
с оберткой<ul>
с классами CSS, определеннымиerror_class
.
-
get_context
()¶ -
New in Django 4.0.
Возвращает контекст для отображения ошибок в шаблоне.
Доступный контекст:
errors
: Список ошибок.error_class
: Строка классов CSS.
-
render
(template_name=None, context=None, renderer=None)¶ -
New in Django 4.0.
Метод render вызывается как методом
__str__
, так и методомas_ul()
.Все аргументы являются необязательными и будут использоваться по умолчанию:
template_name
: Значение, возвращаемоеtemplate_name
context
: Значение, возвращаемоеget_context()
renderer
: Значение, возвращаемоеrenderer
-
as_text
()¶ -
Выводит список ошибок, используя шаблон, определенный
template_name_text
.
-
as_ul
()¶ -
Выводит список ошибок, используя шаблон, определенный
template_name_ul
.
Если вы хотите настроить отображение ошибок, это можно сделать, переопределив атрибут
template_name
или, в более общем случае, переопределив шаблон по умолчанию, см. также Переопределение встроенных шаблонов форм. -
Changed in Django 4.0:
Рендеринг ErrorList
был перенесен в шаблонизатор.
Не рекомендуется, начиная с версии 4.0: Возможность возвращать str
при вызове метода __str__
устарела. Вместо этого используйте шаблонизатор, который возвращает SafeString
.
Более детальный вывод¶
Методы as_p()
, as_ul()
и as_table()
являются ярлыками — это не единственный способ отображения объекта формы.
-
class
BoundField
[исходный код]¶ -
Используется для отображения HTML или атрибутов доступа для одного поля экземпляра
Form
.Метод
__str__()
этого объекта отображает HTML для данного поля.
Чтобы получить одно BoundField
, используйте синтаксис поиска по словарю в вашей форме, используя имя поля в качестве ключа:
>>> form = ContactForm() >>> print(form['subject']) <input id="id_subject" type="text" name="subject" maxlength="100" required>
Чтобы получить все объекты BoundField
, выполните итерацию формы:
>>> form = ContactForm() >>> for boundfield in form: print(boundfield) <input id="id_subject" type="text" name="subject" maxlength="100" required> <input type="text" name="message" id="id_message" required> <input type="email" name="sender" id="id_sender" required> <input type="checkbox" name="cc_myself" id="id_cc_myself">
Вывод для конкретного поля соответствует настройке auto_id
объекта формы:
>>> f = ContactForm(auto_id=False) >>> print(f['message']) <input type="text" name="message" required> >>> f = ContactForm(auto_id='id_%s') >>> print(f['message']) <input type="text" name="message" id="id_message" required>
Атрибуты BoundField
¶
-
BoundField.
auto_id
¶ -
Атрибут HTML ID для данного
BoundField
. Возвращает пустую строку, еслиForm.auto_id
являетсяFalse
.
-
BoundField.
data
¶ -
Это свойство возвращает данные для этого
BoundField
, извлеченные методомvalue_from_datadict()
виджета, илиNone
, если они не были переданы:>>> unbound_form = ContactForm() >>> print(unbound_form['subject'].data) None >>> bound_form = ContactForm(data={'subject': 'My Subject'}) >>> print(bound_form['subject'].data) My Subject
-
BoundField.
errors
¶ -
list-like object, который при печати отображается как HTML
<ul class="errorlist">
:>>> data = {'subject': 'hi', 'message': '', 'sender': '', 'cc_myself': ''} >>> f = ContactForm(data, auto_id=False) >>> print(f['message']) <input type="text" name="message" required> >>> f['message'].errors ['This field is required.'] >>> print(f['message'].errors) <ul class="errorlist"><li>This field is required.</li></ul> >>> f['subject'].errors [] >>> print(f['subject'].errors) >>> str(f['subject'].errors) ''
-
BoundField.
field
¶ -
Экземпляр формы
Field
из класса формы, в который обернут этотBoundField
.
-
BoundField.
form
¶ -
Экземпляр
Form
, к которому привязан данныйBoundField
.
-
BoundField.
help_text
¶ -
Значение
help_text
поля.
-
BoundField.
html_name
¶ -
Имя, которое будет использоваться в HTML атрибуте виджета
name
. При этом учитывается формаprefix
.
-
BoundField.
id_for_label
¶ -
Используйте это свойство для отображения ID этого поля. Например, если вы вручную создаете
<label>
в своем шаблоне (несмотря на то, чтоlabel_tag()
/legend_tag()
сделает это за вас):<label for="{{ form.my_field.id_for_label }}">...</label>{{ my_field }}
По умолчанию это будет имя поля с префиксом
id_
(»id_my_field
» для примера выше). Вы можете изменить ID, установивattrs
в виджете поля. Например, объявив поле следующим образом:my_field = forms.CharField(widget=forms.TextInput(attrs={'id': 'myFIELD'}))
и, используя вышеприведенный шаблон, можно получить что-то вроде:
<label for="myFIELD">...</label><input id="myFIELD" type="text" name="my_field" required>
-
BoundField.
initial
¶ -
Используйте
BoundField.initial
для получения исходных данных для поля формы. Он извлекает данные изForm.initial
, если они присутствуют, в противном случае пробуетField.initial
. Вызываемые значения оцениваются. Дополнительные примеры см. в разделе Начальные значения формы.BoundField.initial
кэширует свое возвращаемое значение, что особенно полезно при работе с callables, возвращаемые значения которых могут изменяться (например,datetime.now
илиuuid.uuid4
):>>> from datetime import datetime >>> class DatedCommentForm(CommentForm): ... created = forms.DateTimeField(initial=datetime.now) >>> f = DatedCommentForm() >>> f['created'].initial datetime.datetime(2021, 7, 27, 9, 5, 54) >>> f['created'].initial datetime.datetime(2021, 7, 27, 9, 5, 54)
Рекомендуется использовать
BoundField.initial
вместоget_initial_for_field()
.
-
BoundField.
is_hidden
¶ -
Возвращает
True
, если виджет этогоBoundField
скрыт.
-
BoundField.
label
¶ -
label
поля. Используется вlabel_tag()
/legend_tag()
.
-
BoundField.
name
¶ -
Имя этого поля в форме:
>>> f = ContactForm() >>> print(f['subject'].name) subject >>> print(f['message'].name) message
-
BoundField.
use_fieldset
¶ -
New in Django 4.1.
Возвращает значение атрибута
use_fieldset
этого виджета BoundField.
-
BoundField.
widget_type
¶ -
Возвращает имя класса виджета обернутого поля в нижнем регистре, с удаленными концевыми строками
input
илиwidget
. Это может быть использовано при создании форм, в которых компоновка зависит от типа виджета. Например:{% for field in form %} {% if field.widget_type == 'checkbox' %} # render one way {% else %} # render another way {% endif %} {% endfor %}
Методы BoundField
¶
-
BoundField.
as_hidden
(attrs=None, **kwargs)[исходный код]¶ -
Возвращает строку HTML для представления этого как
<input type="hidden">
.**kwargs
передаются вas_widget()
.Этот метод используется в основном для внутренних целей. Вместо него следует использовать виджет.
-
BoundField.
as_widget
(widget=None, attrs=None, only_initial=False)[исходный код]¶ -
Рендерит поле путем рендеринга переданного виджета, добавляя любые HTML-атрибуты, переданные как
attrs
. Если виджет не указан, то будет использован виджет поля по умолчанию.only_initial
используется внутренними модулями Django и не должен быть установлен явно.
-
BoundField.
css_classes
(extra_classes=None)[исходный код]¶ -
Когда вы используете ярлыки рендеринга Django, CSS-классы используются для обозначения обязательных полей формы или полей, содержащих ошибки. Если вы вручную отрисовываете форму, вы можете получить доступ к этим CSS-классам, используя метод
css_classes
:>>> f = ContactForm(data={'message': ''}) >>> f['message'].css_classes() 'required'
Если вы хотите предоставить некоторые дополнительные классы в дополнение к классам error и required, которые могут потребоваться, вы можете предоставить эти классы в качестве аргумента:
>>> f = ContactForm(data={'message': ''}) >>> f['message'].css_classes('foo bar') 'foo bar required'
-
BoundField.
label_tag
(contents=None, attrs=None, label_suffix=None, tag=None)[исходный код]¶ -
Отображает тег label для поля формы, используя шаблон, указанный
Form.template_name_label
.Доступный контекст:
field
: Этот экземплярBoundField
.contents
: По умолчанию конкатенированная строка изBoundField.label
иForm.label_suffix
(илиField.label_suffix
, если задано). Это можно переопределить аргументамиcontents
иlabel_suffix
.attrs
:dict
, содержащийfor
,Form.required_css_class
иid
.id
генерируется виджетом поляattrs
илиBoundField.auto_id
. Дополнительные атрибуты могут быть предоставлены аргументомattrs
.use_tag
: Булево значение, которое равноTrue
, если метка имеетid
. ЕслиFalse
, шаблон по умолчанию опускаетtag
.tag
: Необязательная строка для настройки тега, по умолчаниюlabel
.
Совет
В вашем шаблоне
field
является экземпляромBoundField
. Поэтомуfield.field
обращается кBoundField.field
, будучи полем, которое вы объявили, например,forms.CharField
.Чтобы отдельно отобразить тег label поля формы, можно вызвать его метод
label_tag()
:>>> f = ContactForm(data={'message': ''}) >>> print(f['message'].label_tag()) <label for="id_message">Message:</label>
Если вы хотите настроить рендеринг, это можно сделать, переопределив атрибут
Form.template_name_label
или, в более общем случае, переопределив шаблон по умолчанию, см. также Переопределение встроенных шаблонов форм.Changed in Django 4.0:
Теперь этикетка отображается с помощью шаблонизатора.
Changed in Django 4.1:
Был добавлен аргумент
tag
.
-
BoundField.
legend_tag
(contents=None, attrs=None, label_suffix=None)¶ -
New in Django 4.1.
Вызывает
label_tag()
сtag='legend'
для рендеринга метки с тегами<legend>
. Это полезно при рендеринге виджетов радио и нескольких флажков, где<legend>
может быть более уместным, чем<label>
.
-
BoundField.
value
()[исходный код]¶ -
Используйте этот метод для отображения необработанного значения этого поля так, как оно было бы отображено с помощью
Widget
:>>> initial = {'subject': 'welcome'} >>> unbound_form = ContactForm(initial=initial) >>> bound_form = ContactForm(data={'subject': 'hi'}, initial=initial) >>> print(unbound_form['subject'].value()) welcome >>> print(bound_form['subject'].value()) hi
Настройка BoundField
¶
Если вам необходимо получить доступ к дополнительной информации о поле формы в шаблоне и использование подкласса Field
недостаточно, рассмотрите также возможность настройки BoundField
.
Поле пользовательской формы может переопределять get_bound_field()
:
-
Field.
get_bound_field
(form, field_name)[исходный код]¶ -
Принимает экземпляр
Form
и имя поля. Возвращаемое значение будет использоваться при обращении к полю в шаблоне. Скорее всего, это будет экземпляр подклассаBoundField
.
Если у вас есть, например, GPSCoordinatesField
и вы хотите иметь возможность доступа к дополнительной информации о координатах в шаблоне, это можно реализовать следующим образом:
class GPSCoordinatesBoundField(BoundField): @property def country(self): """ Return the country the coordinates lie in or None if it can't be determined. """ value = self.value() if value: return get_country_from_coordinates(value) else: return None class GPSCoordinatesField(Field): def get_bound_field(self, form, field_name): return GPSCoordinatesBoundField(form, self, field_name)
Теперь вы можете получить доступ к стране в шаблоне с помощью {{ form.coordinates.country }}
.
Привязка загруженных файлов к форме¶
Работа с формами, имеющими поля FileField
и ImageField
, немного сложнее, чем с обычной формой.
Во-первых, чтобы загрузить файлы, вам нужно убедиться, что ваш элемент <form>
правильно определяет enctype
как "multipart/form-data"
:
<form enctype="multipart/form-data" method="post" action="/foo/">
Во-вторых, когда вы используете форму, вам необходимо связать данные файла. Файловые данные обрабатываются отдельно от обычных данных формы, поэтому, если ваша форма содержит FileField
и ImageField
, вам необходимо указать второй аргумент при привязке формы. Таким образом, если мы расширим нашу ContactForm и добавим в нее ImageField
, называемую mugshot
, нам нужно будет связать данные файла, содержащего изображение кружки:
# Bound form with an image field >>> from django.core.files.uploadedfile import SimpleUploadedFile >>> data = {'subject': 'hello', ... 'message': 'Hi there', ... 'sender': 'foo@example.com', ... 'cc_myself': True} >>> file_data = {'mugshot': SimpleUploadedFile('face.jpg', <file data>)} >>> f = ContactFormWithMugshot(data, file_data)
На практике вы обычно указываете request.FILES
в качестве источника данных файла (точно так же, как вы используете request.POST
в качестве источника данных формы):
# Bound form with an image field, data from the request >>> f = ContactFormWithMugshot(request.POST, request.FILES)
Конструирование несвязанной формы происходит так же, как и всегда — опустите данные формы и данные файла:
# Unbound form with an image field >>> f = ContactFormWithMugshot()
Тестирование многочастных форм¶
-
Form.
is_multipart
()¶
Если вы пишете многоразовые представления или шаблоны, вы можете не знать заранее, является ли ваша форма многочастной или нет. Метод is_multipart()
сообщает вам, требует ли форма многочастной кодировки для отправки:
>>> f = ContactFormWithMugshot() >>> f.is_multipart() True
Вот пример того, как это можно использовать в шаблоне:
{% if form.is_multipart %} <form enctype="multipart/form-data" method="post" action="/foo/"> {% else %} <form method="post" action="/foo/"> {% endif %} {{ form }} </form>
Подклассификация форм¶
Если у вас есть несколько классов Form
, которые имеют общие поля, вы можете использовать подклассификацию для устранения избыточности.
Когда вы создаете подкласс пользовательского класса Form
, полученный подкласс будет включать все поля родительского класса (классов), а затем поля, которые вы определите в подклассе.
В этом примере ContactFormWithPriority
содержит все поля из ContactForm
, плюс дополнительное поле priority
. Поля ContactForm
упорядочены в первую очередь:
>>> class ContactFormWithPriority(ContactForm): ... priority = forms.CharField() >>> f = ContactFormWithPriority(auto_id=False) >>> print(f.as_div()) <div>Subject:<input type="text" name="subject" maxlength="100" required></div> <div>Message:<textarea name="message" cols="40" rows="10" required></textarea></div> <div>Sender:<input type="email" name="sender" required></div> <div>Cc myself:<input type="checkbox" name="cc_myself"></div> <div>Priority:<input type="text" name="priority" required></div>
Можно подклассифицировать несколько форм, рассматривая формы как миксины. В этом примере BeatleForm
является подклассом PersonForm
и InstrumentForm
(в таком порядке), и его список полей включает поля из родительских классов:
>>> from django import forms >>> class PersonForm(forms.Form): ... first_name = forms.CharField() ... last_name = forms.CharField() >>> class InstrumentForm(forms.Form): ... instrument = forms.CharField() >>> class BeatleForm(InstrumentForm, PersonForm): ... haircut_type = forms.CharField() >>> b = BeatleForm(auto_id=False) >>> print(b.as_div()) <div>First name:<input type="text" name="first_name" required></div> <div>Last name:<input type="text" name="last_name" required></div> <div>Instrument:<input type="text" name="instrument" required></div> <div>Haircut type:<input type="text" name="haircut_type" required></div>
Можно декларативно удалить Field
, унаследованное от родительского класса, установив имя поля None
в подклассе. Например:
>>> from django import forms >>> class ParentForm(forms.Form): ... name = forms.CharField() ... age = forms.IntegerField() >>> class ChildForm(ParentForm): ... name = None >>> list(ChildForm().fields) ['age']
Префиксы для форм¶
-
Form.
prefix
¶
Вы можете разместить несколько форм Django внутри одного тега <form>
. Чтобы дать каждой Form
свое собственное пространство имен, используйте аргумент prefix
ключевое слово:
>>> mother = PersonForm(prefix="mother") >>> father = PersonForm(prefix="father") >>> print(mother.as_div()) <div><label for="id_mother-first_name">First name:</label><input type="text" name="mother-first_name" required id="id_mother-first_name"></div> <div><label for="id_mother-last_name">Last name:</label><input type="text" name="mother-last_name" required id="id_mother-last_name"></div> >>> print(father.as_div()) <div><label for="id_father-first_name">First name:</label><input type="text" name="father-first_name" required id="id_father-first_name"></div> <div><label for="id_father-last_name">Last name:</label><input type="text" name="father-last_name" required id="id_father-last_name"></div>
Префикс также может быть указан на форме class:
>>> class PersonForm(forms.Form): ... ... ... prefix = 'person'
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 aField
is the first step in every
validation. It coerces the value to a correct datatype and raises
ValidationError
if that is not possible. This method accepts the raw
value from the widget and returns the converted value. For example, a
FloatField
will turn the data into a Pythonfloat
or raise a
ValidationError
. -
The
validate()
method on aField
handles field-specific validation
that is not suitable for a validator. It takes a value that has been
coerced to a correct datatype and raisesValidationError
on 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 aField
runs 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 aField
subclass 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_data
dictionary 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_data
and remember that it will be a Python object
at this point, not the original string submitted in the form (it will be
incleaned_data
because the general fieldclean()
method, above,
has already cleaned the data once).For example, if you wanted to validate that the contents of a
CharField
calledserialnumber
was 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 fieldA
is supplied, fieldB
must 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’serrors
attribute 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 aModelForm
subclass. (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
code
to 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
gettext
to 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 ValidationError
s (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 code
s 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
.
Валидация форм и полей¶
Валидация формы происходит при очистке данных. Если вы хотите настроить этот процесс, есть различные места для внесения изменений, каждое из которых служит для разных целей. В процессе обработки формы выполняются три типа методов очистки. Обычно они выполняются, когда вы вызываете метод 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()
значительно выигрывают от полнофункциональных ValidationError
s (с code
именем и params
словарем).
Возникновение множества ошибок¶
Если вы обнаружили несколько ошибок во время работы метода очистки и хотите сигнализировать обо всех из них отправителю формы, можно передать список ошибок конструктору ValidationError
.
Как и выше, рекомендуется передавать список экземпляров ValidationError
с code
s и 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
- Назад (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 использует те же самые техники, которые мы изучали в предыдущих частях руководства (при показе информации из наших моделей): отображение получает запрос, выполняет необходимые действия, включающие в себя чтение данных из моделей, генерацию и возврат страницы 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
message
displays success message oncemessages.add_message(request, messages.SUCCESS, ".....")
is called it is just like the flash message. - The
form.errors
is called on validation has failed. - The
form.non_field_errors
display 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.
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 aField
is the first step in every
validation. It coerces the value to a correct datatype and raises
ValidationError
if that is not possible. This method accepts the raw
value from the widget and returns the converted value. For example, a
FloatField
will turn the data into a Pythonfloat
or raise a
ValidationError
. -
The
validate()
method on aField
handles field-specific validation
that is not suitable for a validator. It takes a value that has been
coerced to a correct datatype and raisesValidationError
on 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 aField
runs 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 aField
subclass 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_data
dictionary 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_data
and remember that it will be a Python object
at this point, not the original string submitted in the form (it will be
incleaned_data
because the general fieldclean()
method, above,
has already cleaned the data once).For example, if you wanted to validate that the contents of a
CharField
calledserialnumber
was 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 fieldA
is supplied, fieldB
must 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’serrors
attribute 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 aModelForm
subclass. (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
code
to 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
gettext
to 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 ValidationError
s (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 code
s 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
.
Проверка формы и поля
Проверка формы происходит при очистке данных. Если вы хотите настроить этот процесс, есть несколько мест для внесения изменений, каждое из которых служит своей цели. Во время обработки формы запускаются три типа методов очистки. Обычно они выполняются, когда вы вызываете метод is_valid()
в форме. Есть и другие вещи, которые также могут вызвать очистку и проверку (доступ к атрибуту errors
или прямой вызов full_clean()
), но обычно они не нужны.
В общем, любой метод очистки может вызвать ValidationError
, если есть проблема с данными, которые он обрабатывает, передав соответствующую информацию конструктору ValidationError
. Ниже приведены рекомендации по повышению ValidationError
. Если ValidationError
не вызывается, метод должен вернуть очищенные (нормализованные) данные в виде объекта Python.
Большую часть проверки можно выполнить с помощью валидаторов — помощников, которые можно использовать повторно. Валидаторы — это функции (или вызываемые объекты), которые принимают один аргумент и вызывают ValidationError
при неверном вводе. Валидаторы запускаются после вызова методов поля to_python
и validate
.
Проверка формы разделена на несколько этапов,которые можно настроить или переопределить:
- Метод
to_python()
дляField
является первым шагом в каждой проверке. Он приводит значение к правильному типу данных и вызываетValidationError
, если это невозможно. Этот метод принимает необработанное значение из виджета и возвращает преобразованное значение. Например,FloatField
превратит данные в число сfloat
Python или вызоветValidationError
. - Метод
validate()
дляField
обрабатывает специфическую для поля проверку, которая не подходит для валидатора. Он принимает значение, которое было приведено к правильному типу данных, и вызываетValidationError
при любой ошибке. Этот метод ничего не возвращает и не должен изменять значение. Вы должны переопределить его, чтобы обрабатывать логику проверки, которую вы не можете или не хотите использовать в валидаторе. - Метод
run_validators()
дляField
запускает все валидаторы поля и объединяет все ошибки в одинValidationError
. Вам не нужно переопределять этот метод. - Метод
clean()
в подклассеField
отвечает за запускto_python()
,validate()
иrun_validators()
в правильном порядке и распространение их ошибок. Если в любое время любой из методов вызываетValidationError
, проверка останавливается, и возникает эта ошибка. Этот метод возвращает чистые данные, которые затем вставляются в словарьcleaned_data
формы. -
Метод
clean_<fieldname>()
вызывается в подклассе формы, где<fieldname>
заменяется именем атрибута поля формы. Этот метод выполняет любую очистку, относящуюся к этому конкретному атрибуту, независимо от типа поля, которым он является. Этому методу не передаются никакие параметры. Вам нужно будет найти значение поля вself.cleaned_data
и помнить, что в этот момент это будет объект Python, а не исходная строка, представленная в форме (она будет вcleaned_data
потому что методclean()
общего поля , выше, уже один раз очистил данные).Например, если вы хотите проверить уникальность содержимого
CharField
, называемогоserialnumber
clean_serialnumber()
. Вам не нужно конкретное поле (этоCharField
), но вам нужна часть проверки, специфичная для поля формы, и, возможно, очистка/нормализация данных.Возвращаемое значение этого метода заменяет существующее значение в
cleaned_data
, поэтому оно должно быть значением поля изcleaned_data
(даже если этот метод не изменил его) или новым очищенным значением. -
clean()
подкласса формы может выполнять проверку, требующую доступа к нескольким полям формы. Здесь вы можете поставить такие проверки, как «если полеA
указано, полеB
должно содержать действительный адрес электронной почты». Этот метод может вернуть совершенно другой словарь, если захочет, который будет использоваться какcleaned_data
.Поскольку методы проверки поля были запущены к моменту
clean()
, у вас также есть доступ к атрибутуerrors
формы, который содержит все ошибки, вызванные очисткой отдельных полей.Обратите внимание, что любые ошибки, вызванные вашим переопределением
Form.clean()
, не будут связаны с каким-либо конкретным полем. Они попадают в специальное «поле» (называемое__all__
), к которому вы можете получить доступ через методnon_field_errors()
, если вам это нужно. Если вы хотите прикрепить ошибки к определенному полю в форме, вам нужно вызватьadd_error()
.Также обратите внимание, что при переопределении метода
clean()
подклассаModelForm
есть особые соображения . (см. документацию ModelForm для получения дополнительной информации)
Эти методы выполняются в порядке, указанном выше, по одному полю за раз. То есть для каждого поля в форме (в порядке их объявления в определении формы) Field.clean()
метод Field.clean () (или его переопределение), затем clean_<fieldname>()
. Наконец, после того, как эти два метода запущены для каждого поля, Form.clean()
метод Form.clean () или его переопределение независимо от того, были ли в предыдущих методах ошибки.
Примеры каждого из этих методов приведены ниже.
Как уже упоминалось, любой из этих методов может вызвать ValidationError
. Для любого поля, если метод Field.clean()
вызывает ValidationError
, какой-либо специфический для поля метод очистки не вызывается. Однако методы очистки для всех оставшихся полей все еще выполняются.
Raising ValidationError
Для того,чтобы сделать сообщения об ошибках гибкими и легко преодолеваемыми,обратите внимание на следующие рекомендации:
-
Предоставьте описательный
code
ошибки конструктору:ValidationError(_('Invalid value'), code='invalid') 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
, чтобы включить перевод:ValidationError(_('Invalid value')) 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
s (с code
именем и params
словарь).
Повышение количества ошибок
Если вы обнаружите несколько ошибок во время метода очистки и хотите сообщить обо всех из них отправителю формы, можно передать список ошибок конструктору ValidationError
.
Как и выше, рекомендуется передавать список экземпляров ValidationError
с code
s и params
, но также будет работать список строк:
# Good raise ValidationError([ ValidationError(_('Error 1'), code='error1'), ValidationError(_('Error 2'), code='error2'), ]) # Bad raise ValidationError([ _('Error 1'), _('Error 2'), ])
Использование валидации на практике
В предыдущих разделах объяснялось,как работает валидация в целом для форм.Так как иногда может быть проще поставить вещи на место,видя каждую используемую функцию,вот серия маленьких примеров,которые используют каждую из предыдущих функций.
Using validators
Поля формы (и модели) Django поддерживают использование служебных функций и классов, известных как валидаторы. Валидатор — это вызываемый объект или функция, которая принимает значение и ничего не возвращает, если значение допустимо, или вызывает ValidationError
, если нет. Их можно передать конструктору поля через аргумент validators
поля или определить в самом классе Field
с помощью атрибута default_validators
.
Валидаторы можно использовать для проверки значений внутри поля, давайте взглянем на SlugField
Django :
from django.core import validators from django.forms import CharField class SlugField(CharField): default_validators = [validators.validate_slug]
Как видите, SlugField
— это CharField
с настроенным валидатором, который проверяет, что отправленный текст подчиняется некоторым правилам символов. Это также можно сделать для определения поля так:
slug = forms.SlugField()
эквивалентно:
slug = forms.CharField(validators=[validators.validate_slug])
Распространенные случаи, такие как проверка по электронной почте или регулярному выражению, могут быть обработаны с использованием существующих классов проверки, доступных в Django. Например, validators.validate_slug
является экземпляром RegexValidator
, построенным с первым аргументом, являющимся шаблоном: ^[-a-zA-Z0-9_]+$
. См. Раздел о написании валидаторов, чтобы увидеть список того, что уже доступно, и пример того, как написать валидатор.
Очистка полей формы по умолчанию
Давайте сначала создадим поле пользовательской формы,которое проверяет,что его входные данные-это строка,содержащая адреса электронной почты,разделенные запятыми.Полный класс выглядит следующим образом:
from django import forms from django.core.validators import validate_email class MultiEmailField(forms.Field): def to_python(self, value): """Normalize data to a list of strings.""" if not value: return [] return value.split(',') def validate(self, value): """Check if value consists only of valid emails.""" super().validate(value) for email in value: validate_email(email)
В каждой форме,использующей это поле,эти методы будут выполняться до того,как с данными поля можно будет сделать что-либо еще.Это очистка,специфичная для данного типа поля,независимо от того,как оно будет использоваться в дальнейшем.
Давайте создадим ContactForm
, чтобы продемонстрировать, как вы будете использовать это поле:
class ContactForm(forms.Form): subject = forms.CharField(max_length=100) message = forms.CharField() sender = forms.EmailField() recipients = MultiEmailField() cc_myself = forms.BooleanField(required=False)
Используйте MultiEmailField
, как и любое другое поле формы. Когда is_valid()
метод вызывается на форме, MultiEmailField.clean()
метод будет работать как часть процесса очистки , и это, в свою очередь, вызывать пользовательские to_python()
и validate()
методы.
Очистка конкретного атрибута поля
Продолжая предыдущий пример, предположим, что в нашей ContactForm
мы хотим убедиться, что поле recipients
всегда содержит адрес "[email protected]"
. Это проверка, специфичная для нашей формы, поэтому мы не хотим помещать ее в общий класс MultiEmailField
. Вместо этого мы пишем метод очистки, который работает с полем recipients
from django import forms from django.core.exceptions import ValidationError class ContactForm(forms.Form): ... def clean_recipients(self): data = self.cleaned_data['recipients'] if "[email protected]" not in data: raise ValidationError("You have forgotten about Fred!") 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): ... def clean(self): cleaned_data = super().clean() cc_myself = cleaned_data.get("cc_myself") subject = cleaned_data.get("subject") if cc_myself and subject: 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): ... def clean(self): cleaned_data = super().clean() cc_myself = cleaned_data.get("cc_myself") subject = cleaned_data.get("subject") if cc_myself and subject and "help" not in subject: msg = "Must put 'help' in subject when cc'ing yourself." self.add_error('cc_myself', msg) self.add_error('subject', msg)
Второй аргумент add_error()
может быть строкой или предпочтительно экземпляром ValidationError
. См. Повышение ValidationError для получения дополнительной информации. Обратите внимание, что add_error()
автоматически удаляет поле из cleaned_data
.
Django
4.1
-
Форма модели Функции
Ссылка на API API модели формы.
-
API рендеринга формы
Виджеты форм в Django отображаются с помощью системы шаблонизаторов.
-
Widgets
Виджет-это представление Django элемента ввода HTML.
-
Built-in widgets
Django предоставляет представление всех основных HTML виджетов,плюс некоторые часто используемые группы в модуле django.forms.widgets,включая текст ввода,различные