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

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

  1. From Browser To Django
  2. URLs Lead The Way
  3. Views On Views
  4. Templates For User Interfaces
  5. User Interaction With Forms
  6. Store Data With Models
  7. Administer All The Things
  8. Anatomy Of An Application
  9. User Authentication
  10. Middleware Do You Go?
  11. Serving Static Files
  12. Test Your Apps
  13. Deploy A Site Live
  14. Per-visitor Data With Sessions
  15. Making Sense Of Settings
  16. User File Use
  17. Command Your App
  18. Go Fast With Django
  19. Security And Django

Web Forms 101

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

HTML может описывать тип данных, которые, возможно, вы хотите, чтобы ваши пользователи отправляли на ваш сайт. Сбор этих данных осуществляется с помощью нескольких тегов. Основными тегами HTML, которые следует рассмотреть, являются form, input и select.

Тег form является контейнером для всех данных, которые пользователь должен отправить в ваше приложение. У тега есть два критических атрибута, которые указывают браузеру, как отправлять данные: action и method.

action лучше было бы назвать "destination" или "url". Увы, мы застряли с action. Этот атрибут тега form является тем местом, куда должны быть отправлены данные пользователя. Полезно также знать, что если не указывать action или использовать action="", данные формы будут отправлены в виде HTTP-запроса на тот же URL, на котором находится браузер пользователя. Иногда полезно знать поведение по умолчанию action.

Атрибут method диктует, какой метод HTTP использовать, и может иметь значение GET или POST. В паре с action браузер знает, как послать правильно оформленный HTTP-запрос.

Допустим, у нас есть такой пример.

<form method="GET" action="/some/form/">
    <input type="text" name="message">
    <button type="submit">Send me!</button>
</form>

Когда метод формы имеет значение GET, данные формы будут отправлены как часть URL в строке запроса. GET-запрос, отправленный на сервер, будет выглядеть как /some/form/?message=Hello. Этот тип отправки формы наиболее полезен, когда нам не нужно сохранять данные и мы пытаемся выполнить какой-то запрос. Например, вы можете наделить свое приложение функцией поиска с помощью URL-адреса вида /search/?q=thing+to+search. Эти ссылки можно легко добавлять в закладки, и они идеально подходят для такого рода функций.

Метод отправки данных формы POST предназначен для данных, которые мы хотим защитить или сохранить в приложении. При запросе GET данные формы в строке запроса открыты в нескольких местах (см. дополнительная информация от Open Web Application Security Project (OWASP)). С другой стороны, POST отправляет данные в теле HTTP-запроса. Это означает, что если ваш сайт безопасен (т.е. использует HTTPS), то данные шифруются при передаче от браузера к серверу.

Если вы когда-нибудь заходили на сайт и отправляли пароль в форме, вы можете быть почти уверены, что форма отправлена с опцией метода POST (а если это не так, убегайте!).

Мы видели, что form - это контейнер, который определяет, как отправлять данные формы. input и select - это теги, которые позволяют нам отобразить пользователю осмысленную форму.

Более распространенным тегом является input. С помощью тега input авторы форм устанавливают type и name в первую очередь. Атрибут type указывает браузеру, какой тип ввода следует отобразить.

  • Нужен ли нам флажок? type="checkbox"
  • Нужно ли нам поле пароля, скрывающее символы? type="password"
  • Как насчет классического текстового поля? type="text"

Полный список типов можно посмотреть на странице Документация по вводу МДС.

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

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

Зная эти основные элементы HTML-форм, мы готовы понять возможности форм Django. Давайте погрузимся!

Django Forms

Функции форм в Django действуют как мост между формами HTML и классами и типами данных Python. При представлении формы пользователю система форм способна отображать правильные теги и структуру HTML-формы. Получая данные формы после отправки пользователем, система форм может преобразовывать необработанные данные формы в браузере в собственные данные Python, которые мы можем использовать.

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

# application/forms.py

from django import forms

class ContactForm(forms.Form):
    name = forms.CharField(max_length=100)
    email = forms.EmailField()
    message = forms.CharField(max_length=1000)
  1. Django forms are sub-classes of the Form class. This class adds a lot of powerful functionality that will aid us as we explore more.
  2. The data that we want to collect is listed as class level attributes. Each field of the form is a certain field type that has its own characteristics to help translate raw form data into the data types that we want to work with in views.

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

{{ form.as_p }}

После этого Django выполнит рендеринг:

<p><label for="id_name">Name:</label>
  <input type="text" name="name" maxlength="100" required id="id_name"></p>
<p><label for="id_email">Email:</label>
  <input type="email" name="email" required id="id_email"></p>
<p><label for="id_message">Message:</label>
  <input type="text" name="message" maxlength="1000" required id="id_message">
</p>

Чтобы сделать возможным отправку формы, нам нужно обернуть этот вывод тегом form и включить кнопку отправки и CSRF-токен.

А? CSRF-токен? К сожалению, мир полон гнусных людей, которые хотели бы взломать ваше приложение, чтобы украсть чужие данные. CSRF-токен - это мера безопасности, которую Django включает, чтобы затруднить злоумышленникам подделку данных вашей формы. Подробнее о безопасности мы поговорим в одной из следующих статей. Пока же вставьте маркер в ваши формы с помощью встроенного в Django тега шаблона, и все должно работать.

<form action="{% url "some-form-view" %}" method="POST">
    {% csrf_token %}
    {{ form.as_p }}
    <p><input type="submit" value="Send the form!"></p>
</form>

Вот как отображается форма. Теперь давайте рассмотрим представление, которое правильно обрабатывает форму. При работе с представлениями формы мы часто будем использовать представление, способное обрабатывать HTTP-запросы GET и POST. Вот полная форма, которую мы можем разбить на части. Для простоты в примере используется представление функции, но вы можете сделать нечто подобное с представлением на основе класса.

# application/views.py

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

from .forms import ContactForm

def contact_us(request):
    if request.method == "POST":
        form = ContactForm(request.POST)
        if form.is_valid():
            # Do something with the form data like send an email.
            return HttpResponseRedirect(reverse('some-form-success-view'))
    else:
        form = ContactForm()

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

Если мы начнем с размышлений о ветке else, то увидим, как мало делает это представление при запросе GET. Когда HTTP-методом является GET, он создает пустую форму без данных, переданных в конструктор, и отображает шаблон с form в контексте.

contact_form.html будет содержать шаблон Django, который находится выше для отображения HTML-формы пользователю. Когда пользователь нажимает "Отправить форму!", в то же представление приходит другой запрос, но на этот раз метод - HTTP POST и содержит отправленные данные.

Запрос POST создает form, но есть разница в том, как он построен. Данные для отправки формы хранятся в request.POST, который представляет собой словареподобный объект, с которым мы впервые столкнулись в статье о представлениях. Передавая request.POST в конструктор формы, мы создаем форму с данными. В документации Django это называется bound form, потому что данные bound привязаны к форме.

После того как форма готова, представление проверяет, являются ли данные действительными. Мы подробно поговорим о валидации формы позже в этой статье. В данном примере вы видите, что is_valid может вернуть False, если данные формы содержат "I am not an email address" в поле email, например.

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

В этом суть работы с формами! Представленное представление является общим шаблоном для работы с представлениями форм в Django. На самом деле, этот шаблон представления настолько распространен, что Django предоставляет встроенное представление для реализации того, что сделано в примере под названием FormView.

# application/views.py

from django.views.generic import FormView
from django.urls import reverse

from .forms import ContactForm

class ContactUs(FormView):
    form = ContactForm
    template = 'contact_form.html'

    def get_success_url(self):
        return reverse('some-form-success-view')

    def form_valid(self, form):
        # Do something with the form data like send an email.
        return super().form_valid(form)

Поля формы

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

Первое, что нужно помнить о полях формы Django, это то, что они преобразуют данные HTML-формы в собственные типы данных Python. Если вы изучите данные формы отправки, то обнаружите, что каждое значение по умолчанию является строкой. Если бы Django ничего не делал для вас, то вам пришлось бы постоянно преобразовывать данные в нужные вам типы. При работе с полями формы это преобразование данных выполняется автоматически. Например, если вы выбрали BooleanField, то после проверки формы Django значение этого поля будет либо True, либо False.

Еще один важный момент, который необходимо знать о полях - это то, что они связаны с определенными виджетами Django. Виджеты - это способ управления тем, что Django отображает, когда вы выводите форму. Каждое поле формы имеет тип виджета по умолчанию. Если взять BooleanField, то виджет по умолчанию - это CheckboxInput, который отображает тег input с типом checkbox (т.е. стандартный чекбокс формы).

Поля - это критическое пересечение между миром браузера и HTML и миром Python со всеми его надежными типами данных.

К каким полям вы чаще всего обращаетесь? И что вам нужно установить на этих полях?

CharField

CharField - настоящая рабочая лошадка для форм Django. CharField захватывает вводимый текст и использует стандартный тег input с типом text. Если вы хотите собирать больше текста, например, в форме обратной связи, вы можете переключиться со стандартного виджета TextInput на виджет Textarea. Это заставит вашу форму отображать тег textarea, который даст гораздо больше места для ввода.

# application/forms.py

from django import forms

class FeedbackForm(forms.Form):
    email = forms.EmailField()
    comment = forms.CharField(widget=forms.Textarea)

EmailField

Поле EmailField является специализированной версией поля CharField. В поле используется тег input с типом email. Многие современные браузеры могут помочь проверить, что указаны действительные адреса электронной почты. Также, когда это поле проверяется внутри фреймворка, Django попытается проверить и адрес электронной почты, если браузер не смог этого сделать.

DateField

А DateField - это еще одно поле, которое в основном похоже на CharField. Поле даже использует тип input text при визуализации. Разница с этим полем исходит от типа данных, которые форма предоставит после проверки. DateField будет преобразовывать различные форматы строк в объект Python datetime.date.

ChoiceField

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

# application/forms.py

from django import forms

class SurveyForm(forms.Form):
    MEALS = [("b", "Breakfast"), ("l", "Lunch"), ("d", "Dinner")]
    favorite_meal = forms.ChoiceField(choices=MEALS)

Здесь будет содержаться форма с тегом select, который выглядит следующим образом:

<p>
  <label for="id_favorite_meal">Favorite meal:</label>
  <select name="favorite_meal" id="id_favorite_meal">
    <option value="b">Breakfast</option>
    <option value="l">Lunch</option>
    <option value="d">Dinner</option>
  </select>
</p>

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

Поля формы имеют некоторые общие атрибуты для вещей, которые нужны каждому полю.

Атрибут required представляет собой булево значение, которое указывает, должно ли поле иметь значение или нет. Например, не имеет особого смысла, если на вашем сайте есть форма поддержки, которую вы планируете использовать для связи с людьми по электронной почте, а поле email в форме необязательно.

label задает, какой текст будет использоваться для тега label, который отображается вместе с формой input. В примере с едой мы могли бы использовать атрибут label, чтобы изменить "Любимое блюдо" на "Какое ваше любимое блюдо?". Это сделало бы опрос гораздо более удобным.

Иногда формы могут быть непонятны, и пользователям требуется помощь. Вы можете добавить атрибут help_text, который выведет дополнительный текст у поля вашей формы, обернутый в тег span с классом helptext, если вы хотите стилизовать его с помощью CSS.

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

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

Валидация форм

В примере с представлением я показал форму, которая вызывает метод is_valid. На высоком уровне мы можем понять, что делает этот метод; он определяет, является ли форма действительной или нет.

Но что же на самом деле делает is_valid? Он делает многое!

Метод обрабатывает каждое из полей. Как мы видели, поля могут иметь конечный тип данных (как в BooleanField) или ожидаемую структуру (как в EmailField). Этот процесс преобразования типов данных и проверки данных поля называется очисткой. Фактически, каждое поле должно иметь метод clean, который форма будет вызывать при вызове is_valid.

Когда is_valid будет True, данные формы будут находиться в словаре с именем cleaned_data с ключами, которые соответствуют именам полей, объявленных в формах. С подтвержденными данными вы обращаетесь к cleaned_data для выполнения своей работы. Например, если бы у нас была какая-то интеграция с тикет-системой поддержки, возможно, наша FeedbackForm выше обрабатывалась бы в представлении следующим образом:

if form.is_valid():
    email = form.cleaned_data['email']
    comment = form.cleaned_data['comment']
    create_support_ticket(email, comment)
    return HttpReponseRedirect(reverse('feedback-received'))

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

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

Если у вас есть поле формы, вы можете добавить настройку, написав метод на классе формы. Формат метода должен совпадать с именем поля и предваряться clean_. Представим, что мы хотим создать веб-сайт для Bobs. Чтобы зарегистрироваться на сайте, ваш адрес электронной почты должен содержать "bob". Мы можем написать чистый метод для проверки этого.

# application/forms.py

from django import forms

class SignUpForm(forms.Form):
    email = forms.EmailField()
    password = forms.CharField(widget=forms.PasswordInput)

    def clean_email(self):
        email = self.cleaned_data['email']
        if 'bob' not in email:
            raise forms.ValidationError('Sorry, you are not a Bob.')
        return email

В этом вопросе есть несколько важных моментов:

  • clean_email будет пытаться очистить только поле email.
  • Если валидация не прошла, ваш код должен выдать ошибку ValidationError. Django обработает это и поместит ошибку в правильном формате в атрибут errors формы.
  • Если все хорошо, не забудьте вернуть очищенные данные. Это часть интерфейса, который Django ожидает для чистых методов.

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

# application/forms.py

from django import forms

class HistoricalPersonForm(forms.Form):
    name = forms.CharField()
    date_of_birth = forms.DateField()
    date_of_death = forms.DateField()

    def clean(self):
        cleaned_data = super().clean()
        date_of_birth = cleaned_data.get('date_of_birth')
        date_of_death = cleaned_data.get('date_of_death')
        if date_of_birth and date_of_death and date_of_birth > date_of_death:
            raise forms.ValidationError('Birth date must be before death date.')
        return cleaned_data

Этот метод похож на clean_<field name>, но мы должны быть более осторожны. Сначала выполняется валидация отдельных полей, но она может быть неудачной! Когда чистые методы терпят неудачу, поле формы удаляется cleaned_data, поэтому мы не можем выполнить прямой доступ к ключу. Чистый метод проверяет, являются ли две даты истинными и каждая из них имеет значение, а затем выполняет сравнение между ними.

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

Итоги

Так формы позволяют собирать данные от ваших пользователей, чтобы ваш сайт мог взаимодействовать с ними. Мы видели:

  • Веб-формы и тег form HTML
  • Класс Form, который Django использует для обработки данных форм в Python
  • Как Django отображает формы для пользователей
  • Управление тем, какие поля находятся в формах
  • Как сделать валидацию формы

Теперь, когда мы знаем, как собирать данные от пользователей, как сделать так, чтобы приложение хранило эти данные для них? В следующей статье мы начнем хранить данные в базе данных. Мы будем работать с:

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