Отображение ошибок валидации django

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

Lucas Noah's user avatar

Couple of things:

  1. You are not taking the POST being sent to the POST.

  2. 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

karthikr's user avatar

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

Keivan Ipchi Hagh's user avatar

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 a Field 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 Python float or raise a
    ValidationError.

  • The validate() method on a Field handles field-specific validation
    that is not suitable for a validator. It takes a value that has been
    coerced to a correct datatype and raises ValidationError 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 a Field 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 a Field subclass is responsible for running
    to_python(), validate(), and run_validators() in the correct
    order and propagating their errors. If, at any time, any of the methods
    raise ValidationError, 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
    in self.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
    in cleaned_data because the general field clean() method, above,
    has already cleaned the data once).

    For example, if you wanted to validate that the contents of a
    CharField called serialnumber was unique,
    clean_serialnumber() would be the right place to do this. You don’t
    need a specific field (it’s a CharField), 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 from cleaned_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 field A is supplied, field B must contain a valid email address”.
    This method can return a completely different dictionary if it wishes, which
    will be used as the cleaned_data.

    Since the field validation methods have been run by the time clean() is
    called, you also have access to the form’s errors 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
    the clean() method of a ModelForm 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 ValidationErrors (with a code name
and a params dictionary).

Raising multiple errors¶

If you detect multiple errors during a cleaning method and wish to signal all
of them to the form submitter, it is possible to pass a list of errors to the
ValidationError constructor.

As above, it is recommended to pass a list of ValidationError instances
with codes and params but a list of strings will also work:

# Good
raise ValidationError(
    [
        ValidationError(_("Error 1"), code="error1"),
        ValidationError(_("Error 2"), code="error2"),
    ]
)

# Bad
raise ValidationError(
    [
        _("Error 1"),
        _("Error 2"),
    ]
)

Using validation in practice¶

The previous sections explained how validation works in general for forms.
Since it can sometimes be easier to put things into place by seeing each
feature in use, here are a series of small examples that use each of the
previous features.

Using validators¶

Django’s form (and model) fields support use of utility functions and classes
known as validators. A validator is a callable object or function that takes a
value and returns nothing if the value is valid or raises a
ValidationError if not. These can be passed to a
field’s constructor, via the field’s validators argument, or defined on the
Field class itself with the default_validators
attribute.

Validators can be used to validate values inside the field, let’s have a look
at Django’s SlugField:

from django.core import validators
from django.forms import CharField


class SlugField(CharField):
    default_validators = [validators.validate_slug]

As you can see, SlugField is a CharField with a customized validator
that validates that submitted text obeys to some character rules. This can also
be done on field definition so:

is equivalent to:

slug = forms.CharField(validators=[validators.validate_slug])

Common cases such as validating against an email or a regular expression can be
handled using existing validator classes available in Django. For example,
validators.validate_slug is an instance of
a RegexValidator constructed with the first
argument being the pattern: ^[-a-zA-Z0-9_]+$. See the section on
writing validators to see a list of what is already
available and for an example of how to write a validator.

Form field default cleaning¶

Let’s first create a custom form field that validates its input is a string
containing comma-separated email addresses. The full class looks like this:

from django import forms
from django.core.validators import validate_email


class MultiEmailField(forms.Field):
    def to_python(self, value):
        """Normalize data to a list of strings."""
        # Return an empty list if no input was given.
        if not value:
            return []
        return value.split(",")

    def validate(self, value):
        """Check if value consists only of valid emails."""
        # Use the parent's handling of required fields, etc.
        super().validate(value)
        for email in value:
            validate_email(email)

Every form that uses this field will have these methods run before anything
else can be done with the field’s data. This is cleaning that is specific to
this type of field, regardless of how it is subsequently used.

Let’s create a ContactForm to demonstrate how you’d use this field:

class ContactForm(forms.Form):
    subject = forms.CharField(max_length=100)
    message = forms.CharField()
    sender = forms.EmailField()
    recipients = MultiEmailField()
    cc_myself = forms.BooleanField(required=False)

Use MultiEmailField like any other form field. When the is_valid()
method is called on the form, the MultiEmailField.clean() method will be
run as part of the cleaning process and it will, in turn, call the custom
to_python() and validate() methods.

Cleaning a specific field attribute¶

Continuing on from the previous example, suppose that in our ContactForm,
we want to make sure that the recipients field always contains the address
"fred@example.com". This is validation that is specific to our form, so we
don’t want to put it into the general MultiEmailField class. Instead, we
write a cleaning method that operates on the recipients field, like so:

from django import forms
from django.core.exceptions import ValidationError


class ContactForm(forms.Form):
    # Everything as before.
    ...

    def clean_recipients(self):
        data = self.cleaned_data["recipients"]
        if "fred@example.com" not in data:
            raise ValidationError("You have forgotten about Fred!")

        # Always return a value to use as the new cleaned data, even if
        # this method didn't change it.
        return data

Cleaning and validating fields that depend on each other¶

Suppose we add another requirement to our contact form: if the cc_myself
field is True, the subject must contain the word "help". We are
performing validation on more than one field at a time, so the form’s
clean() method is a good spot to do this. Notice that we are
talking about the clean() method on the form here, whereas earlier we were
writing a clean() method on a field. It’s important to keep the field and
form difference clear when working out where to validate things. Fields are
single data points, forms are a collection of fields.

By the time the form’s clean() method is called, all the individual field
clean methods will have been run (the previous two sections), so
self.cleaned_data will be populated with any data that has survived so
far. So you also need to remember to allow for the fact that the fields you
are wanting to validate might not have survived the initial individual field
checks.

There are two ways to report any errors from this step. Probably the most
common method is to display the error at the top of the form. To create such
an error, you can raise a ValidationError from the clean() method. For
example:

from django import forms
from django.core.exceptions import ValidationError


class ContactForm(forms.Form):
    # Everything as before.
    ...

    def clean(self):
        cleaned_data = super().clean()
        cc_myself = cleaned_data.get("cc_myself")
        subject = cleaned_data.get("subject")

        if cc_myself and subject:
            # Only do something if both fields are valid so far.
            if "help" not in subject:
                raise ValidationError(
                    "Did not send for 'help' in the subject despite " "CC'ing yourself."
                )

In this code, if the validation error is raised, the form will display an
error message at the top of the form (normally) describing the problem. Such
errors are non-field errors, which are displayed in the template with
{{ form.non_field_errors }}.

The call to super().clean() in the example code ensures that any validation
logic in parent classes is maintained. If your form inherits another that
doesn’t return a cleaned_data dictionary in its clean() method (doing
so is optional), then don’t assign cleaned_data to the result of the
super() call and use self.cleaned_data instead:

def clean(self):
    super().clean()
    cc_myself = self.cleaned_data.get("cc_myself")
    ...

The second approach for reporting validation errors might involve assigning the
error message to one of the fields. In this case, let’s assign an error message
to both the “subject” and “cc_myself” rows in the form display. Be careful when
doing this in practice, since it can lead to confusing form output. We’re
showing what is possible here and leaving it up to you and your designers to
work out what works effectively in your particular situation. Our new code
(replacing the previous sample) looks like this:

from django import forms


class ContactForm(forms.Form):
    # Everything as before.
    ...

    def clean(self):
        cleaned_data = super().clean()
        cc_myself = cleaned_data.get("cc_myself")
        subject = cleaned_data.get("subject")

        if cc_myself and subject and "help" not in subject:
            msg = "Must put 'help' in subject when cc'ing yourself."
            self.add_error("cc_myself", msg)
            self.add_error("subject", msg)

The second argument of add_error() can be a string, or preferably an
instance of ValidationError. See Raising ValidationError for more
details. Note that add_error() automatically removes the field from
cleaned_data.

Связанные и несвязанные формы¶

Экземпляр 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. Если codeNone, то он вернет 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, а CharFields рассматривает пустые значения как пустую строку. Каждый тип поля знает, что такое «пустое» значение — например, для 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, а не стиль XHTML checked='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 a Field 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 Python float or raise a
    ValidationError.

  • The validate() method on a Field handles field-specific validation
    that is not suitable for a validator. It takes a value that has been
    coerced to a correct datatype and raises ValidationError 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 a Field 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 a Field subclass is responsible for running
    to_python(), validate(), and run_validators() in the correct
    order and propagating their errors. If, at any time, any of the methods
    raise ValidationError, 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
    in self.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
    in cleaned_data because the general field clean() method, above,
    has already cleaned the data once).

    For example, if you wanted to validate that the contents of a
    CharField called serialnumber was unique,
    clean_serialnumber() would be the right place to do this. You don’t
    need a specific field (it’s a CharField), 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 from cleaned_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 field A is supplied, field B must contain a valid email address”.
    This method can return a completely different dictionary if it wishes, which
    will be used as the cleaned_data.

    Since the field validation methods have been run by the time clean() is
    called, you also have access to the form’s errors 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
    the clean() method of a ModelForm 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 ValidationErrors (with a code name
and a params dictionary).

Raising multiple errors¶

If you detect multiple errors during a cleaning method and wish to signal all
of them to the form submitter, it is possible to pass a list of errors to the
ValidationError constructor.

As above, it is recommended to pass a list of ValidationError instances
with codes and params but a list of strings will also work:

# Good
raise ValidationError([
    ValidationError(_('Error 1'), code='error1'),
    ValidationError(_('Error 2'), code='error2'),
])

# Bad
raise ValidationError([
    _('Error 1'),
    _('Error 2'),
])

Using validation in practice¶

The previous sections explained how validation works in general for forms.
Since it can sometimes be easier to put things into place by seeing each
feature in use, here are a series of small examples that use each of the
previous features.

Using validators¶

Django’s form (and model) fields support use of utility functions and classes
known as validators. A validator is a callable object or function that takes a
value and returns nothing if the value is valid or raises a
ValidationError if not. These can be passed to a
field’s constructor, via the field’s validators argument, or defined on the
Field class itself with the default_validators
attribute.

Validators can be used to validate values inside the field, let’s have a look
at Django’s SlugField:

from django.core import validators
from django.forms import CharField


class SlugField(CharField):
    default_validators = [validators.validate_slug]

As you can see, SlugField is a CharField with a customized validator
that validates that submitted text obeys to some character rules. This can also
be done on field definition so:

is equivalent to:

slug = forms.CharField(validators=[validators.validate_slug])

Common cases such as validating against an email or a regular expression can be
handled using existing validator classes available in Django. For example,
validators.validate_slug is an instance of
a RegexValidator constructed with the first
argument being the pattern: ^[-a-zA-Z0-9_]+$. See the section on
writing validators to see a list of what is already
available and for an example of how to write a validator.

Form field default cleaning¶

Let’s first create a custom form field that validates its input is a string
containing comma-separated email addresses. The full class looks like this:

from django import forms
from django.core.validators import validate_email

class MultiEmailField(forms.Field):
    def to_python(self, value):
        """Normalize data to a list of strings."""
        # Return an empty list if no input was given.
        if not value:
            return []
        return value.split(',')

    def validate(self, value):
        """Check if value consists only of valid emails."""
        # Use the parent's handling of required fields, etc.
        super().validate(value)
        for email in value:
            validate_email(email)

Every form that uses this field will have these methods run before anything
else can be done with the field’s data. This is cleaning that is specific to
this type of field, regardless of how it is subsequently used.

Let’s create a ContactForm to demonstrate how you’d use this field:

class ContactForm(forms.Form):
    subject = forms.CharField(max_length=100)
    message = forms.CharField()
    sender = forms.EmailField()
    recipients = MultiEmailField()
    cc_myself = forms.BooleanField(required=False)

Use MultiEmailField like any other form field. When the is_valid()
method is called on the form, the MultiEmailField.clean() method will be
run as part of the cleaning process and it will, in turn, call the custom
to_python() and validate() methods.

Cleaning a specific field attribute¶

Continuing on from the previous example, suppose that in our ContactForm,
we want to make sure that the recipients field always contains the address
"fred@example.com". This is validation that is specific to our form, so we
don’t want to put it into the general MultiEmailField class. Instead, we
write a cleaning method that operates on the recipients field, like so:

from django import forms
from django.core.exceptions import ValidationError

class ContactForm(forms.Form):
    # Everything as before.
    ...

    def clean_recipients(self):
        data = self.cleaned_data['recipients']
        if "fred@example.com" not in data:
            raise ValidationError("You have forgotten about Fred!")

        # Always return a value to use as the new cleaned data, even if
        # this method didn't change it.
        return data

Cleaning and validating fields that depend on each other¶

Suppose we add another requirement to our contact form: if the cc_myself
field is True, the subject must contain the word "help". We are
performing validation on more than one field at a time, so the form’s
clean() method is a good spot to do this. Notice that we are
talking about the clean() method on the form here, whereas earlier we were
writing a clean() method on a field. It’s important to keep the field and
form difference clear when working out where to validate things. Fields are
single data points, forms are a collection of fields.

By the time the form’s clean() method is called, all the individual field
clean methods will have been run (the previous two sections), so
self.cleaned_data will be populated with any data that has survived so
far. So you also need to remember to allow for the fact that the fields you
are wanting to validate might not have survived the initial individual field
checks.

There are two ways to report any errors from this step. Probably the most
common method is to display the error at the top of the form. To create such
an error, you can raise a ValidationError from the clean() method. For
example:

from django import forms
from django.core.exceptions import ValidationError

class ContactForm(forms.Form):
    # Everything as before.
    ...

    def clean(self):
        cleaned_data = super().clean()
        cc_myself = cleaned_data.get("cc_myself")
        subject = cleaned_data.get("subject")

        if cc_myself and subject:
            # Only do something if both fields are valid so far.
            if "help" not in subject:
                raise ValidationError(
                    "Did not send for 'help' in the subject despite "
                    "CC'ing yourself."
                )

In this code, if the validation error is raised, the form will display an
error message at the top of the form (normally) describing the problem. Such
errors are non-field errors, which are displayed in the template with
{{ form.non_field_errors }}.

The call to super().clean() in the example code ensures that any validation
logic in parent classes is maintained. If your form inherits another that
doesn’t return a cleaned_data dictionary in its clean() method (doing
so is optional), then don’t assign cleaned_data to the result of the
super() call and use self.cleaned_data instead:

def clean(self):
    super().clean()
    cc_myself = self.cleaned_data.get("cc_myself")
    ...

The second approach for reporting validation errors might involve assigning the
error message to one of the fields. In this case, let’s assign an error message
to both the “subject” and “cc_myself” rows in the form display. Be careful when
doing this in practice, since it can lead to confusing form output. We’re
showing what is possible here and leaving it up to you and your designers to
work out what works effectively in your particular situation. Our new code
(replacing the previous sample) looks like this:

from django import forms

class ContactForm(forms.Form):
    # Everything as before.
    ...

    def clean(self):
        cleaned_data = super().clean()
        cc_myself = cleaned_data.get("cc_myself")
        subject = cleaned_data.get("subject")

        if cc_myself and subject and "help" not in subject:
            msg = "Must put 'help' in subject when cc'ing yourself."
            self.add_error('cc_myself', msg)
            self.add_error('subject', msg)

The second argument of add_error() can be a string, or preferably an
instance of ValidationError. See Raising ValidationError for more
details. Note that add_error() automatically removes the field from
cleaned_data.

Валидация форм и полей¶

Валидация формы происходит при очистке данных. Если вы хотите настроить этот процесс, есть различные места для внесения изменений, каждое из которых служит для разных целей. В процессе обработки формы выполняются три типа методов очистки. Обычно они выполняются, когда вы вызываете метод is_valid() на форме. Есть и другие вещи, которые также могут вызвать очистку и проверку (обращение к атрибуту errors или прямой вызов full_clean()), но обычно они не нужны.

В общем, любой метод очистки может поднять ValidationError, если есть проблема с данными, которые он обрабатывает, передавая соответствующую информацию конструктору ValidationError. See below для лучшей практики поднятия ValidationError. Если не поднимается ValidationError, метод должен вернуть очищенные (нормализованные) данные в виде объекта Python.

Большинство валидаций можно выполнить с помощью validators — помощников, которые можно использовать повторно. Валидаторы — это функции (или callables), которые принимают один аргумент и вызывают ValidationError при недопустимом вводе. Валидаторы запускаются после вызова методов to_python и validate поля.

Валидация формы разбита на несколько этапов, которые можно настроить или отменить:

  • Метод to_python() на Field является первым шагом в каждой валидации. Он преобразует значение к правильному типу данных и выдает сообщение ValidationError, если это невозможно. Этот метод принимает необработанное значение от виджета и возвращает преобразованное значение. Например, FloatField превратит данные в Python float или выдаст 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 field 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 documentation для получения дополнительной информации)

Эти методы выполняются в указанном выше порядке, по одному полю за раз. То есть, для каждого поля формы (в порядке их объявления в определении формы) выполняется метод Field.clean() (или его переопределение), затем clean_<fieldname>(). Наконец, когда эти два метода выполнены для каждого поля, выполняется метод Form.clean(), или его переопределение, независимо от того, вызвали ли предыдущие методы ошибки.

Примеры каждого из этих методов приведены ниже.

Как уже упоминалось, любой из этих методов может вызвать ошибку ValidationError. Для любого поля, если метод Field.clean() вызывает ValidationError, любой метод очистки, специфичный для данного поля, не вызывается. Однако методы очистки для всех оставшихся полей все равно выполняются.

Поднятие ValidationError

Чтобы сделать сообщения об ошибках гибкими и легко переопределяемыми, примите во внимание следующие рекомендации:

  • Предоставить описательную ошибку code конструктору:

    # Good
    ValidationError(_('Invalid value'), code='invalid')
    
    # Bad
    ValidationError(_('Invalid value'))
    
  • Не вставляйте переменные в сообщение; используйте заполнители и аргумент params конструктора:

    # Good
    ValidationError(
        _('Invalid value: %(value)s'),
        params={'value': '42'},
    )
    
    # Bad
    ValidationError(_('Invalid value: %s') % value)
    
  • Используйте ключи отображения вместо позиционного форматирования. Это позволяет располагать переменные в любом порядке или вообще их не использовать при переписывании сообщения:

    # Good
    ValidationError(
        _('Invalid value: %(value)s'),
        params={'value': '42'},
    )
    
    # Bad
    ValidationError(
        _('Invalid value: %s'),
        params=('42',),
    )
    
  • Оберните сообщение символом gettext, чтобы включить перевод:

    # Good
    ValidationError(_('Invalid value'))
    
    # Bad
    ValidationError('Invalid value')
    

Собираем все вместе:

raise ValidationError(
    _('Invalid value: %(value)s'),
    code='invalid',
    params={'value': '42'},
)

Следование этим рекомендациям особенно необходимо, если вы пишете многократно используемые формы, поля форм и поля моделей.

Хотя это и не рекомендуется, если вы находитесь в конце цепочки валидации (т.е. ваша форма clean() метод) и вы знаете, что вам никогда не понадобится переопределять сообщение об ошибке, вы можете выбрать менее многословный вариант:

ValidationError(_('Invalid value: %s') % value)

Методы Form.errors.as_data() и Form.errors.as_json() значительно выигрывают от полнофункциональных ValidationErrors (с code именем и params словарем).

Возникновение множества ошибок¶

Если вы обнаружили несколько ошибок во время работы метода очистки и хотите сигнализировать обо всех из них отправителю формы, можно передать список ошибок конструктору ValidationError.

Как и выше, рекомендуется передавать список экземпляров ValidationError с codes и params, но подойдет и список строк:

# Good
raise ValidationError([
    ValidationError(_('Error 1'), code='error1'),
    ValidationError(_('Error 2'), code='error2'),
])

# Bad
raise ValidationError([
    _('Error 1'),
    _('Error 2'),
])

Использование валидации на практике¶

В предыдущих разделах объяснялось, как работает валидация в целом для форм. Поскольку иногда бывает проще понять, как работает каждая функция, здесь приведена серия небольших примеров, в которых используется каждая из предыдущих функций.

Использование валидаторов¶

Поля формы (и модели) Django поддерживают использование полезных функций и классов, известных как валидаторы. Валидатор — это вызываемый объект или функция, которая принимает значение и не возвращает ничего, если значение действительно, или выдает ошибку ValidationError, если нет. Они могут быть переданы в конструктор поля через аргумент validators или определены в самом классе Field с помощью атрибута default_validators.

Валидаторы могут использоваться для проверки значений внутри поля, давайте посмотрим на Django’s SlugField:

from django.core import validators
from django.forms import CharField


class SlugField(CharField):
    default_validators = [validators.validate_slug]

Как вы можете видеть, SlugField — это CharField с настроенным валидатором, который проверяет, что отправленный текст соответствует некоторым правилам символов. Это также можно сделать при определении поля так:

эквивалентно:

slug = forms.CharField(validators=[validators.validate_slug])

Обычные случаи, такие как проверка по электронной почте или регулярному выражению, могут быть обработаны с помощью существующих классов валидаторов, доступных в Django. Например, validators.validate_slug — это экземпляр RegexValidator, построенный с первым аргументом в виде шаблона: ^[-a-zA-Z0-9_]+$. Смотрите раздел writing validators, чтобы увидеть список того, что уже доступно, и пример того, как написать валидатор.

Очистка полей формы по умолчанию¶

Давайте сначала создадим поле пользовательской формы, которое проверяет, что его входные данные — это строка, содержащая адреса электронной почты, разделенные запятыми. Полный класс выглядит следующим образом:

from django import forms
from django.core.validators import validate_email

class MultiEmailField(forms.Field):
    def to_python(self, value):
        """Normalize data to a list of strings."""
        # Return an empty list if no input was given.
        if not value:
            return []
        return value.split(',')

    def validate(self, value):
        """Check if value consists only of valid emails."""
        # Use the parent's handling of required fields, etc.
        super().validate(value)
        for email in value:
            validate_email(email)

В каждой форме, использующей это поле, эти методы будут выполняться до того, как с данными поля можно будет сделать что-либо еще. Это очистка, специфичная для данного типа поля, независимо от того, как оно будет использоваться в дальнейшем.

Давайте создадим ContactForm, чтобы продемонстрировать, как вы будете использовать это поле:

class ContactForm(forms.Form):
    subject = forms.CharField(max_length=100)
    message = forms.CharField()
    sender = forms.EmailField()
    recipients = MultiEmailField()
    cc_myself = forms.BooleanField(required=False)

Используйте MultiEmailField как любое другое поле формы. Когда на форме будет вызван метод is_valid(), в процессе очистки будет запущен метод MultiEmailField.clean(), который, в свою очередь, вызовет пользовательские методы to_python() и validate().

Очистка определенного атрибута поля¶

Продолжая предыдущий пример, предположим, что в нашем ContactForm мы хотим убедиться, что поле recipients всегда содержит адрес "fred@example.com". Это проверка, специфичная для нашей формы, поэтому мы не хотим помещать ее в общий класс MultiEmailField. Вместо этого мы напишем метод очистки, который работает с полем recipients, следующим образом:

from django import forms
from django.core.exceptions import ValidationError

class ContactForm(forms.Form):
    # Everything as before.
    ...

    def clean_recipients(self):
        data = self.cleaned_data['recipients']
        if "fred@example.com" not in data:
            raise ValidationError("You have forgotten about Fred!")

        # Always return a value to use as the new cleaned data, even if
        # this method didn't change it.
        return data

Очистка и проверка полей, которые зависят друг от друга¶

Предположим, мы добавим еще одно требование к нашей контактной форме: если поле cc_myself является True, то subject должно содержать слово "help". Мы выполняем проверку более чем одного поля одновременно, поэтому метод формы clean() является хорошим местом для этого. Обратите внимание, что здесь мы говорим о методе clean() на форме, тогда как ранее мы писали метод clean() на поле. Важно четко различать поля и формы, когда мы решаем, где проводить валидацию. Поля — это отдельные точки данных, а формы — это набор полей.

К моменту вызова метода clean() формы будут запущены все методы очистки отдельных полей (предыдущие два раздела), поэтому self.cleaned_data будет заполнен любыми данными, которые сохранились до сих пор. Поэтому вам также нужно помнить о том, что поля, которые вы хотите проверить, могут не выдержать первоначальной проверки отдельных полей.

Есть два способа сообщить о любых ошибках на этом этапе. Вероятно, самый распространенный способ — вывести ошибку в верхней части формы. Чтобы создать такую ошибку, вы можете поднять ValidationError из метода clean(). Например:

from django import forms
from django.core.exceptions import ValidationError

class ContactForm(forms.Form):
    # Everything as before.
    ...

    def clean(self):
        cleaned_data = super().clean()
        cc_myself = cleaned_data.get("cc_myself")
        subject = cleaned_data.get("subject")

        if cc_myself and subject:
            # Only do something if both fields are valid so far.
            if "help" not in subject:
                raise ValidationError(
                    "Did not send for 'help' in the subject despite "
                    "CC'ing yourself."
                )

В этом коде, если возникает ошибка валидации, форма выводит сообщение об ошибке в верхней части формы (обычно) с описанием проблемы. Такие ошибки являются не-полевыми ошибками, которые отображаются в шаблоне с помощью {{ form.non_field_errors }}.

Вызов super().clean() в коде примера гарантирует, что любая логика валидации в родительских классах будет сохранена. Если ваша форма наследует другую, которая не возвращает словарь cleaned_data в своем методе clean() (это необязательно), то не присваивайте cleaned_data результату вызова super() и используйте self.cleaned_data вместо этого:

def clean(self):
    super().clean()
    cc_myself = self.cleaned_data.get("cc_myself")
    ...

Второй подход для сообщения об ошибках валидации может включать присвоение сообщения об ошибке одному из полей. В данном случае давайте присвоим сообщение об ошибке обеим строкам «subject» и «cc_myself» в отображении формы. Будьте осторожны, делая это на практике, так как это может привести к запутанному выводу формы. Мы показываем, что здесь возможно, и предоставляем вам и вашим дизайнерам самим решать, что будет эффективно работать в вашей конкретной ситуации. Наш новый код (заменяющий предыдущий пример) выглядит следующим образом:

from django import forms

class ContactForm(forms.Form):
    # Everything as before.
    ...

    def clean(self):
        cleaned_data = super().clean()
        cc_myself = cleaned_data.get("cc_myself")
        subject = cleaned_data.get("subject")

        if cc_myself and subject and "help" not in subject:
            msg = "Must put 'help' in subject when cc'ing yourself."
            self.add_error('cc_myself', msg)
            self.add_error('subject', msg)

Вторым аргументом add_error() может быть строка или, предпочтительно, экземпляр ValidationError. Более подробную информацию смотрите в Поднятие ValidationError. Обратите внимание, что add_error() автоматически удаляет поле из cleaned_data.

In this tutorial, we will learn how to show custom validation exceptions on Django admin.

Understanding The Problem

Django admin site will raise exceptions when we try to save objects with non-acceptable values. Often we need to create our custom validators and raise custom exceptions depending on the needs of the project. Like if you want the phone number to be in a particular format or you want to make sure the first name starts with a capital letter.

There are a lot of ways to achieve this but if your goal is to ensure proper validation limited from the admin interface only, then this tutorial is for you.

Note: If creating validators that remain constant throughout the app is your goal, then you should check out this article — Creating Custom Model Validation In Django

Displaying Custom Validation Exception in Django Admin

We will use the popular blog project for this tutorial. The goal is to ensure that the title of the post is in the proper title case.

Displaying Custom Validation Exception in Django

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), состоящую из нескольких списков выбора и текстовых редакторов.

Admin Site - Book Add

Работа с формами может быть достаточно сложной! Разработчикам надо описать форму на HTML, проверить её валидность, а также, на стороне сервера, проверять введённые пользователем данные (а возможно и на стороне клиента), далее, в случае возникновения ошибок необходимо опять показать пользователю форму и, при этом, указать на то, что пошло не так, в случае же успеха проделать с данными необходимые операции и каким-то образом проинформировать об этом пользователя. Django, при работе с формами, берёт большую часть, описанной выше работы, на себя. Он предоставляет фреймворк, который позволяет вам определять форму и её поля программно, а затем использовать эти объекты и для генерации непосредственно кода HTML-формы, и для контроля за процессом валидации и других пользовательский взаимодействий с формой.

В данной части руководства мы покажем вам несколько способов создания и работы с формами и, в частности, как применение обобщённых классов работы с формой могут значительно уменьшить необходимый объем работы. Кроме того, мы расширим возможности нашего сайта LocalLibrary, путём добавления функциональности для библиотекарей, которая будет позволять им обновлять информацию — добавим страницы для создания, редактирования, удаления книг и авторов (воспроизведём и расширим стандартные возможности административной части сайта).

Формы HTML

Начнём мы с краткого обзора Форм HTML. Рассмотрим простую форму HTML, имеющую поле для ввода имени некоторой «команды» («team»), и, связанную с данным полем, текстовой меткой:

Simple name field example in HTML form

Форма описывается на языке 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, начиная с запроса страницы, содержащей форму (выделено зелёным цветом).

Updated form handling process doc.

В соответствии с данной диаграммой, главными моментами, которые берут на себя формы Django являются:

  1. Показ формы по умолчанию при первом запросе со стороны пользователя.
    • Форма может содержать пустые поля (например, если вы создаёте новую запись в базе данных), или они (поля) могут иметь начальные значения (например, если вы изменяете запись, или хотите заполнить её каким-либо начальным значением).
    • Форма в данный момент является несвязанной, потому что она не ассоциируется с какими-либо введёнными пользователем данными (хотя и может иметь начальные значения).
  2. Получение данных из формы (из HTML-формы) со стороны клиента и связывание их с формой (классом формы) на стороне сервера.
    • Связывание данных с формой означает, что данные, введённые пользователем, а также возможные ошибки, при переотрисовке в дальнейшем, будут относиться именно к данной форме, а не к какой-либо ещё.
  3. Очистка и валидация данных.
    • Очистка данных — это их проверка на наличие возможных значений, или вставок в поля ввода (то есть очистка — это удаление неправильных символов, которые потенциально могут использоваться для отправки вредоносного содержимого на сервер), с последующей конвертацией очищенных данных в подходящие типы данных Python.
    • Валидация проверяет, значения полей (например, правильность введённых дат, их диапазон и так далее)
  4. Если какие-либо данные являются неверными, то выполнение перерисовки формы, но на этот раз, с уже введёнными пользователем данными и сообщениями об ошибках, описывающих возникшие проблемы.
  5. Если все данные верны, то исполнение необходимых действий (например, сохранение данных, отправка писем, возврат результата поиска, загрузка файла и так далее)
  6. Когда все действия были успешно завершены, то перенаправление пользователя на другую страницу.

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) в классе BookInstancecan_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/, которая должна быть похожей на следующий скриншот.

Form Example: Create Author

Введите в поля значения и нажмите на кнопку 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.

User Profile Form

  • The message displays success message once messages.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.

User Profile Form Displaying Validation Errors

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.

Showing success message on successfully validating form and saving its contents into the database

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

51star1star1star1star1star

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 a Field 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 Python float or raise a
    ValidationError.

  • The validate() method on a Field handles field-specific validation
    that is not suitable for a validator. It takes a value that has been
    coerced to a correct datatype and raises ValidationError 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 a Field 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 a Field subclass is responsible for running
    to_python(), validate(), and run_validators() in the correct
    order and propagating their errors. If, at any time, any of the methods
    raise ValidationError, 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
    in self.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
    in cleaned_data because the general field clean() method, above,
    has already cleaned the data once).

    For example, if you wanted to validate that the contents of a
    CharField called serialnumber was unique,
    clean_serialnumber() would be the right place to do this. You don’t
    need a specific field (it’s a CharField), 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 from cleaned_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 field A is supplied, field B must contain a valid email address”.
    This method can return a completely different dictionary if it wishes, which
    will be used as the cleaned_data.

    Since the field validation methods have been run by the time clean() is
    called, you also have access to the form’s errors 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
    the clean() method of a ModelForm 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 ValidationErrors (with a code name
and a params dictionary).

Raising multiple errors¶

If you detect multiple errors during a cleaning method and wish to signal all
of them to the form submitter, it is possible to pass a list of errors to the
ValidationError constructor.

As above, it is recommended to pass a list of ValidationError instances
with codes and params but a list of strings will also work:

# Good
raise ValidationError(
    [
        ValidationError(_("Error 1"), code="error1"),
        ValidationError(_("Error 2"), code="error2"),
    ]
)

# Bad
raise ValidationError(
    [
        _("Error 1"),
        _("Error 2"),
    ]
)

Using validation in practice¶

The previous sections explained how validation works in general for forms.
Since it can sometimes be easier to put things into place by seeing each
feature in use, here are a series of small examples that use each of the
previous features.

Using validators¶

Django’s form (and model) fields support use of utility functions and classes
known as validators. A validator is a callable object or function that takes a
value and returns nothing if the value is valid or raises a
ValidationError if not. These can be passed to a
field’s constructor, via the field’s validators argument, or defined on the
Field class itself with the default_validators
attribute.

Validators can be used to validate values inside the field, let’s have a look
at Django’s SlugField:

from django.core import validators
from django.forms import CharField


class SlugField(CharField):
    default_validators = [validators.validate_slug]

As you can see, SlugField is a CharField with a customized validator
that validates that submitted text obeys to some character rules. This can also
be done on field definition so:

is equivalent to:

slug = forms.CharField(validators=[validators.validate_slug])

Common cases such as validating against an email or a regular expression can be
handled using existing validator classes available in Django. For example,
validators.validate_slug is an instance of
a RegexValidator constructed with the first
argument being the pattern: ^[-a-zA-Z0-9_]+$. See the section on
writing validators to see a list of what is already
available and for an example of how to write a validator.

Form field default cleaning¶

Let’s first create a custom form field that validates its input is a string
containing comma-separated email addresses. The full class looks like this:

from django import forms
from django.core.validators import validate_email


class MultiEmailField(forms.Field):
    def to_python(self, value):
        """Normalize data to a list of strings."""
        # Return an empty list if no input was given.
        if not value:
            return []
        return value.split(",")

    def validate(self, value):
        """Check if value consists only of valid emails."""
        # Use the parent's handling of required fields, etc.
        super().validate(value)
        for email in value:
            validate_email(email)

Every form that uses this field will have these methods run before anything
else can be done with the field’s data. This is cleaning that is specific to
this type of field, regardless of how it is subsequently used.

Let’s create a ContactForm to demonstrate how you’d use this field:

class ContactForm(forms.Form):
    subject = forms.CharField(max_length=100)
    message = forms.CharField()
    sender = forms.EmailField()
    recipients = MultiEmailField()
    cc_myself = forms.BooleanField(required=False)

Use MultiEmailField like any other form field. When the is_valid()
method is called on the form, the MultiEmailField.clean() method will be
run as part of the cleaning process and it will, in turn, call the custom
to_python() and validate() methods.

Cleaning a specific field attribute¶

Continuing on from the previous example, suppose that in our ContactForm,
we want to make sure that the recipients field always contains the address
"fred@example.com". This is validation that is specific to our form, so we
don’t want to put it into the general MultiEmailField class. Instead, we
write a cleaning method that operates on the recipients field, like so:

from django import forms
from django.core.exceptions import ValidationError


class ContactForm(forms.Form):
    # Everything as before.
    ...

    def clean_recipients(self):
        data = self.cleaned_data["recipients"]
        if "fred@example.com" not in data:
            raise ValidationError("You have forgotten about Fred!")

        # Always return a value to use as the new cleaned data, even if
        # this method didn't change it.
        return data

Cleaning and validating fields that depend on each other¶

Suppose we add another requirement to our contact form: if the cc_myself
field is True, the subject must contain the word "help". We are
performing validation on more than one field at a time, so the form’s
clean() method is a good spot to do this. Notice that we are
talking about the clean() method on the form here, whereas earlier we were
writing a clean() method on a field. It’s important to keep the field and
form difference clear when working out where to validate things. Fields are
single data points, forms are a collection of fields.

By the time the form’s clean() method is called, all the individual field
clean methods will have been run (the previous two sections), so
self.cleaned_data will be populated with any data that has survived so
far. So you also need to remember to allow for the fact that the fields you
are wanting to validate might not have survived the initial individual field
checks.

There are two ways to report any errors from this step. Probably the most
common method is to display the error at the top of the form. To create such
an error, you can raise a ValidationError from the clean() method. For
example:

from django import forms
from django.core.exceptions import ValidationError


class ContactForm(forms.Form):
    # Everything as before.
    ...

    def clean(self):
        cleaned_data = super().clean()
        cc_myself = cleaned_data.get("cc_myself")
        subject = cleaned_data.get("subject")

        if cc_myself and subject:
            # Only do something if both fields are valid so far.
            if "help" not in subject:
                raise ValidationError(
                    "Did not send for 'help' in the subject despite " "CC'ing yourself."
                )

In this code, if the validation error is raised, the form will display an
error message at the top of the form (normally) describing the problem. Such
errors are non-field errors, which are displayed in the template with
{{ form.non_field_errors }}.

The call to super().clean() in the example code ensures that any validation
logic in parent classes is maintained. If your form inherits another that
doesn’t return a cleaned_data dictionary in its clean() method (doing
so is optional), then don’t assign cleaned_data to the result of the
super() call and use self.cleaned_data instead:

def clean(self):
    super().clean()
    cc_myself = self.cleaned_data.get("cc_myself")
    ...

The second approach for reporting validation errors might involve assigning the
error message to one of the fields. In this case, let’s assign an error message
to both the “subject” and “cc_myself” rows in the form display. Be careful when
doing this in practice, since it can lead to confusing form output. We’re
showing what is possible here and leaving it up to you and your designers to
work out what works effectively in your particular situation. Our new code
(replacing the previous sample) looks like this:

from django import forms


class ContactForm(forms.Form):
    # Everything as before.
    ...

    def clean(self):
        cleaned_data = super().clean()
        cc_myself = cleaned_data.get("cc_myself")
        subject = cleaned_data.get("subject")

        if cc_myself and subject and "help" not in subject:
            msg = "Must put 'help' in subject when cc'ing yourself."
            self.add_error("cc_myself", msg)
            self.add_error("subject", msg)

The second argument of add_error() can be a string, or preferably an
instance of ValidationError. See Raising ValidationError for more
details. Note that add_error() automatically removes the field from
cleaned_data.

Проверка формы и поля

Проверка формы происходит при очистке данных. Если вы хотите настроить этот процесс, есть несколько мест для внесения изменений, каждое из которых служит своей цели. Во время обработки формы запускаются три типа методов очистки. Обычно они выполняются, когда вы вызываете метод 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,включая текст ввода,различные

Понравилась статья? Поделить с друзьями:

Интересное по теме:

  • Отображение ошибок php xampp
  • Отношения между мужчиной и женщиной психология ошибки этапы
  • Отображение ошибок php ini
  • Отображение ошибок codeigniter
  • Отображение всех ошибок php htaccess

  • Добавить комментарий

    ;-) :| :x :twisted: :smile: :shock: :sad: :roll: :razz: :oops: :o :mrgreen: :lol: :idea: :grin: :evil: :cry: :cool: :arrow: :???: :?: :!: