Работа с формами
О документе
Этот документ предоставляет введение в основы веб-форм и их обработку в Django. Для более подробного ознакомления со специфическими разделами API форм см. API форм, Поля форм и Валидация форм и полей.
Если вы не планируете создавать веб-сайты и приложения, которые просто публикуют контент и не принимают входные данные от посетителей, вам нужно будет понять и использовать формы.
Django предоставляет ряд инструментов и библиотек, которые помогут вам создавать формы для приема входных данных от посетителей сайта, а затем обрабатывать и отвечать на эти данные.
HTML-формы
В HTML форма представляет собой набор элементов внутри <form>...</form>, которые позволяют пользователю выполнять такие действия, как ввод текста, выбор параметров, манипулирование объектами или элементами управления и т.д., а затем отправлять эту информацию на сервер.
Некоторые из этих элементов интерфейса формы — текстовые поля или флажки — довольно простые и реализованы в самом HTML. Другие гораздо сложнее; интерфейс, который отображает календарь, позволяет перемещать ползунок или управлять элементами управления, обычно использует JavaScript и CSS, а также элементы HTML-формы <input> для достижения этих эффектов.
Помимо своих <input> элементов, форма должна указывать два параметра:
- куда: URL, на который должны быть возвращены данные, соответствующие вводу пользователя
- как: HTTP-метод, который должен быть использован для возврата данных
Например, форма входа в Django админ-панель содержит несколько <input> элементов: одно type="text" для имени пользователя, одно type="password" для пароля и одно type="submit" для кнопки «Войти». Она также содержит некоторые скрытые текстовые поля, которые пользователь не видит, используемые Django для определения дальнейших действий.
Она также сообщает браузеру, что данные формы должны быть отправлены по URL, указанному в <form> атрибуте - /admin/, и что они должны быть отправлены с помощью HTTP-метода, указанного в method атрибуте - post.
Когда <input type="submit" value="Log in"> элемент активируется, данные возвращаются в /admin/.
GET и POST
GET и POST — единственные HTTP-методы, которые нужно использовать при работе с формами.
Форма входа в Django возвращается с помощью POST метода, в котором браузер собирает данные формы, кодирует их для передачи, отправляет на сервер и получает ответ.
GET, в отличие от этого, собирает отправленные данные в строку и использует это для составления URL. URL содержит адрес, куда должны быть отправлены данные, а также ключи и значения данных. Вы можете увидеть это в действии, если выполните поиск в документации Django, что приведет к URL-адресу в формате https://docs.djangoproject.com/search/?q=forms&release=1.
GET и POST обычно используются для разных целей.
Любой запрос, который может изменить состояние системы (например, запрос, изменяющий базу данных), должен использовать POST. GET следует использовать только для запросов, не влияющих на состояние системы.
GET также не подходит для формы пароля, так как пароль будет отображаться в URL, а значит, и в истории браузера и логах сервера — в открытом виде. Также он не подходит для больших объемов данных или бинарных данных, таких как изображение. Веб-приложение, использующее GET запросы для форм администратора, представляет собой угрозу безопасности: злоумышленник может легко имитировать запрос формы для получения доступа к чувствительным частям системы. POST, в сочетании с другими мерами защиты, такими как защита от CSRF в Django, обеспечивает больший контроль над доступом.
С другой стороны, GET подходит для таких вещей, как форма веб-поиска, поскольку URL-адреса, представляющие GET запрос, легко можно закрепить, поделиться или повторно отправить.
Роль Django в формах
Обработка форм — сложная задача. Рассмотрим Django админ-панель, где много данных разных типов может потребоваться подготовить для отображения в форме, отрендерить как HTML, редактировать с удобным интерфейсом, вернуть на сервер, валидировать и очищать, а затем сохранять или передавать для дальнейшей обработки.
Функциональность форм Django может упростить и автоматизировать значительные части этой работы, а также сделать это более безопасно, чем большинство программистов могли бы сделать с помощью собственного кода.
Django обрабатывает три отдельных части работы с формами:
- подготовка и реструктурирование данных для их подготовки к рендерингу
- создание HTML-форм для данных
- прием и обработка отправленных форм и данных с клиента
Возможно написать код, который сделает все это вручную, но Django может позаботиться обо всем за вас.
Формы в Django
Мы кратко описали HTML-формы, но HTML <form> — всего лишь одна часть необходимой машины.
В контексте веб-приложения «форма» может относиться к этому HTML <form>, или к классу Django Form, который его генерирует, или к структурированным данным, возвращаемым при отправке, или к полному рабочему набору этих частей.
Класс Django Form
В основе этой системы компонентов лежит класс Django Form. Так же, как модель Django описывает логическую структуру объекта, его поведение и то, как его части представлены нам, класс Form описывает форму и определяет, как она работает и выглядит.
Подобно тому, как поля класса модели соответствуют полям базы данных, поля класса формы соответствуют элементам HTML-формы <input>. (Класс ModelForm сопоставляет поля класса модели с элементами HTML-формы <input> через Form; на этом основана админ-панель Django.)
Поля формы — это классы; они управляют данными формы и выполняют валидацию при отправке формы. Класс DateField и FileField обрабатывают очень разные типы данных и должны делать с ними разные вещи.
Поле формы отображается пользователю в браузере как HTML-«виджет» — часть механизма пользовательского интерфейса. Каждый тип поля имеет соответствующий по умолчанию класс виджета, но их можно переопределять по мере необходимости.
Создание, обработка и рендеринг форм
При рендеринге объекта в Django, как правило:
- получаем его в представлении (например, получаем из базы данных)
- передаем его в контекст шаблона
- преобразуем его в HTML-разметку с помощью переменных шаблона
Рендеринг формы в шаблоне во многом похож на рендеринг любого другого объекта, но есть некоторые ключевые различия.
В случае экземпляра модели, не содержащего данных, вряд ли будет полезно что-либо с ним делать в шаблоне. С другой стороны, вполне логично рендерить пустую форму — это то, что мы делаем, когда хотим, чтобы пользователь ее заполнил.
Таким образом, при обработке экземпляра модели в представлении мы обычно получаем его из базы данных. При работе с формой мы обычно создаем экземпляр формы в представлении.
При создании формы мы можем оставить ее пустой или предварительно заполнить, например, с помощью:
- данных из сохраненного экземпляра модели (как в случае с формами администрирования для редактирования)
- данных, собранных из других источников
- данных, полученных при предыдущей отправке 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. Он отобразит текстовое поле с меткой «Ваше имя:» и кнопку «ОК». Если в контексте шаблона есть переменная current_name, она будет использована для предварительного заполнения поля your_name.
Вам понадобится представление, которое рендерит шаблон с HTML-формой и может предоставить поле current_name соответствующим образом.
При отправке формы запрос POST, который отправляется на сервер, будет содержать данные формы.
Теперь вам также потребуется представление, соответствующее URL /your-name/, которое найдет соответствующие пары ключ/значение в запросе и обработает их.
Это очень простая форма. На практике форма может содержать десятки или сотни полей, многие из которых могут потребоваться предварительно заполнить, и мы можем ожидать, что пользователь несколько раз пройдёт цикл редактирования-отправки, прежде чем завершит операцию.
Возможно, потребуется валидация на стороне браузера, даже до отправки формы; возможно, нам понадобятся более сложные поля, которые позволят пользователю, например, выбирать даты из календаря.
В этот момент гораздо проще поручить Django выполнить большую часть этой работы.
Создание формы в Django
Класс Form
Мы уже знаем, как должна выглядеть наша HTML-форма. Начало для неё в Django таково:
from django import forms
class NameForm(forms.Form):
your_name = forms.CharField(label='Your name', max_length=100)
Это определяет класс Form с одним полем (your_name). Мы применили удобочитаемое имя поля, которое будет отображаться в <label> при его рендеринге (хотя в этом случае, указанное label фактически такое же, которое было бы сгенерировано автоматически, если бы мы его опустили).
Максимальная допустимая длина поля определяется параметром 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" required />
Обратите внимание, что он не включает теги <form>, или кнопку отправки. Нам нужно будет предоставить их самим в шаблоне.
Обработка формы
Данные формы, отправленные на веб-сайт Django, обрабатываются представлением, как правило, тем же представлением, которое опубликовало форму. Это позволяет нам повторно использовать часть той же логики.
Для обработки формы нам нужно создать её экземпляр в представлении для URL, куда она должна быть отправлена:
from django.http import HttpResponseRedirect
from django.shortcuts import render
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 запросом, оно создаст пустой экземпляр формы и поместит его в контекст шаблона для рендеринга. Это то, что мы можем ожидать при первом посещении URL.
Если форма отправляется с помощью POST запроса, представление ещё раз создаст экземпляр формы и заполнит его данными из запроса: form =
NameForm(request.POST) Это называется «связыванием данных с формой» (теперь это связанная форма).
Мы вызываем метод is_valid() формы; если он не True, мы возвращаемся в шаблон с формой. В этот раз форма больше не пустая (несвязанная), поэтому HTML-форма будет заполнены ранее отправленными данными, где их можно отредактировать и исправить по мере необходимости.
Если is_valid() является True, мы теперь сможем найти все валидированные данные формы в его атрибуте cleaned_data. Мы можем использовать эти данные для обновления базы данных или для других обработок перед отправкой HTTP-перенаправления браузеру, сообщая ему, куда идти дальше.
Шаблон
В нашем шаблоне name.html нам не нужно делать много. Самый простой пример:
<form action="/your-name/" method="post">
{% csrf_token %}
{{ form }}
<input type="submit" value="Submit" />
</form>
Все поля формы и их атрибуты будут распакованы в HTML-разметку из этого {{ form }} языком шаблонов Django.
Формы и защита от CSRF-атак
Django поставляется с лёгкой в использовании защитой от Cross-Site Request Forgery. При отправке формы через POST с включённой защитой CSRF необходимо использовать тег шаблона csrf_token, как в приведённом выше примере. Однако, так как защита от CSRF не напрямую связана с формами в шаблонах, этот тег опущен из следующих примеров в этом документе.
HTML5 типы input и проверка браузером
Если ваша форма включает URLField, EmailField или любой тип целочисленного поля, Django будет использовать url, email и number HTML5 типы input. По умолчанию, браузеры могут применять свою собственную валидацию на этих полях, которая может быть строже, чем валидация Django. Если вы хотите отключить это поведение, установите атрибут novalidate на теге form, или укажите другой виджет на поле, как TextInput.
Теперь у нас есть рабочая веб-форма, описанная Django Form, обработанная представлением и отображаемая как HTML-<form>.
Этого достаточно для начала, но фреймворк форм предоставляет гораздо больше возможностей. После того, как вы поймёте основы описанного выше процесса, вы должны быть готовы понять другие возможности системы форм и готовы узнать больше о лежащих в основе механизмах.
Подробнее о классах форм Django
Все классы форм создаются как подклассы либо django.forms.Form, либо django.forms.ModelForm. Вы можете представить ModelForm как подкласс Form. Form и ModelForm фактически наследуют общие функции от (приватного) класса BaseForm, но эта реализационная деталь редко важна.
Модели и формы
Фактически, если ваша форма будет использоваться для непосредственного добавления или редактирования модели Django, ModelForm сэкономит вам много времени, усилий и кода, так как она создаст форму, вместе с соответствующими полями и их атрибутами, из класса Model.
Связанные и несвязанные экземпляры форм
Различие между связанными и несвязанными формами важно:
- Несвязанная форма не имеет данных, связанных с ней. При рендеринге для пользователя она будет пустой или будет содержать значения по умолчанию.
- Связанная форма содержит отправленные данные и, следовательно, может использоваться для определения того, являются ли эти данные корректными. Если некорректная связанная форма отображается, она может содержать встроенные сообщения об ошибках, указывающие пользователю, какие данные необходимо исправить.
Атрибут формы is_bound укажет, есть ли данные, привязанные к форме, или нет.
Подробнее о полях
Рассмотрим более полезную форму, чем наш минимальный пример выше, которую мы могли бы использовать для реализации функции «свяжитесь со мной» на личном веб-сайте:
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 соответственно.
Вот как данные формы можно обработать в представлении, которое обрабатывает эту форму:
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" required /></p>
<p><label for="id_message">Message:</label>
<textarea name="message" id="id_message" required></textarea></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>
Обратите внимание, что каждое поле формы имеет атрибут ID, установленный в id_<field-name>, который ссылается на соответствующий тег метки. Это важно для обеспечения доступности форм для вспомогательных технологий, таких как программы чтения с экрана. Вы также можете настроить способ генерации меток и идентификаторов.
См. Вывод форм как HTML для получения дополнительной информации об этом.
Вывод полей вручную
Мы не обязаны позволять Django распаковывать поля формы; мы можем сделать это вручную, если захотим (например, позволяя нам изменять порядок полей). Каждое поле доступно как атрибут формы, используя {{ form.name_of_field }}, и в шаблоне Django будет отображаться соответствующим образом. Например:
{{ 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>
Вывод сообщений об ошибках формы
Конечно, цена этой гибкости — больше работы. До сих пор нам не приходилось беспокоиться о том, как отображать сообщения об ошибках формы, так как это за нас делалось. В этом примере нам необходимо было убедиться, что мы обрабатываем все ошибки для каждого поля и для всей формы в целом. Обратите внимание на {{ 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>
См. 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_emailв примере выше). Если вы создаёте метку вручную, вы можете использовать его вместоlabel_tag. Он также полезен, например, если у вас есть какой-то встроенный JavaScript и вы хотите избежать жёсткого кодирования идентификатора поля. -
{{ field.value }} - Значение поля. Например,
someone@example.com. -
{{ field.html_name }} - Имя поля, которое будет использоваться в поле имени элемента ввода. При этом учитывается префикс формы, если он задан.
-
{{ 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.
Перебор скрытых и видимых полей
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 %}
Если вы часто делаете это, вы можете рассмотреть создание пользовательского тега включения.
Дополнительные темы
Это базовые принципы, но формы могут делать гораздо больше:
-
Наборы форм
- Использование начальных данных с набором форм
- Ограничение максимального количества форм
- Валидация набора форм
- Валидация количества форм в наборе форм
- Обработка упорядочивания и удаления форм
- Добавление дополнительных полей в набор форм
- Передача пользовательских параметров формам набора форм
- Настройка префикса набора форм
- Использование набора форм в представлениях и шаблонах
- Создание форм из моделей
-
Активы форм (класс
Media)
См. также
- Справочник по формам
- Покрывает полную справочную информацию по API, включая поля форм, виджеты форм и валидацию форм и полей.
© Django Software Foundation and individual contributors
Licensed under the BSD License.
https://docs.djangoproject.com/en/2.0/topics/forms/index/