Отображение полей формы. Сохранение переданных данных в БД

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

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

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

class AddPostForm(forms.Form):
    title = forms.CharField(max_length=255, label="Заголовок")
    slug = forms.SlugField(max_length=255, label="URL")
    content = forms.CharField(widget=forms.Textarea(), required=False, label="Контент")
    is_published = forms.BooleanField(required=False, label="Статус")
    cat = forms.ModelChoiceField(queryset=Category.objects.all(), label="Категории")
    husband = forms.ModelChoiceField(queryset=Husband.objects.all(), required=False, label="Муж")

Теперь, все выглядит гораздо приятнее. Давайте для примера сделаем поле is_published с установленной галочкой. Пропишем в классе BooleanField параметр initial=True. Также в классах ModelChoiceField добавим параметры empty_label="Категория не выбрана" и empty_label="Не замужем", чтобы вместо черточек отображались по умолчанию в списке эти фразы.

Со всеми возможными параметрами можно ознакомиться в документации, все по той же ссылке:

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

Способы отображения формы в шаблонах

Давайте теперь посмотрим, что же в действительности представляет собой объект form внутри шаблона на примере ручного перебора и отображения всех наших полей. Я сейчас уберу строчку {{ form.as_p }} и вместо нее запишу все поля формы по порядку, друг за другом.

Первое поле title мы сформируем так:

<label class="form-label" for="{{ form.title.id_for_label }}">{{form.title.label}}: </label>{{ form.title }}</p>
<div class="form-error">{{ form.title.errors }}</div>

Смотрите, мы здесь самостоятельно прописали HTML-теги внутри формы. Сначала идет тег абзаца <p>, внутри него тег <label> для оформления подписи. У нее указан класс оформления form-label и идентификатор через свойство form.title.id_for_label. Далее, идет само название form.title.label и после тега <label> отображается поле для ввода заголовка form.title. Вот так можно самостоятельно расписать атрибуты объекта form внутри шаблона. Ну а следующая строчка определяет тег <div> с классом оформления form-error для отображения возможных ошибок при вводе некорректных данных. Список ошибок доступен через переменную form.title.errors.

Все, если теперь обновить страницу сайта, то увидим это одно поле в форме. По аналогии можно прописать и все остальные поля:

<label class="form-label" for="{{ form.slug.id_for_label }}">{{form.slug.label}}: </label>{{ form.slug }}</p>
<div class="form-error">{{ form.slug.errors }}</div>
<label class="form-label" for="{{ form.content.id_for_label }}">{{form.content.label}}: </label>{{ form.content }}</p>
<div class="form-error">{{ form.content.errors }}</div>
<label class="form-label" for="{{ form.is_published.id_for_label }}">{{form.is_published.label}}: </label>{{ form.is_published }}</p>
<div class="form-error">{{ form.is_published.errors }}</div>
<label class="form-label" for="{{ form.cat.id_for_label }}">{{form.cat.label}}: </label>{{ form.cat }}</p>
<div class="form-error">{{ form.cat.errors }}</div>
<label class="form-label" for="{{ form.husband.id_for_label }}">{{form.husband.label}}: </label>{{ form.husband }}</p>
<div class="form-error">{{ form.husband.errors }}</div>

А в самом верху добавим строчку:

<div class="form-error">{{ form.non_field_errors }}</div>

для вывода ошибок валидации, не связанных с заполнением того или иного поля.

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

<div class="form-error">{{ form.non_field_errors }}</div>
{% for f in form %}
<label class="form-label" for="{{ f.id_for_label }}">{{f.label}}: </label>{{ f }}</p>
<div class="form-error">{{ f.errors }}</div>
{% endfor %}

Я, думаю, этот ход вполне понятен: мы здесь на каждой итерации имеем объект поля формы и так же обращаемся к нужным его атрибутам. Такая запись значительно короче и гибче в плане изменения формы (достаточно поменять класс формы и это автоматом изменит ее вид в шаблоне). Правда, все поля теперь будут иметь одни и те же стили оформления.

Однако для виджетов стили оформлений можно прописывать непосредственно в классе формы. Например, у класса поля ввода title добавить именованный параметр widget:

title = forms.CharField(max_length=255, label="Заголовок", widget=forms.TextInput(attrs={'class': 'form-input'}))

Мы здесь формируем виджет через класс TextInput и указываем у него стиль оформления form-input. При обновлении страницы, видим, что первое поле изменило свой вид. И так можно делать со всеми полями. В частности, настроить размер поля Textarea:

content = forms.CharField(widget=forms.Textarea(attrs={'cols': 50, 'rows': 5}), required=False, label="Контент")

То есть, с помощью словаря attrs можно назначать любые атрибуты HTML для соответствующих тегов.

Тестирование формы

Если мы попробуем отправить пустую форму на сервер, то браузер укажет, что поле title обязательное. То же самое и для поля URL, напишем что-нибудь латинскими буквами. Выберем категорию и нажмем «Добавить». В результате, в консоли у нас отображается словарь с принятыми данными:

{'title': 'ghfh', 'slug': 'gfhgfhfg', 'content': '', 'is_published': True, 'cat': <Category: Актрисы>, 'husband': None}

Все это мы можем сохранить в БД и сформировать новый пост. Если же данные в форме окажутся некорректными, например, в поле URL напишем что-то русскими буквами и сделаем отправку. Видим сообщение:

«Значение должно состоять только из латинских букв…»

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

Конечно, помимо используемых параметров в классах имеется множество других, дополнительных. Их можно посмотреть либо на странице документации:

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

Либо в самом PyCharm перейти в определение класса поля и прочитать о возможных используемых переменных в инициализаторе. Здесь все достаточно просто и очевидно.

Добавление новой записи

Теперь, когда наша форма в целом готова, выполним добавление записи в БД. Для этого после проверки валидности данных, запишем конструкцию:

        if form.is_valid():
            #print(form.cleaned_data)
            try:
                Women.objects.create(**form.cleaned_data)
                return redirect('home')
            except:
                form.add_error(None, 'Ошибка добавления поста')

Мы здесь используем ORM Django для формирования новой записи в таблице women и передаем в метод create() распакованный словарь полученных данных. Так как метод create() может генерировать исключения, то помещаем его вызов в блок try и при успешном выполнении, осуществляется перенаправление на главную страницу. Если же возникли какие-либо ошибки, то попадаем в блок except и формируем общую ошибку для ее отображения в форме.

Давайте, для начала введем корректные данные в форму, тогда после нажатия на кнопку «Добавить» в таблице women появится новая запись с заполненными полями. Вернемся в форму и попробуем добавить статью с неуникальным слагом. Тогда возникнет исключение, и мы увидим сообщение «Ошибка добавления поста». Как видите, все достаточно просто.

Это был пример использования формы не связанной с моделью. В результате, нам пришлось в классе AddPostForm дублировать поля, описанные в модели Women и, кроме того, вручную выполнять сохранение данных в таблицу women. На последующих занятиях мы увидим, как все это можно автоматизировать, используя форму в связке с моделью.

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

Видео по теме