Django 37. Две формы - добавление категории и файла
В этом посте добавим две формы, создадим три представления, применив наследование и напишем два шаблона для страниц.
Дополнительные материалы
Для скачивания материалов необходимо войти или зарегистрироваться
Файлы также можно получить в Telegram-боте по коду: 821104
Реклама
В прошлом посте мы написали форму добавления поста автором.
В этом посте добавим две формы: добавление новой категории и добавления файла для поста.
В процессе работы мы закрепим создание форм, создадим три представления, применив наследование, а также создадим два шаблона.
Однако есть и второй вариант, а именно создание нового связанного объекта (в нашем случае категория и файл поста) прямо на странице добавления нового поста. Примерно также, как это реализовано в панели администратора Django. Подробнее можно прочитать в посте на Boosty "Django - Добавление новой категории в форме создания поста без перезагрузки страницы" (платный контент).
Формы добавления категории и файла поста.
Откроем файл forms.py
в директории приложения user_app
.
Форма добавления категории.
Создадим новый класс AddCategoryByAuthorForm
, унаследованный от ModelForm
.
Так же, как мы делали в посте "Django 36. Добавление постов пользователем", создаём конструктор класса __init__
.
В нём добавляем всем полям формы bootstrap-класс form-control
и дополнительный класс для поля description
- django_ckeditor_5
, для работы визуального редактора Django CKEditor 5
.
Обратите внимание, в прошлом посте мы делали поле необязательным к заполнению для корректной работы редактора. В этот раз поле необязательно по умолчанию, поэтому пропускаем этот параметр.
Далее создаём подкласс Meta
, прописываем модель и необходимые поля.
Код формы:
from blog.models import CategoryModel
class AddCategoryByAuthorForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
for field in self.fields:
self.fields[field].widget.attrs.update({'class': 'form-control', 'autofocus': ''})
self.fields['description'].widget.attrs.update({'class': 'django_ckeditor_5'})
class Meta:
model = CategoryModel
fields = ('title', 'parent', 'description', 'telegram_link')
Форма добавления файла.
Форма такая же, как и предыдущая. Исключение составляет только отсутствие дополнительного класса для поля, модель и сам список полей формы.
Код формы:
from blog.models import PostFilesModel
class AddFileByAuthorForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
for field in self.fields:
self.fields[field].widget.attrs.update({'class': 'form-control', 'autofocus': ''})
class Meta:
model = PostFilesModel
fields = ('title', 'file')
Представления страницы добавления категории и страницы добавления файла поста.
Откроем файл views.py
в директории приложения user_app
.
Прочитав вступление, вы можете задаться вопросом: "зачем нам три представления, если будет две формы?". Всё верно, мы создадим три представления, а не два.
В посте "Django 27.1 Представления на основе классов" я описывал преимущества классовых представлений, одно из них - наследование.
Оба представления практически идентичны, отчего было бы разумнее сделать общий класс представления и уже от него наследовать два конкретных представления для страниц.
Базовый класс.
Создадим класс AddCategoryAndFile
с двойным наследованием: UserPassesTestMixin
и CreateView
.
В теле класса будет всего два метода:
test_func
- для проверки того, что пользователь входит в группу "Автор". Подробнее об этом рассказывал в прошлом посте - "Django 36. Добавление постов пользователем".get_success_url
- метод, определяющий куда будет перенаправлен пользователь после заполнения формы. В нашем случае на страницу профиля.
Это всё, что будет в нашем классе. Остальные параметры будут заменены в классах-наследниках.
Код класса:
from django.contrib.auth.mixins import UserPassesTestMixin
from django.views.generic import CreateView
class AddCategoryAndFile(UserPassesTestMixin, CreateView):
def test_func(self):
return self.request.user.groups.filter(name='Автор').exists()
def get_success_url(self):
return reverse('user_app:user_profile', kwargs={'username': self.request.user.username})
Представление добавления файла.
Создадим класс AddFileView
, унаследованный от нашего класса AddCategoryAndFile
.
В этом представлении не будет методов, только четыре поля:
template_name
- шаблон страницы.form_class
- класс формы.model
- используемая модель.extra_context
- дополнительные данные, в нашем случае это заголовок страницы.
Это весь класс.
Код класса:
from blog.models import PostFilesModel
class AddFileView(AddCategoryAndFile):
template_name = 'user_app/add_file.html'
form_class = forms.AddFileByAuthorForm
model = PostFilesModel
extra_context = {'title': 'Добавление файла'}
Представление добавления категории.
Создадим класс AddCategoryView
, унаследованный от нашего класса AddCategoryAndFile
.
В этом представлении будут точно такие же поля, как и в предыдущем, а также один метод:
form_valid
- в этом методе мы приводим полеslug
к корректному виду. Почему это важно сделать, я объяснял в прошлом посте.
Код класса:
from blog.models import CategoryModel
class AddCategoryView(AddCategoryAndFile):
template_name = 'user_app/add_category.html'
form_class = forms.AddCategoryByAuthorForm
model = CategoryModel
extra_context = {'title': 'Добавление категории'}
def form_valid(self, form):
form.instance.slug = slugify(form.instance.title)
return super().form_valid(form)
URL-паттерны.
Откроем файл urls.py
в директории приложения user_app
и в список urlpatterns
добавим следующие строки:
path('post/add_category/', views.AddCategoryView.as_view(), name='add_category'),
path('post/add_file/', views.AddFileView.as_view(), name='add_file'),
Шаблоны страниц.
В директории с шаблонами приложения user_app
создадим два файла:
add_category.html
- файл с формой добавления категории.add_file.html
- файл с формой добавления файла поста.
Оба шаблона практически идентичны.
Различия заключаются в том, что в форме добавления категории выводится {{ form.media }}
для отображения визуального редактора, а в форме добавления файла указано enctype="multipart/form-data"
, означающее, что форма может передавать файлы.
Код страницы добавления категории:
{% extends 'blog/base.html' %}
{% block title %}{{ title }}{% endblock %}
{% block content %}
<div class="container mt-3 d-flex justify-content-center">
<div class="col-lg-8 col-sm-12">
<h2>{{ title }}</h2>
<form method="post">
{% csrf_token %}
{{ form.media }}
{{ form.as_p }}
<button type="submit" class="btn btn-primary my-btn-success mt-3">Сохранить</button>
</form>
</div>
</div>
{% endblock %}
Код страницы добавления файла:
{% extends 'blog/base.html' %}
{% block title %}{{ title }}{% endblock %}
{% block content %}
<div class="container mt-3 d-flex justify-content-center">
<div class="col-lg-8 col-sm-12">
<h2>{{ title }}</h2>
<form method="post" enctype="multipart/form-data">
{% csrf_token %}
{{ form.as_p }}
<button type="submit" class="btn btn-primary my-btn-success mt-3">Сохранить</button>
</form>
</div>
</div>
{% endblock %}
Добавляем ссылки в меню пользователя.
Откройте файл header.html
и под ссылкой на добавление нового поста добавьте следующий код:
<li>
<a class="dropdown-item dd-item-color" href="{% url 'user_app:add_category' %}">Добавить
категорию</a>
</li>
<li>
<a class="dropdown-item dd-item-color" href="{% url 'user_app:add_file' %}">Добавить
файл</a>
</li>
Заключение.
Довольно часто приходится создавать однотипные классы, страницы или просто использовать одни и те же фрагменты кода несколько раз.
Для решения проблемы ненужного "раздувания" кода - отлично подходит наследование и создание базовых классов.
Если вас заинтересует другой способ реализации подобного функционала (не в виде отдельных страниц, а непосредственно в форме добавления поста), то об этом я рассказываю в посте на Boosty "Django - Добавление новой категории в форме создания поста без перезагрузки страницы" (платный контент).
В следующем посте мы продолжим эту тему и затронем такие конструкции, как декораторы и миксины.
Все статьи