Skip to content
C Codeloom

Courses / Django Full Stack Web Development

Lesson 4 of 13

Django Class-Based Views: A Practical Tutorial

Understand Django's class-based views by building from View up to ListView and UpdateView. Learn the MRO, mixins, and when CBVs beat function-based views in real projects.

Intermediate 12 min read

What you'll learn

  • How class-based views dispatch HTTP methods
  • The View, TemplateView, ListView, and UpdateView hierarchy
  • How mixins compose behavior via MRO
  • When CBVs are clearly better than FBVs
  • How to override get_queryset and get_context_data
  • Pitfalls in inheritance and method resolution

Prerequisites

  • Familiarity with Django URLs and templates

Django’s class-based views (CBVs) are powerful but famously confusing. Once the dispatch mechanism clicks, they save real time on standard CRUD screens.

What and Why

A class-based view is a Python class with a dispatch method that routes incoming requests to get, post, put, etc., based on the HTTP method. Django ships generic CBVs like ListView, DetailView, CreateView, UpdateView, and DeleteView that implement common patterns so you only override the parts that change.

Function-based views are simpler for one-off pages. CBVs shine when you have many similar pages or when you want to reuse behavior across views.

Mental Model

Picture a CBV as a small pipeline. A URL maps to MyView.as_view(), which returns a callable. That callable creates a fresh instance, runs setup, then dispatch, which looks up self.get or self.post. The generic views layer in mixins like SingleObjectMixin and FormMixin to provide get_object, get_form, and friends. The order they appear in your class declaration determines the method resolution order (MRO).

Hands-on Example

Here is a typical edit page using UpdateView:

from django.views.generic import ListView, UpdateView
from django.urls import reverse_lazy
from .models import Article

class ArticleListView(ListView):
    model = Article
    paginate_by = 20
    template_name = "articles/list.html"

    def get_queryset(self):
        qs = super().get_queryset().filter(published=True)
        q = self.request.GET.get("q")
        return qs.filter(title__icontains=q) if q else qs

class ArticleUpdateView(UpdateView):
    model = Article
    fields = ["title", "body", "published"]
    template_name = "articles/edit.html"
    success_url = reverse_lazy("article_list")

    def form_valid(self, form):
        form.instance.editor = self.request.user
        return super().form_valid(form)
as_view returns a callable. Each request creates an instance, runs setup and dispatch, and dispatch picks get or post based on the HTTP method.

UpdateView already knows how to load the object, build a ModelForm, validate input, save, and redirect. You only override form_valid to attach the editor.

Common Pitfalls

The first pitfall is mutating class attributes. Setting self.queryset = ... inside a method works; setting queryset = ... on the class and then mutating it leaks across requests. Override get_queryset() instead.

The second is misordering mixins. With LoginRequiredMixin, it must come before the generic view in the bases list: class X(LoginRequiredMixin, UpdateView). Reverse the order and the login check never runs.

The third is overriding dispatch without calling super(). You silently break every method handler.

The fourth is forgetting that each request creates a new instance, but the class is shared. Do not stash request-scoped data on the class itself.

Practical Tips

Read the source of the generic view you are using; each is small and pedagogical. Use get_context_data to add extras instead of overriding render_to_response. For permission checks, prefer the official mixins (LoginRequiredMixin, PermissionRequiredMixin, UserPassesTestMixin) over custom dispatch hacks.

When you need substantial logic, do not be afraid to drop back to a function-based view or a plain View subclass with explicit get and post methods. Generic CBVs are a default, not a religion.

Wrap-up

Class-based views reward a small upfront investment in understanding as_view, dispatch, and mixin ordering. Once you have that mental model, generic views remove a lot of repetition from list, detail, and form pages, while remaining easy to escape from when a screen does not fit the pattern.

Progress is saved locally to your browser.