Class-Based View (CBV) Refactoring

In this chapter, you will learn Django's class-based views (CBV) and refactor the homepage and detail page with more concise code.


FBV vs CBV

Up to now, all our views have been written using functions (Function-Based Views, FBV).

Django also providesClass-Based View (CBV), using classes to encapsulate view logic.

FeaturesFBV (Function-Based View)CBV (Class-Based Views)
code volumeFlexible, but common logic needs to be written repeatedlyAfter inheriting the generic view, only a few lines of code are needed
ReadabilityLinear flow, clear logicThe inheritance chain requires tracking, but once familiar, it's clear at a glance.
ReusabilityEncapsulated via functionsUsing Mixin composition (a Django hallmark)
Applicable scenariosPages with simple logic and clear flowPatterned pages such as create, read, update, delete (CRUD)
HTTP method dispatchManual if request.method == 'POST'Automatically dispatch to get() / post() methods

There is no absolute advantage or disadvantage between the two; Django projects usually...Mixing FBV and CBV: Use FBV for complex logic, and CBV for standard CRUD.


ListView — refactoring the list page

The index view of the homepage is essentially:Query article list → render template。

For this kind of patterned logic, Django's...ListViewAlready written for you.

Example

# File path: blog/views.py
from django.views.generic import ListView
from django.db.models import Q
from .models import Post, Category

class PostListView(ListView):
    """Blog homepage: inherits ListView, displays article list"""
    model = Post                           # Specify model
    template_name = 'blog/index.html'       # Specify the template (default: blog/post_list.html)
    context_object_name = 'posts'           # The variable name used in the template (default: object_list)
    paginate_by = 12                        # 12 items per page (pagination)
    ordering = ['-created_at']             # Sort

    def get_queryset(self):
        """Override the query method: supports category filtering + keyword search"""
        queryset = super().get_queryset()

        # Category filter
        self.category_slug = self.request.GET.get('category', '')
        if self.category_slug:
            queryset = queryset.filter(category__slug=self.category_slug)

        # Keyword search
        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):
        """Override context method: inject additional data into the template"""
        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

Compare the code volume before and after refactoring: FBV is about 40 lines → CBV is about 30 lines, and the logic is clearly divided into different methods.


DetailView — refactoring the detail page

Example

# File path: blog/views.py (newly added)
from django.views.generic import DetailView

class PostDetailView(DetailView):
    """Article detail page: inherits DetailView"""
    model = Post
    template_name = 'blog/post_detail.html'
    context_object_name = 'post'

    # pk_url_kwarg: parameter name for passing the primary key in the URL (default is pk; explicitly declared here).
    pk_url_kwarg = 'pk'

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['title'] = f'{self.object.title} - EXAMPLE Blog'
        return context

Update route configuration

CBV routing syntax is slightly different from FBV — it requires calling....as_view()。

Example

# File path: blog/urls.py
from django.urls import path
from django.contrib.auth import views as auth_views
from . import views

urlpatterns = [
    # CBV: add .as_view() after the class to convert it into a view function
    path('', views.PostListView.as_view(), name='index'),
    path('post/<int:pk>/', views.PostDetailView.as_view(), name='post_detail'),

    # Other views remain unchanged
    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'),
]

Why use CBV?.as_view()? Django's URL configuration expects a callable object (function)..as_view()It converts the class view into a callable object that conforms to the Django view function signature, and internally handles automatically dispatching to the class's get() or post() method based on the HTTP method (GET/POST).


Overview of common generic views

viewDefault template namePurpose
ListViewmodelname_list.htmlDisplay object list
DetailViewmodelname_detail.htmlDisplay details of a single object
CreateViewmodelname_form.htmlCreate a new object (with form)
UpdateViewmodelname_form.htmlUpdate existing object
DeleteViewmodel_name_confirm_delete.htmlConfirm object deletion
TemplateViewneed to specifyRender a static template (no data query)

Django's generic views cover about 80% of web development scenarios. When encountering situations that don't meet your needs, fall back to FBV.


LoginRequiredMixin

CBV not available@login_requiredDecorators (those are for functions), switch to...LoginRequiredMixin。

Example

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

class FavoriteListView(LoginRequiredMixin, ListView):
    """Favorites list: login required to access"""
    model = Post
    template_name = 'blog/favorites.html'
    context_object_name = 'posts'
    ordering = ['-created_at']

    # LoginRequiredMixin: unauthenticated users will be automatically redirected to settings.LOGIN_URL

    def get_queryset(self):
        # Only display articles favorited by the current user
        return self.request.user.favorite_posts.all().order_by('-created_at')

The inheritance order of Mixins is very important:LoginRequiredMixinMust Be Written InListViewEarlier. Python's multiple inheritance resolution order is from left to right; LoginRequiredMixin first checks the login status, and only after passing does it continue executing the ListView logic.


When to use CBV, and when to use FBV?

Rule of thumb:

  • FBV: Views with complex logic, many conditional branches, and non-standard flows (such as toggle_favorite)
  • CBV: Views with standard CRUD operations and logic that can be clearly split into get/post (such as list pages, detail pages)

Operations like toggle_favorite (favorite toggle) have simple logic but a flow different from conventional CRUD; FBV is more appropriate.

For standard data display pages like index and post_detail, CBV can significantly reduce boilerplate code.


Chapter summary

In this chapter, you learned the core usage of Django class-based views: ListView displays lists, DetailView displays details, get_queryset/get_context_data customize queries and context, as_view() enables routing, and LoginRequiredMixin replaces login_required.

The refactored homepage and detail page code is shorter and has a clearer structure.

other extensions