Класс ListView

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

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

На предыдущем занятии мы с вами заменили функцию index() на класс представления WomenHome, который наследовался от класса TemplateView. Однако можно сделать еще лучше и использовать другой базовый класс ListView созданный специально для отображения произвольных списков:

https://docs.djangoproject.com/en/4.2/ref/class-based-views/

Как мы помним, у нас на главной странице как раз отображается список статей.

Вначале импортируем класс ListView:

from django.views.generic import ListView

И укажем его в качестве базового для класса WomenHome:

class WomenHome(ListView):
    ...

Если сейчас запустить тестовый веб-сервер и перейти на главную страницу, то увидим ошибку. Она связана с тем, что класс ListView предполагает получение данных из таблиц БД. И для этого (в самом простом варианте) в классе WomenHome нужно определить специальный атрибут:

class WomenHome(ListView):
    model = Women

(Все остальные строчки класса WomenHome я поставил в комментарии.)

Фактически, строчка model = Women выбирает все записи из таблицы women и попытается отобразить их в виде списка, используя шаблон с именем:

<имя приложения>/<имя модели>_list.html

Именно эту ошибку «TemplateDoesNotExist» мы видим, при обновлении главной страницы, так как фреймворк не находит шаблон по умолчанию:

women/women_list.html

Конечно, мы могли бы создать такой шаблон, но у нас уже есть свой собственный для этих целей – women/index.html. Чтобы его указать в классе представлений, используется атрибут template_name, которому присваиваем путь к нужному шаблону:

class WomenHome(ListView):
    model = Women
    template_name = 'women/index.html'

Если теперь обновить главную страницу, то увидим пустой список так, словно у нас нет ни одной статьи. Почему это произошло? Дело в том, что мы в шаблоне обращаемся к переменным со своими именами, которые определили в функции представления index. Например, post содержал список всех записей. Спрашивается, что же теперь нужно прописать вместо post? По умолчанию, данные из модели Women, указанной в классе представлений, помещаются в коллекцию object_list. Если мы ее запишем вместо post, то должно все заработать. И, действительно, обновляя главную страницу, видим список всех постов.

Если в шаблоне вместо object_list мы хотим использовать другое обозначение (имя), то в классе WomenHome следует прописать атрибут context_object_name с указанием другого имени переменной:

context_object_name = 'posts'

И, далее, в шаблоне index.html снова можем писать posts.

Осталось в index.html передать остальные данные. Для этого воспользуемся атрибутом extra_context:

class WomenHome(ListView):
    model = Women
    template_name = 'women/index.html'
    context_object_name = 'posts'
 
    extra_context = {
        'title': 'Главная страница',
        'menu': menu,
#        'posts': Women.published.all().select_related('cat'),
        'cat_selected': 0,
    }

Я напомню, что с помощью атрибута extra_context мы можем передавать в шаблон только те данные, которые можно сформировать в момент определения самого класса. Если же данные зависят от параметров GET-запроса, то для этого следует переопределять метод get_context_data(). Для класса ListView он имеет следующий вид:

class WomenHome(ListView):
    model = Women
    template_name = 'women/index.html'
    context_object_name = 'posts'
 
    def get_context_data(self, *, object_list=None, **kwargs):
        context = super().get_context_data(**kwargs)
        context['title'] = 'Главная страница'
        context['menu'] = menu
        context['cat_selected'] = 0
        return context

Здесь дополнительно определен параметр object_list.

Все, главная страница приобрела почти тот же самый вид. Почему почти? Потому что строчка model = Women, как я уже говорил, выбирает все записи из таблицы women, а мы это делали с помощью команды:

Women.published.all().select_related('cat')

Чтобы это поправить вместо атрибута model следует переопределить специальный метод get_queryset() следующим образом (атрибут model следует удалить из класса):

    def get_queryset(self):
        return Women.published.all().select_related('cat')

Вот так, переопределяя метод get_queryset(), можно задавать свой алгоритм выборки записей из модели.

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

Создаем класс представлений для категорий

Итак, мы с вами создали класс представления для главной страницы. Давайте повторим этот процесс и пропишем аналогичный класс для отдельных категорий. Делается это очень просто. Сначала объявим класс WomenCategory с тем же базовым классом ListView, указав те же самые атрибуты и методы:

class WomenCategory(ListView):
    template_name = 'women/index.html'
    context_object_name = 'posts'
 
    def get_context_data(self, *, object_list=None, **kwargs):
        context = super().get_context_data(**kwargs)
        cat = context['posts'][0].cat
        context['title'] = 'Категория - ' + cat.name
        context['menu'] = menu
        context['cat_selected'] = cat.id
        return context 
 
    def get_queryset(self):
        return Women.published.filter(cat__slug=self.kwargs['cat_slug']).select_related('cat')

Обратите внимание, как записана выборка записей. Здесь первым параметром указано имя cat__slug – это способ обращения к слагу таблицы category через объект cat модели Women. Далее, указываем, что поле slug у категории должно быть равно параметру cat_slug, который мы берем из словаря kwargs объекта класса WomenCategory. Ключ cat_slug автоматически формируется по шаблону маршрута (файл women/urls.py), в котором мы должны вместо функции указать класс представления:

path('category/<slug:cat_slug>/', WomenCategory.as_view(), name='category'),

В принципе, это все. Если теперь перейти на страницу сайта и выбирать категории, то будут отображаться статьи выбранной рубрики. Но, если указать несуществующий слаг, то увидим пустую страницу, а нам бы хотелось увидеть ошибку 404 – страница не найдена. Для этого в классе WomenCategory достаточно указать атрибут:

allow_empty = False

который указывает генерировать исключение 404 если список статей пуст. Так мы сохраняем общий функционал нашего сайта.

В качестве самостоятельного задания сделайте замену функции show_tag_postlist() классом представления с именем TagPostList, унаследованным от базового класса ListView.

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

Видео по теме