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

Курс по Django: https://stepik.org/a/183363

Архив проекта: 44_sitewomen.zip

Итак, у нас все готово для создания нашей первой формы. Для этого в Django существует специальный класс Form, на базе которого удобно создавать формы, не связанные с моделями:

https://docs.djangoproject.com/en/4.2/ref/forms/api/#django.forms.Form

Где следует объявлять формы? Обычно, для этого создают в приложении отдельный файл forms.py. Мы так и сделаем (создаем файл women/forms.py). И в этом файле импортируем пакет forms и наши модели:

from django import forms
from .models import Category, Husband

Следующий шаг – объявить класс AddPostForm, описывающий форму добавления статьи. Он будет унаследован от базового класса Form и иметь следующий вид:

class AddPostForm(forms.Form):
    title = forms.CharField(max_length=255)
    slug = forms.SlugField(max_length=255)
    content = forms.CharField(widget=forms.Textarea())
    is_published = forms.BooleanField()
    cat = forms.ModelChoiceField(queryset=Category.objects.all())
    husband = forms.ModelChoiceField(queryset=Husband.objects.all())

Смотрите, мы здесь определяем только те поля, с которыми будет взаимодействовать пользователь. Например, поля модели time_create или time_update нигде не фигурируют, так как заполняются автоматически. Далее, каждый атрибут формы лучше назвать так же, как называются поля в таблице women. Впоследствии нам это облегчит написание кода.

В классе формы каждый атрибут – это ссылка на тот или иной экземпляр класса из пакета forms. Например, title определен через класс CharField, поле is_published – через BooleanField, а список категорий cat – через класс ModelChoiceField, который формирует выпадающий список из данных, прочитанных из таблицы Category.

Почему указаны именно такие классы? И какие классы вообще существуют для формирования полей формы? Полный их список и назначения можно посмотреть на следующей странице документации:

https://docs.djangoproject.com/en/4.2/ref/forms/fields/

Я советую вам в целом изучить его и знать, как создавать различные типы полей. В частности, класс CharField служит для создания обычного текстового поля ввода, класс BooleanField – для checkbox’а, класс ModelChoiceField – списка с данными из указанной модели.

Отображение формы в шаблоне

После того, как форма определена, ее можно использовать в функции представления addpage(). В самом простом варианте можно записать так:

def addpage(request):
    form = AddPostForm()
    return render(request, 'women/addpage.html', {'menu': menu, 'title': 'Добавление статьи', 'form': form})

Здесь создается экземпляр формы и через переменную form передается шаблону addpage.html. Осталось отобразить форму в нашем шаблоне. Перейдем в файл addpage.html и пропишем там следующие строчки:

<form action="" method="post">
    {% csrf_token %}
    {{ form.as_p }}
    <button type="submit">Добавить</button>
</form>

Здесь в теге <form> через атрибут action в качестве обработчика указана текущая страница (пустые кавычки). Атрибут method определяет способ передачи информации на сервер (используется POST-запрос). В этом случае внутри формы обязательно записываем специальный тег csrf_token, который генерирует скрытое поле с уникальным токеном. Как я уже отмечал, это необходимо для защиты от CSRF-атак, когда вредоносный сайт пытается отправить данные от имени авторизованного пользователя. Фреймворк Django не станет обрабатывать данные, если отсутствует или не совпадает csrf-токен и, тем самым, защищает пользователя от подобных атак.

Следующая строчка {{ form.as_p }} вызывает метод as_p объекта формы для отображения ее полей с тегами абзацев <p>. Существуют и другие методы, которые формируют поля в виде элементов списка <ul> или в виде таблицы. Последний вариант, хоть и возможен, но считается устаревшей практикой. Здесь также стоит иметь в виду, что по умолчанию все поля в Django обязательны, если не указано обратное через параметр required=False.

Наконец, последняя строчка – тег <button> создает кнопку типа submit для отправки данных формы на сервер и, в конечном итоге, нашей функции представления addpage().

Если теперь обновить страницу, то увидим все указанные поля формы со списком и кнопкой. Заполним их, оставив флажок is_published выключенным, и попробуем отправить данные. Браузер скажет нам, что поле is_published является обязательным. Это не то поведение, которое нам нужно. Давайте все необязательные поля отметим, как необязательные. Для этого в классе AddPostForm у таких атрибутов пропишем параметр required=False:

class AddPostForm(forms.Form):
    title = forms.CharField(max_length=255)
    slug = forms.SlugField(max_length=255)
    content = forms.CharField(widget=forms.Textarea(), required=False)
    is_published = forms.BooleanField(required=False)
    cat = forms.ModelChoiceField(queryset=Category.objects.all())
    husband = forms.ModelChoiceField(queryset=Husband.objects.all(), required=False)

Все, теперь у нас три обязательных поля: title, slug и cat, остальные можно оставлять пустыми.

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

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

Чтобы лучше понять, что мы сейчас будем делать, я покажу данные, которые приходят в функцию, при отправке формы. Поставим в addpage() точку останова, запустим веб-сервер в режиме отладки и просто откроем страницу «Добавить статью». Отобразим содержимое объекта request и видим, что в нем словари GET и POST пустые, а атрибут method принимает значение ‘GET’ (в виде строки). Давайте теперь заполним форму данным и снова отправим ее на сервер. Снова смотрим содержимое объекта request и видим, что словарь POST заполнен переданными данными, а атрибут method соответствует строке ‘POST’. Именно по этому методу мы передаем данные из формы на сервер.

Всей этой информацией мы сейчас воспользуемся для обработки данных из формы. И определим функцию addpost() следующим образом:

def addpage(request):
    if request.method == 'POST':
        form = AddPostForm(request.POST)
        if form.is_valid():
            print(form.cleaned_data)
    else:
        form = AddPostForm()
 
    return render(request, 'women/addpage.html', {'menu': menu, 'title': 'Добавление статьи', 'form': form})

Как это работает? Смотрите, вначале приходит обычный GET-запрос от браузера для открытии страницы по маршруту:

http://127.0.0.1:8000/addpage/

В итоге, условие не срабатывает и создается форма с пустыми полями, которая передается в шаблон и он, затем, отображается в браузере. Именно поэтому мы видим форму без данных. Далее, пользователь заполняет ее поля и отправляет на сервер по POST-запросу. Снова срабатывает наша функция представления addpost() и на этот раз проверка проходит. Формируется объект формы с переданными данными и вызывается метод формы is_valid(), который проверяет поля на корректность заполнения. Если проверка прошла, то в консоли отобразится словарь form.cleaned_data полученных данных от пользователя. Если же проверка не пройдет, то пользователь увидит сообщение об ошибке.

Давайте запустим веб-сервер, заполним нашу форму и нажмем на кнопку «Добавить». Если все заполнено корректно, то метод is_valid() вернет True и в консоли отобразятся, так называемые, очищенные данные (cleaned_data). При этом страница перезагрузится, но введенные данные не пропадут, так как мы в шаблон передаем объект form с заполненными полями, а не пустую.

Если же заполнить форму с ошибками, например в поле slug указать русские символы, то фреймворк Django автоматически сформирует сообщение об ошибке, что в поле slug присутствуют недопустимые символы. Соответственно, метод is_valid() в этом случае вернет False и коллекция cleaned_data не будет отображена в консоли. Видите, как это удобно? Нам самим даже не нужно делать обработку типовых ошибок. Фреймворк Django все берет на себя.

На следующем занятии мы продолжим эту тему и сделаем улучшение внешнего вида формы, а также сохранение переданных данных в БД.

Курс по Django: https://stepik.org/a/183363

Видео по теме