Рефакторинг представления на основе классов (CBV)

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


FBV против CBV

До сих пор все наши представления были написаны с использованием функций (представления на основе функций, FBV).

Django также предоставляетпредставление на основе классов (CBV), используя классы для инкапсуляции логики представления.

ОсобенностиFBV (представление на основе функций)CBV (представления на основе классов)
объём кодаГибко, но общую логику приходится писать повторноПосле наследования обобщённого представления требуется всего несколько строк кода
ЧитаемостьЛинейный поток, понятная логикаЦепочку наследования нужно отслеживать, но, освоившись, всё ясно с первого взгляда.
ПереиспользуемостьИнкапсулируется с помощью функцийИспользование композиции Mixin (визитная карточка Django)
Применимые сценарииСтраницы с простой логикой и понятным потокомТиповые страницы, такие как создание, чтение, обновление, удаление (CRUD)
Диспетчеризация HTTP-методовВручную: if request.method == 'POST'Автоматическая диспетчеризация в методы get() / post()

Между ними нет абсолютного преимущества или недостатка; в проектах Django обычно...Смешивание FBV и CBV: используйте FBV для сложной логики, а CBV — для стандартного CRUD.


ListView — рефакторинг страницы списка

Index-представление домашней страницы по сути:Запросить список статей → отрендерить шаблон。

Для такой шаблонной логики Django...ListViewУже написано для вас.

Пример

# Путь к файлу: blog/views.py
from django.views.generic import ListView
from django.db.models import Q
from .models import Post, Category

class PostListView(ListView):
    """Домашняя страница блога: наследует ListView, отображает список статей"""
    model = Post                           # Укажите модель
    template_name = 'blog/index.html'       # Укажите шаблон (по умолчанию: blog/post_list.html)
    context_object_name = 'posts'           # Имя переменной, используемой в шаблоне (по умолчанию: object_list)
    paginate_by = 12                        # 12 элементов на странице (пагинация)
    ordering = ['-created_at']             # Сортировка

    def get_queryset(self):
        """Переопределение метода запроса: поддерживает фильтрацию по категориям + поиск по ключевым словам"""
        queryset = super().get_queryset()

        # Фильтр по категории
        self.category_slug = self.request.GET.get('category', '')
        if self.category_slug:
            queryset = queryset.filter(category__slug=self.category_slug)

        # Поиск по ключевым словам
        self.keyword = self.request.GET.get('q', '')
        if self.keyword:
            queryset = queryset.filter(
                Q(title__icontains=self.keyword) |
                Q(summary__icontains=self.keyword)
            )

        return queryset

    def get_context_data(self, **kwargs):
        """Переопределение метода контекста: внедрение дополнительных данных в шаблон"""
        context = super().get_context_data(**kwargs)
        context['categories'] = Category.objects.all()
        context['category_slug'] = self.category_slug
        context['keyword'] = self.keyword
        context['title'] = 'EXAMPLE Blog - Home'
        return context

Сравните объём кода до и после рефакторинга: FBV — около 40 строк → CBV — около 30 строк, а логика чётко разделена на разные методы.


DetailView — рефакторинг детальной страницы

Пример

# Путь к файлу: blog/views.py (недавно добавлено)
from django.views.generic import DetailView

class PostDetailView(DetailView):
    """Страница подробностей статьи: наследует DetailView"""
    model = Post
    template_name = 'blog/post_detail.html'
    context_object_name = 'post'

    # pk_url_kwarg: имя параметра для передачи первичного ключа в URL (по умолчанию — pk; здесь объявлено явно).
    pk_url_kwarg = 'pk'

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['title'] = f'{self.object.title} - ПРИМЕР Блог'
        return context

Обновление конфигурации маршрута

Синтаксис маршрутизации CBV немного отличается от FBV — требуется вызов....as_view()。

Пример

# Путь к файлу: blog/urls.py
from django.urls import path
from django.contrib.auth import views as auth_views
from . import views

urlpatterns = [
    # CBV: добавьте .as_view() после класса, чтобы преобразовать его в функцию представления
    path('', views.PostListView.as_view(), name='index'),
    path('post/<int:pk>/', views.PostDetailView.as_view(), name='post_detail'),

    # Остальные представления остаются без изменений
    path('register/', views.register, name='register'),
    path('login/', auth_views.LoginView.as_view(
        template_name='blog/login.html',
        redirect_authenticated_user=True
    ), name='login'),
    path('logout/', auth_views.LogoutView.as_view(), name='logout'),
    path('post/<int:pk>/favorite/', views.toggle_favorite, name='toggle_favorite'),
]

Зачем использовать CBV?.as_view()? Конфигурация URL Django ожидает вызываемый объект (функцию)..as_view()Он преобразует классовое представление в вызываемый объект, соответствующий сигнатуре функции представления Django, и внутри автоматически выполняет диспетчеризацию к методу get() или post() класса в зависимости от HTTP-метода (GET/POST).


Обзор распространённых обобщённых представлений

представлениеИмя шаблона по умолчаниюНазначение
ListViewmodelname_list.htmlОтобразить список объектов
DetailViewmodelname_detail.htmlОтобразить сведения об одном объекте
CreateViewmodelname_form.htmlСоздать новый объект (с помощью формы)
UpdateViewmodelname_form.htmlОбновить существующий объект
DeleteViewmodel_name_confirm_delete.htmlПодтвердить удаление объекта
TemplateViewнужно указатьОтрендерить статический шаблон (без запроса данных)

Обобщённые представления Django покрывают около 80% сценариев веб-разработки. Если вы столкнулись с ситуациями, которые не отвечают вашим потребностям, вернитесь к FBV.


LoginRequiredMixin

CBV недоступен@login_requiredДекораторы (они предназначены для функций), переключитесь на...LoginRequiredMixin。

Пример

from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic import ListView

class FavoriteListView(LoginRequiredMixin, ListView):
    """Список избранного: для доступа требуется вход"""
    model = Post
    template_name = 'blog/favorites.html'
    context_object_name = 'posts'
    ordering = ['-created_at']

    # LoginRequiredMixin: неаутентифицированные пользователи будут автоматически перенаправлены на settings.LOGIN_URL

    def get_queryset(self):
        # Показывать только статьи, добавленные в избранное текущим пользователем
        return self.request.user.favorite_posts.all().order_by('-created_at')

Порядок наследования миксинов очень важен:LoginRequiredMixinДолжен быть указан вListViewРанее. Порядок разрешения множественного наследования в Python — слева направо; LoginRequiredMixin сначала проверяет статус входа, и только после успешной проверки продолжает выполнение логики ListView.


Когда использовать CBV, а когда FBV?

Практическое правило:

  • FBV: представления со сложной логикой, множеством условных ветвлений и нестандартными сценариями (например, toggle_favorite)
  • CBV: Представления со стандартными CRUD-операциями и логикой, которую можно чётко разделить на get/post (например, страницы списков, страницы деталей)

Операции вроде toggle_favorite (переключение избранного) имеют простую логику, но поток, отличный от обычного CRUD; FBV подходит больше.

Для стандартных страниц отображения данных, таких как index и post_detail, CBV может значительно сократить шаблонный код.


Итоги главы

В этой главе вы узнали об основных способах использования классовых представлений Django: ListView отображает списки, DetailView отображает детали, get_queryset/get_context_data настраивают запросы и контекст, as_view() обеспечивает маршрутизацию, а LoginRequiredMixin заменяет login_required.

Код переработанной главной страницы и страницы деталей стал короче и имеет более ясную структуру.

Другие расширения