Django 27.2 Представления на основе классов - Практика
В этом посте на практике изменим функциональные представления на классовые.
Дополнительные материалы
Для скачивания материалов необходимо войти или зарегистрироваться
Файлы также можно получить в Telegram-боте по коду: 546974
Реклама
Разобравшись для чего нужны функциональные и классовые представления, перейдём к практике по преобразованию представлений.
На данный момент у меня в проекте 4 представления, а именно:
- Главная страница
- Страница категории
- Страница поста
- Страница тега
Откроем файлы views.py
и urls.py
.
Представление главной страницы.
Вот так представление выглядит на данный момент:
def index(request):
categories_list = models.CategoryModel.objects.all()
latest_posts = models.PostModel.post_manager.latest_posts()
context = {
'categories_list': categories_list,
'latest_posts': latest_posts,
}
return render(request,
'blog/index.html',
context)
В функции мы получаем список категорий и список последних постов, добавляем их в контекст и возвращаем рендер шаблона с данными.
Для того чтобы переписать это в классовый вид, создадим новый класс IndexView
и унаследуем его от TemplateView
.
В классе пропишем единственное поле - template_name
и присвоим ему строку с путём до шаблона главной страницы.
Также в классе будет всего один метод - get_context_data
, принимающий self
и набор именованных аргументов **kwargs
.
В теле метода, получим из родительского класса экземпляр контекста, представляющий собой словарь.
Далее пропишем две строки, где по ключам добавим данные контекста. Ключом будет переменная для использования в шаблоне, а значением данные.
В конце делаем возврат контекста.
Код:
from django.views.generic import TemplateView
class IndexView(TemplateView):
template_name = 'blog/index.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['categories_list'] = models.CategoryModel.objects.all()
context['latest_posts'] = models.PostModel.post_manager.latest_posts()
return context
В данном случае, нам не нужно беспокоиться о рендере шаблона и прочем. Всё это прописано в родительском классе, он при обращении к странице, получит данные из переопределённого метода и вернёт пользователю шаблон.
URL-паттерн главной страницы.**
У нас уже есть паттерн для главной страницы:
path('', views.index, name='index'),
Он ведёт на функцию, для класса, его необходимо слегка изменить, а именно поменять имя функции index
на имя класса IndexView
и добавить обращение к методу as_view()
, сообщающему, что мы обращаемся к классу-представлению.
path('', views.IndexView.as_view(), name='index'),
Представление страницы категории.
Сейчас представление выглядит так:
def category_page(request, category_slug):
category = models.CategoryModel.objects.get(slug=category_slug)
descendant_categories = category.get_descendants(include_self=True)
posts = models.PostModel.objects.filter(category__in=descendant_categories)
categories_list = models.CategoryModel.get_children(self=category)
context = {
'category': category,
'posts': posts,
'categories_list': categories_list,
}
return render(request,
'blog/category_page.html',
context)
Для вывода списка постов на странице категории будем использовать ListView
.
Создадим класс CategoryPageView
, который наследует ListView
.
В данном классе будет 3 поля, 2 переопределённых метода и один собственный метод.
Поля класса.
Первое поле model
, в нём мы передаём ссылку на модель, с которой будет представление. В нашем случае это модель поста.
Второе поле, как и в предыдущем классе - template_name
.
В третьем поле context_object_name
, мы определяем то, как будет называться переменная к которой мы обращаемся в шаблоне, у нас это - posts
.
Собственный метод.
В нашем классе мы создадим собственный метод get_category
, возвращающий объект текущей категории.
Это сделано для избегания повторных обращений к базе данных, для получения объекта текущей категории в переопределённых методах.
Логика проста, мы не задали поле класса для хранения объекта категории. При обращении к методу, производится проверка, существует или нет поле с именем category
.
Если поле существует, возвращается его значение.
Если поле не существует, то оно создаётся и в него мы присваиваем полученные из БД данные.
Обратите внимание, PyCharm и возможно другие IDE будут сигнализировать, что в классе не задано поле category, игнорируем сообщение, поскольку именно в этом и смысл.
Переопределённые методы.
В предыдущем классе, мы переопределили только один метод get_context_data
, переопределим его и в этом классе.
В методе добавим в контекст объект текущей категории и список подкатегорий.
Вторым переопределённым методом, будет метод get_queryset
, необходимый для создания списка объектов, в нашем случае постов относящихся к этой и вложенным категориям. Об этом я подробно рассказывал в посте про создание представления страницы категории.
Код.
from django.views.generic import ListView
class CategoryPageView(ListView):
model = models.PostModel
template_name = 'blog/category_page.html'
context_object_name = 'posts'
def get_queryset(self):
category = self.get_category()
descendant_categories = category.get_descendants(include_self=True)
return models.PostModel.objects.filter(category__in=descendant_categories)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
category = self.get_category()
context['category'] = category
context['categories_list'] = models.CategoryModel.get_children(self=category)
return context
def get_category(self):
if not hasattr(self, 'category'):
self.category = models.CategoryModel.objects.get(slug=self.kwargs['category_slug'])
return self.category
По аналогии с предыдущим классом, измените URL-паттерн.
path('category/<slug:category_slug>/', views.CategoryPageView.as_view(), name='category_page'),
Представление страницы поста.
Сейчас представление выглядит так:
def post_page(request, category_slug, slug):
post = get_object_or_404(models.PostModel, slug=slug)
post.views += 1
post.save()
context = {
'post': post,
}
return render(request,
'blog/post_page.html',
context)
Для классового представления страницы поста будем использовать DetailView
.
Создадим класс PostPageView
, наследующий DetailView
и пропишем такие же, как в классе страницы категории, поля, а именно - model
, template_name
и context_object_name
.
Переопределим метод get_object
.
В этом методе мы будем получать объект текущего поста и увеличивать счётчик просмотров на 1.
Метод будет вызываться автоматически при открытии поста.
Код.
from django.views.generic import DetailView
class PostPageView(DetailView):
model = models.PostModel
template_name = 'blog/post_page.html'
context_object_name = 'post'
def get_object(self, queryset=None):
obj = super().get_object(queryset=queryset)
obj.views += 1
obj.save()
return obj
И изменим URL-паттерн:
path('post/<slug:category_slug>/<slug:slug>/', views.PostPageView.as_view(), name='post_page'),
Представление страницы тега.
Последнее, четвёртое представление. Сейчас оно такое:
def tag_page(request, tag_name):
posts = models.PostModel.objects.filter(tags__slug=tag_name).distinct()
context = {
'tag_name': tag_name,
'posts': posts,
}
return render(request,
'blog/tag_page.html',
context)
Последний, самый простой, в том плане, что он такой же, как и представление категории.
Создаём класс TagPageView
, унаследованный от ListView
.
В классе прописываем три поля, как в предыдущие два раза.
Переопределяем метод get_queryset
, возвращающий посты относящиеся к текущему тегу.
Переопределяем метод get_context_data
, добавляя в контекст объект текущего тега.
Код.
class TagPageView(ListView):
model = models.PostModel
template_name = 'blog/tag_page.html'
context_object_name = 'posts'
def get_queryset(self):
tag_name = self.kwargs['tag_name']
return models.PostModel.objects.filter(tags__slug=tag_name).distinct()
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['tag_name'] = self.kwargs['tag_name']
return context
И конечно же, URL-паттерн:
re_path(r'/(?P<tag_name>[\w-]+)/$', views.TagPageView.as_view(), name='tag_page'),
Теперь, если запустить Django, то всё будет как прежде.
Все статьи