Cat

Django 27.2 Представления на основе классов - Практика

В этом посте на практике изменим функциональные представления на классовые.

Все статьи

Icon Link

Дополнительные материалы

Icon Link

Реклама

Icon Link
Сайт на Django proDream 06 Сентябрь 2023 Просмотров: 1007

Разобравшись для чего нужны функциональные и классовые представления, перейдём к практике по преобразованию представлений.

На данный момент у меня в проекте 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, то всё будет как прежде.

Автор

    Нет комментариев

    Реклама