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

О разделе

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

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

Django предоставляет широкий набор инструментов для этого.

HTML формы

Форма в HTML – это набор элементов в <form>...</form>, которые позволяют пользователю вводить текст, выбирать опции, изменять объекты и конторолы страницы, и так далее, а потом отправлять эту информацию на сервер.

Некоторые элементы формы - текстовые поля ввода и чекбоксы - достаточно простые и встроены в HTML. Некоторые – довольно сложные, состоят из диалогов выбора даты, слайдеров и других контролов, который обычно используют JavaScript и CSS.

Кроме <input> элементов форма должна содержать еще две вещи:

  • куда: URL, на который будут отправлены данные

  • как: HTTP метод, который должна использовать форма для отправки данных

Например, форма авторизации Django содержит несколько <input> элементов: один с type="text" для логина пользователя, один с type="password" для пароля, и один с type="submit" для кнопки “Log in”. Она также содержит несколько скрытых текстовых полей, которые Django использует для определения что делать после авторизации.

Форма также говорит браузеру, что данные должны оправляться на URL, указанный в атрибуте action тега <form> - /admin/ - и для отправки необходимо использовать HTTP метод, указанный атрибуте method - post.

При нажатии на элемент <input type="submit" value="Log in"> данные будут отправлены на /admin/.

GET или POST

GET и POST – единственные HTTP методы, которые используются для форм.

Форма авторизации в Django использует POST метод. При отправке формы браузер собирает все данные формы, кодирует для отправки, отправляет на сервер и получает ответ.

При GET, в отличии от POST, данные собираются в строку и передаются в URL. URL содержит адрес, куда отправлять данные, и данные для отправки. Пример работы можно посмотреть на странице поиска по документации Django. При оправке формы поиска, вы будете перенаправлены на URL https://docs.djangoproject.com/search/?q=forms&release=1.

GET и POST обычно используются для разных действий.

Любой запрос, который может изменить состояние системы - например, который изменяет данные в базе данных - должен использовать POST. GET должен использоваться для запросов, которые не влияют на состояние системы.

Не следует использовать GET запросы для формы с паролем, т.к. пароль появится в URL, а следовательно в истории браузера и журналах сервера. Также он не подходит для отправки большого количества данных или бинарных данных, например, изображения. Web-приложение, которое использует GET запросы в админке, подвержено атакам: не сложно подделать форму для запроса на сервер и получить важные данные о системе. Применение POST запросов, объединённых с другими видами защиты подобных CSRF, предоставляет больше контроля за доступом к данным.

GET удобен для таких вещей, как форма поиска, т.к. URL, который представляет GET запрос, можно легко сохранить в избранное или отправить по почте.

Роль Django в формах

Работа с формами – довольно непростая задача. Возьмем админку Django. Необходимо подготовить для отображения в форме большое количество данных разного типа, отобразить форму в HTML, создать удобный интерфейс для редактирования, получить данные на сервере, проверить и преобразовать в нужный формат, и в конце сохранить или передать для дальнейшей обработки.

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

Django позволяет:

  • подготовить данные для отображения в форме

  • создать HTML формы для данных

  • получить и обработать отправленные формой данные

Вы можете написать код, который все это будет делать, но Django может выполнить большую часть самостоятельно.

Формы в Django

Мы уже описали HTML формы, но HTML <form> только часть необходимого механизма.

В контексте Web приложения ‘form’ может означать HTML <form>, или класс Django Form, который создает HTML формы, или данне, которые передаются при отправке формы, или ко всему механизму вместе.

Класс Django Form

Сердце всего механизма – класс Form. Как и модель в Django, которая описывает структуру объекта, его поведение и представление, Form описывает форму, как она работает и показывается пользователю.

Как поля модели представляют поля в базе данных, поля формы представляют HTML <input> элементы. (ModelForm отображает поля модели в виде HTML <input> элементов, используя Form. Используется в админке Django.)

Поля формы сами являются классами. Они управляют данными формы и выполняют их проверку при отправке формы. Например, DateField и FileField работают с разными данными и выполняют разные действия с ними.

Поле формы представлено в браузере HTML “виджетом” - компонент интерфейса. Каждый тип поля представлен по умолчанию определенным классом Widget, который можно переопределить при необходимости.

Создание, обработка и рендеринг форм

При рендеринге объекта в Django мы обычно:

  1. получаем его в представлении (например, загружаем из базы данных)

  2. передаем в контекст шаблона

  3. представляем в виде HTML в шаблоне, используя переменные контекста

Рендеринг форм происходит аналогичным образом с некоторыми отличиями.

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

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

При создании формы мы может оставить её пустой, или добавить начальные данные, например:

  • сохраненного ранее объекта модели (для редактирования их в админке)

  • полученные из других источников

  • получение из предыдущей отправки формы

Последний случай самый интересный, т.к. позволяет пользователям не только читать сайт, но и отправлять ему информацию.

Создание формы

Что необходимо сделать

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

<form action="/your-name/" method="post">
    <label for="your_name">Your name: </label>
    <input id="your_name" type="text" name="your_name" value="{{ current_name }}">
    <input type="submit" value="OK">
</form>

Этот код указывает браузеру отправить данные на URL /your-name/, используя метод POST. Браузер покажет на странице текстовое поле ввода с меткой “Your name:”, и кнопку “OK”. Если в контексте шаблона указать переменную current_name, её значение будет добавлено в текстовое поле your_name.

Вам необходимо создать представление, которое рендерит шаблон с HTML формой и передает в контекст шаблона current_name.

При отправке формы будет отправлен POST запрос с данными из формы.

Также необходимо добавить представление, которое обрабатывает запрос на /your-name/ URL.

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

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

На много легче позволить Django сделать эту работу за нас.

Создание форм в Django

Класс Form

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

forms.py
from django import forms

class NameForm(forms.Form):
    your_name = forms.CharField(label='Your name', max_length=100)

Этот код создает класс Form с одним полем (your_name). Мы добавили читабельное название поля, которое будет добавлено в тег <label> при рендеринге поля (на самом деле мы могли не добавлять label т.к. аналогичное название Django сгенерировал бы самостоятельно).

Максимальное количество символом в значении мы указали с помощью параметра max_length. Он используется для двух вещей. Будет добавлен атрибут maxlength="100" в HTML тег <input> (теперь браузер не позволит пользователю ввести больше символов, чем мы указали). Также Django выполнит проверку введенного значения, когда получит запрос с браузера с введенными данными.

Экземпляр Form содержит метод is_valid(), который выполняет проверку всех полей формы. Если все данные правильные, это метод:

  • вернет True

  • добавит данные формы в атрибут cleaned_data.

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

<label for="your_name">Your name: </label>
<input id="your_name" type="text" name="your_name" maxlength="100">

Обратите внимание, она не содержит тег <form>, или кнопку отправки. Вам необходимо самостоятельно их добавить в шаблоне.

Представление

Данные формы отправляются обратно в Django и обрабатываются представлением, обычно тем же, которое и создает форму. Это позволяет повторно использовать часть кода.

Для обработки данных формой нам необходимо создать ее в представлении для URL, на который браузер отправляет данные формы:

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

from .forms import NameForm

def get_name(request):
    # if this is a POST request we need to process the form data
    if request.method == 'POST':
        # create a form instance and populate it with data from the request:
        form = NameForm(request.POST)
        # check whether it's valid:
        if form.is_valid():
            # process the data in form.cleaned_data as required
            # ...
            # redirect to a new URL:
            return HttpResponseRedirect('/thanks/')

    # if a GET (or any other method) we'll create a blank form
    else:
        form = NameForm()

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

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

Если форма отправлена через POST запрос, представление создаст форму с данными из запроса: form = NameForm(request.POST) Это называется “привязать данные к форме” (теперь это связанная с данными форма).

Мы вызываем метод is_valid() формы. Если получили не True, снова рендерим шаблон, передав нашу форму. Теперь форма не пустая (не связана с данными) и HTML форма будет содержать ранее отправленные данные, их можно повторно отредактировать и отправить снова.

Если is_valid() вернет True, мы можем найти проверенные данные в атрибуте cleaned_data. Мы можем сохранить эти данные в базе данных, или выполнить какие-то другие действия над ними, перед тем, как сделать редирект на другую страницу.

Шаблон

Наш name.html шаблон может быть довольно простым:

<form action="/your-name/" method="post">
    {% csrf_token %}
    {{ form }}
    <input type="submit" value="Submit" />
</form>

Все поля формы и их атрибуты будут добавлены в HTML из {{ form }} при рендеринге шаблона.

Формы и CSRF защита

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

HTML5 поля и проверка в браузере

Если ваша форма содержит URLField, EmailField или одно из числовых полей, Django будет использовать url, email и number поля ввода HTML5. По умолчанию браузеры могут выполнять собственную проверку для этих полей, некоторые проверки могут быть более строгие чем в Django. Если вы хотите отключить это, добавьте novalidate атрибут в тег form, или используйте другой виджет для этих полей, например TextInput.

Теперь у нас есть полностью рабочая форма, созданная классом Form, обрабатываемая представлением, и представленная на странице как HTML <form>.

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

Дополнительные возможности Django Form

Все формы являются дочерними классами django.forms.Form, включая ModelForm, которые вы можете увидеть в админке Django.

Модели и формы

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

Связанные и не связанные с данными экземпляры формы

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

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

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

Атрибут формы is_bound позволяет узнать связана форма с данными или нет.

Поля формы

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

forms.py
from django import forms

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

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

Виджеты

Каждое поле формы содержит соответствующий класс Widget, который отвечает за создание HTML кода, представляющего поле, например <input type="text">.

В большинстве случаев поле уже содержит подходящий виджет. Например, по умолчанию поле CharField представлено виджетом TextInput, который создает тег <input type="text"> в HTML. Если вам вместо него необходим <textarea>, вы можете указать подходящий виджет при определении поля формы, как мы сделали для поля message.

Данные поля

Когда в форму добавлены данные, и она проверена методом is_valid()is_valid() вернул True), проверенные данные будут добавлены в словарь form.cleaned_data. Эти данные будет преобразованы в подходящий тип Python.

Примечание

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

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

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

views.py
from django.core.mail import send_mail

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)

    send_mail(subject, message, sender, recipients)
    return HttpResponseRedirect('/thanks/')

Совет

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

Некоторые поля требуют дополнительной обработки. Например, загруженные файлы необходимо обрабатывать по другому (их можно получить из request.FILES, а не request.POST). Подробности смотрите в Привязка загруженных файлов к форме.

Работа с шаблонами формы

Чтобы получить доступ к форме в шаблоне, просто передайте экземпляр в контекст шаблона. Если ваша форма добавлена в контекст как form, {{ form }} создаст необходимые теги <label> и <input>.

Настройки рендеринга формы

Что еще необходимо при рендеринге формы

Обратите внимание, форма не добавляет тег <form> и submit кнопку. Вы должны добавить их самостоятельно.

Вы можете использовать следующие варианты рендеринга <label>/<input>:

  • {{ form.as_table }} выведет их в таблице, в ячейках тега <tr>

  • {{ form.as_p }} обернет их в тег <p>

  • {{ form.as_ul }} выведет в теге <li>

Обратите внимание, тег <table> или <ul> вы должны добавить сами.

Вот результат {{ form.as_p }} для нашей формы ContactForm:

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

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

Смотрите Выдача формы в виде HTML.

Рендеринг полей вручную

Мы можем не использовать полный рендеринг формы и отрендерить каждое поле отдельно (например, чтобы поменять порядок полей). Каждое поле формы можно получить через атрибут формы {{ form.name_of_field }}. Например:

{{ form.non_field_errors }}
<div class="fieldWrapper">
    {{ form.subject.errors }}
    <label for="{{ form.subject.id_for_label }}">Email subject:</label>
    {{ form.subject }}
</div>
<div class="fieldWrapper">
    {{ form.message.errors }}
    <label for="{{ form.message.id_for_label }}">Your message:</label>
    {{ form.message }}
</div>
<div class="fieldWrapper">
    {{ form.sender.errors }}
    <label for="{{ form.sender.id_for_label }}">Your email address:</label>
    {{ form.sender }}
</div>
<div class="fieldWrapper">
    {{ form.cc_myself.errors }}
    <label for="{{ form.cc_myself.id_for_label }}">CC yourself?</label>
    {{ form.cc_myself }}
</div>

Элемент <label> также может быть создан с помощью метода label_tag(). Например:

<div class="fieldWrapper">
    {{ form.subject.errors }}
    {{ form.subject.label_tag }}
    {{ form.subject }}
</div>

Рендеринг ошибок проверки

За гибкость мы платим количеством кода. До этого мы не заботились о выводе ошибок проверки, т.к. Django делал это за нас. В этом примере нам пришлось явно выводить ошибки проверки для каждого поля и для всей формы. Обратите внимание на {{ form.non_field_errors }} в начале в формы и вывод ошибок для каждого поля.

Список ошибок можно вывести используя {{ 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 %}

Ошибки, не относящиеся к полям, (и/или ошибки скрытых полей, которые отображаются наверху формы при использовании методов подобных form.as_p()) будут отображаться с дополнительным классом nonfield, что поможет их отделить от ошибок полей формы. Например, {{ form.non_field_errors }} может выглядеть так:

<ul class="errorlist nonfield">
    <li>Generic validation error</li>
</ul>
Изменено в Django 1.8:

Был добавлен класс nonfield, который был описан выше.

Подробности о выводе ошибок, стилях и атрибутах смотрите в API форм.

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

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

{% for field in form %}
    <div class="fieldWrapper">
        {{ field.errors }}
        {{ field.label_tag }} {{ field }}
        {% if field.help_text %}
        <p class="help">{{ field.help_text|safe }}</p>
        {% endif %}
    </div>
{% endfor %}

Полезные атрибуты {{ field }}:

{{ field.label }}

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

{{ field.label_tag }}

Метка поля, обёрнутая в соответствующий HTML тег <label>. Также включает атрибут формы label_suffix. Например, по умолчания label_suffix содержит двоеточие:

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

См.также

Полный список методов и атрибутов смотрите в описании BoundField.

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

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

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

{# 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 %}

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

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

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

# In your form template:
{% include "form_snippet.html" %}

# In form_snippet.html:
{% for field in form %}
    <div class="fieldWrapper">
        {{ field.errors }}
        {{ field.label_tag }} {{ field }}
    </div>
{% endfor %}

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

{% include "form_snippet.html" with form=comment_form %}

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