Работа с формами

About this document

Этот документ знакомит со способами работы с формами. Для более детальной информации по API форм обращайтесь к API форм, Поля формы и Проверка форм и полей формы.

django.forms – библиотека для работы с формами.

Несмотря на наличие возможности обработки форм через обычный класс Django HttpRequest, использование специализированной библиотеки предоставляет решение для общих задач, возникающих при работе с формами, например:

  1. Автоматически генерировать HTML код формы с использованием необходимых виджетов.

  2. Проверять переданную информацию с помощью набора правил валидации.

  3. Заново отображать форму при обнаружении ошибок.

  4. Преобразовывать переданную информацию в соответствующие типы данных языка Python.

Введение

Библиотека работает с нижеперечисленными сущностями:

Widget

Класс, который соответствует определённому HTML-коду т.е. <input type="text"> или <textarea>. Он реализует генерацию HTML-кода для виджета.

Field

Класс, который отвечает за проведение валидации, т.е. EmailField обеспечивает получение правильного адреса электронной почты.

Form

Коллекция полей, которые знают как проверять введённую информацию и отображать себя в виде HTML кода.

Form Assets (the Media class)

Ресурсы CSS и JavaScript, которые необходимы для правильного отображения формы.

Эта библиотека отделена от остальных компонентов Django, таких как слой взаимодействия с базой данных, представления и шаблоны. Оно зависит только от настроек Django, ряда вспомогательных функций из django.utils и механизма интернационализации (необязательная зависимость).

Объекты форм

Объект формы содержит в себе последовательность полей формы и коллекцию правил валидации, которые должны быть пройдены для того, чтобы форма приняла переданную информацию. Классы форм создаются как подклассы django.forms.Form и используют декларативный стиль, аналогичный используемому в моделях Django.

Например, рассмотрим форму, созданную для реализации функционала “свяжитесь со мной” на личном веб сайте:

from django import forms

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

Форма составлена из объектов Field. В нашем случае, форма содержит четыре поля: subject, message, sender и cc_myself. CharField, EmailField и BooleanField – это просто три стандартных типа поля; полный список полей может быть найден в Поля формы.

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

Использование формы в представлении

Стандартный шаблон для работы с формой в представлении выглядит следующим образом:

from django.shortcuts import render
from django.http import HttpResponseRedirect

def contact(request):
    if request.method == 'POST': # If the form has been submitted...
        form = ContactForm(request.POST) # A form bound to the POST data
        if form.is_valid(): # All validation rules pass
            # Process the data in form.cleaned_data
            # ...
            return HttpResponseRedirect('/thanks/') # Redirect after POST
    else:
        form = ContactForm() # An unbound form

    return render(request, 'contact.html', {
        'form': form,
    })

В этом коде заложено три сценария:

Форма отправлена?

Данные

Что происходит

Не отправлена

Ещё нет

Шаблон получает пустой экземпляр ContactForm.

Отправлена

Неверные данные

Шаблон получает заполненный экземпляр ContactForm.

Отправлена

Правильные данные

Данные обрабатываются. Перенаправление на страницу с “благодарностями”.

Важно понимать разницу между Заполненные и незаполненные формы:

  • Незаполненная форма не содержит данных, привязанных к её полям. При отображении формы пользователь увидит её пустой или содержащей значения по умолчанию.

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

Обработка закачки файла через форму

Чтобы узнать как обрабатывать загрузку файлов через форму, обратитесь к Привязка загруженных файлов к форме.

Обработка данных формы

Если is_valid() возвратила True, то проверенные данные будут располагаться в словаре form.cleaned_data. Все значения этого словаря приведены к соответствующему типу данных.

Примечание

Также вы можете напрямую обратиться к непроверенным данным через request.POST, но лучше использовать проверенные данные.

В приведённом ранее примере, cc_myself будет булевым значением. Аналогично, такие поля как IntegerField и FloatField преобразовывают свои значения в типы Python int и float соответственно.

Поля “только для чтения” не доступны через form.cleaned_data (и установка значения в собственном методе clean() не даст никакого эффекта). Такие поля отображаются в виде текста, а не в виде элемента ввода и, следовательно, не отправляются на сервер.

Продолжая работать над предыдущим примером, покажем, как могут быть обработаны данные из формы:

if form.is_valid():
    subject = form.cleaned_data['subject']
    message = form.cleaned_data['message']
    sender = form.cleaned_data['sender']
    cc_myself = form.cleaned_data['cc_myself']

    recipients = ['info@example.com']
    if cc_myself:
        recipients.append(sender)

    from django.core.mail import send_mail
    send_mail(subject, message, sender, recipients)
    return HttpResponseRedirect('/thanks/') # Redirect after POST

Совет

Подробную информацию про отправку электронной почты с помощью Django можно найти в Sending email.

Отображение формы с помощью шаблона

Формы разработаны для работы с шаблонным языком Django. В приведённом ранее примере мы передавали экземпляр ContactForm в шаблон, используя контекстную переменную form. Покажем простой пример шаблона:

<form action="/contact/" method="post">{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="Submit" />
</form>

Форма просто выводит свои поля. Указание тега <form> и кнопки для отправки формы – это ваша задача.

Если ваша форма содержит загружаемые файлы, удостоверьтесь, что элемент form имеет атрибут enctype="multipart/form-data". Если вам потребуется создать общий шаблон, не зависящий от наличия функционала загрузки файлов, вы можете использовать атрибут формы is_multipart():

<form action="/contact/" method="post"
    {% if form.is_multipart %}enctype="multipart/form-data"{% endif %}>

Forms and Cross Site Request Forgery protection

Django поставляется с защитой против Cross Site Request Forgeries. Для отправки формы через POST при включенной защите от CSRF вы должны использовать шаблонный тег csrf_token, как показано в предыдущем примере. Тем не менее, раз CSRF-защита не обязательна для применения в шаблонах при оформлении формы, этот тег опущен в последующих примерах.

Тег form.as_p выводит поля формы в виде параграфов (т.е. <p/>) вместе с соответствующими метками. Ниже представлены пример с результатом использования нашего шаблона:

<form action="/contact/" method="post">
<p><label for="id_subject">Subject:</label>
    <input id="id_subject" type="text" name="subject" maxlength="100" /></p>
<p><label for="id_message">Message:</label>
    <input type="text" name="message" id="id_message" /></p>
<p><label for="id_sender">Sender:</label>
    <input type="email" name="sender" id="id_sender" /></p>
<p><label for="id_cc_myself">Cc myself:</label>
    <input type="checkbox" name="cc_myself" id="id_cc_myself" /></p>
<input type="submit" value="Submit" />
</form>

Следует отметить, что каждое поле формы обладает атрибутом с идентификатором id_<field-name>, с помощью которого обеспечивается связь с тегом метки. Это позволяет формам быть дружественными к вспомогательным технологиям, например, это поможет работе ПО для слепых. Также вы можете настроить способ генерации меток и идентификаторов.

Вы можете использовать form.as_table для вывода полей формы в виде таблицы (потребуется прописать в шаблоне теги <table>) и form.as_ul для вывода полей формы в виде элементов списка.

Настройка шаблона формы

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

<form action="/contact/" method="post">
    {{ form.non_field_errors }}
    <div class="fieldWrapper">
        {{ form.subject.errors }}
        <label for="id_subject">Email subject:</label>
        {{ form.subject }}
    </div>
    <div class="fieldWrapper">
        {{ form.message.errors }}
        <label for="id_message">Your message:</label>
        {{ form.message }}
    </div>
    <div class="fieldWrapper">
        {{ form.sender.errors }}
        <label for="id_sender">Your email address:</label>
        {{ form.sender }}
    </div>
    <div class="fieldWrapper">
        {{ form.cc_myself.errors }}
        <label for="id_cc_myself">CC yourself?</label>
        {{ form.cc_myself }}
    </div>
    <p><input type="submit" value="Send message" /></p>
</form>

Каждое именованное поле формы может быть выведено в шаблоне с помощью тега {{ form.name_of_field }}, который создаст HTML-код, необходимый для отображения виджета. Использование тега {{ form.name_of_field.errors }} отобразит список ошибок поля формы в виде ненумерованного списка. Результат может быть таким:

<ul class="errorlist">
    <li>Sender is required.</li>
</ul>

Списку назначен CSS-класс errorlist, что позволяет вам настроить параметры его отображения. Если потребуется более тонкая настройка отображения ошибок, вы можете это организовать с помощью цикла по ним:

{% if form.subject.errors %}
    <ol>
    {% for error in form.subject.errors %}
        <li><strong>{{ error|escape }}</strong></li>
    {% endfor %}
    </ol>
{% endif %}

Цикл по полям формы

Если вы используете однотипный HTML для каждого поля формы, вы можете избежать дублирования кода, используя тег {% for %} для прохода по полям формы:

<form action="/contact/" method="post">
    {% for field in form %}
        <div class="fieldWrapper">
            {{ field.errors }}
            {{ field.label_tag }} {{ field }}
        </div>
    {% endfor %}
    <p><input type="submit" value="Send message" /></p>
</form>

Внутри цикла тег {{ field }} представляет собой экземпляр класса BoundField. Класс BoundField также имеет приведённые далее атрибуты, которые могут быть полезны в ваших шаблонах:

{{ field.label }}

Метка поля, т.е. Email address.

{{ field.label_tag }}

Метка поля обёрнута в соответствующий HTML-тег <label>.

Changed in Django 1.6:

This includes the form’s label_suffix. For example, the default label_suffix is a colon:

<label for="id_email">Email address:</label>
{{ field.id_for_label }}

ID, которое будет использоваться для этого поля (id_email в примере выше). Вы можете использовать его вместо label_tag, если самостоятельно генерируете <label> для поля. Так полезно при генерации JavaScript, если вы не хотите “хардкодить” ID.

{{ field.value }}

Значение поля, т.е. someone@example.com.

{{ field.html_name }}

Имя поля, которое будет использовано в HTML-поле. Здесь учитывается префикс формы, если он был установлен.

{{ field.help_text }}

Любой вспомогательный текст, который привязан к полю.

{{ field.errors }}

Вывод <ul class="errorlist">, содержащий все ошибки валидации, относящиеся к полю. Вы можете настраивать представление списка ошибок с помощью цикла {% for error in field.errors %}. В этом случае, каждый объект в цикле является простой строкой, содержащей сообщение об ошибке.

{{ field.is_hidden }}

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

{% if field.is_hidden %}
   {# Do something special #}
{% endif %}
{{ field.field }}

Экземпляр Field из класса формы, который обёрнут с помощью BoundField. Он предоставляет доступ к атрибутам Field, т.е. {{ char_field.field.max_length }}.

Цикл по скрытым и отображаемым полям

Если вы вручную размещаете форму в шаблоне, то у вас появляется возможность трактовать поля вида <input type="hidden"> по своему. Например, так как скрытые поля не отображаются на форме, размещение сообщений об ошибке для поля “перейти далее” может смутить пользователей. Такие ошибки следует обрабатывать другим способом.

Django предоставляет два метода класса формы, которые позволяют организовать раздельные циклы по скрытым и отображаемым полям: hidden_fields() и visible_fields(). Покажем как изменится наш пример, если воспользоваться этими методами:

<form action="/contact/" method="post">
    {# Include the hidden fields #}
    {% for hidden in form.hidden_fields %}
    {{ hidden }}
    {% endfor %}
    {# Include the visible fields #}
    {% for field in form.visible_fields %}
        <div class="fieldWrapper">
            {{ field.errors }}
            {{ field.label_tag }} {{ field }}
        </div>
    {% endfor %}
    <p><input type="submit" value="Send message" /></p>
</form>

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

Повторное использование шаблонов форм

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

<form action="/contact/" method="post">
    {% include "form_snippet.html" %}
    <p><input type="submit" value="Send message" /></p>
</form>

# In form_snippet.html:

{% for field in form %}
    <div class="fieldWrapper">
        {{ field.errors }}
        {{ field.label_tag }} {{ field }}
    </div>
{% endfor %}

Если объект формы, переданный в шаблон, имеет другое имя в контексте, вы можете создать для него псевдоним, используя аргумент with тега include:

<form action="/comments/add/" method="post">
    {% include "form_snippet.html" with form=comment_form %}
    <p><input type="submit" value="Submit comment" /></p>
</form>

Если вам придётся делать такое часто, то можно создать собственный включающий тег.