Class-Based Views

Reviewed & published by Brayan K

A class-based view (CBV) in Django is a view written as a Python class instead of a function — it uses methods like get() and post() to handle each HTTP verb, and it lets you reuse and extend view behaviour through inheritance and mixins.

Part of the free Django course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.

You'll meet Django's base View class, render pages with TemplateView, pass data via get_context_data, and learn when a CBV beats a plain function.

What You'll Learn in This Lesson

1 The Base View Class

Every class-based view inherits from django.views.View. Instead of one function with branching logic, you write a separate method for each HTTP verb: get() for GET requests, post() for POST, and so on. Each method receives the request and returns a response — exactly like a function-based view, just organised by verb.

Here is a real Django class-based view. Notice that the URL points at .as_view(), not the class itself:

# views.py
from django.http import HttpResponse
from django.views import View

class GreetingView(View):
    def get(self, request):
        return HttpResponse("Hello from a GET request!")

    def post(self, request):
        return HttpResponse("Thanks for your POST!")

# urls.py
from django.urls import path
from .views import GreetingView

urlpatterns = [
    path("greet/", GreetingView.as_view(), name="greet"),
]
# Let's simulate exactly what Django's View does, in plain Python.
# A View dispatches a request to get(), post(), etc. based on its method.

class View:
    def dispatch(self, request):
        # Pick the method named after the HTTP verb, lowercased.
        verb = request["method"].lower()
        handler = getattr(self, verb, self.not_allowed)
        return handler(request)

    def not_allowed(self, request):
        return "405 Method Not Allowed"

class GreetingView(View):
    def get(self, request):
        return "Hello from a GET request!"

    def post(self, request):
        return "Thanks for your POST!"

# as_view() returns a callable that builds an instance and dispatches.
def as_view(view_class):
    def view(request):
        return view_class().dispatch(request)
    return view

greet = as_view(GreetingView)

print(greet({"method": "GET"}))
print(greet({"method": "POST"}))
print(greet({"method": "DELETE"}))   # falls back to not_allowed

# ✅ Expected output:
# Hello from a GET request!
# Thanks for your POST!
# 405 Method Not Allowed

2 TemplateView and Context

Most pages just render a template with some data. Django's TemplateView handles that for you: set template_name, then override get_context_data() to supply the variables your template needs. You never write get() yourself — TemplateView does it.

# views.py
from django.views.generic import TemplateView

class HomePageView(TemplateView):
    template_name = "home.html"

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context["title"] = "Welcome"
        context["features"] = ["Fast", "Secure", "Scalable"]
        return context

# urls.py
from django.urls import path
from .views import HomePageView

urlpatterns = [
    path("", HomePageView.as_view(), name="home"),
]

In home.html you would then read those values with template tags such as the title variable and a loop over the features list. The key rule: always call super().get_context_data(**kwargs) first so you keep the context Django already built.

# Simulate TemplateView: a base class that builds context, and a subclass
# that adds its own data on top — just like get_context_data() does.

class TemplateView:
    template_name = None

    def get_context_data(self, **kwargs):
        # Django seeds the context with the view itself; we mimic that.
        context = {"view": self.__class__.__name__}
        context.update(kwargs)
        return context

    def render(self):
        context = self.get_context_data()
        return f"Rendering {self.template_name} with {context}"

class HomePageView(TemplateView):
    template_name = "home.html"

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)   # keep base context!
        context["title"] = "Welcome"
        context["features"] = ["Fast", "Secure", "Scalable"]
        return context

page = HomePageView()
print(page.render())

# EXPECTED: the printed context includes view, title, and features.

# ✅ Expected output:
# Rendering home.html with {'view': 'HomePageView', 'title': 'Welcome', 'features': ['Fast', 'Secure', 'Scalable']}

3 CBVs vs FBVs, and Mixins

Neither style is always right. Reach for a function-based view when the logic is short, custom, and read top-to-bottom in one place. Reach for a class-based view when you want to reuse behaviour across pages, handle several HTTP verbs cleanly, or build on Django's generic views.

A mixin is a class that supplies one slice of behaviour, meant to be combined with a view through multiple inheritance. You list mixins before the base view so their behaviour wraps around it. For example, LoginRequiredMixin blocks anonymous users before your get() ever runs:

# views.py
from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic import TemplateView

# Mixin comes FIRST so its check runs before the view.
class DashboardView(LoginRequiredMixin, TemplateView):
    template_name = "dashboard.html"
    login_url = "/login/"
# Mixins layer behaviour through multiple inheritance and super().
# Watch how each mixin wraps the next in the chain.

class TimingMixin:
    def handle(self, request):
        print("[TimingMixin] start timer")
        result = super().handle(request)
        print("[TimingMixin] stop timer")
        return result

class LoginRequiredMixin:
    def handle(self, request):
        if not request.get("user"):
            return "Redirecting to /login/"
        print("[LoginRequiredMixin] user is authenticated")
        return super().handle(request)

class DashboardView:
    def handle(self, request):
        return "Rendering dashboard.html"

# Mixins listed first wrap the view (listed last).
class Dashboard(TimingMixin, LoginRequiredMixin, DashboardView):
    pass

print(Dashboard().handle({"user": "Alice"}))
print("---")
print(Dashboard().handle({"user": None}))   # blocked before rendering

# ✅ Expected output:
# [TimingMixin] start timer
# [LoginRequiredMixin] user is authenticated
# [TimingMixin] stop timer
# Rendering dashboard.html
# ---
# [TimingMixin] start timer
# [TimingMixin] stop timer
# Redirecting to /login/

🎯 Your Turn: Wire Up the View

Fill in the blank so the URL helper calls the right classmethod that turns a view class into a callable Django can route to.

# 🎯 YOUR TURN: replace ___ with the classmethod name that makes
# a class-based view routable in urls.py.

class AboutView:
    @classmethod
    def as_view(cls):
        return f"<callable for {cls.__name__}>"

def build_url(path, view_class):
    # In real Django you write: path("about/", AboutView.as_view())
    return f'path("{path}", AboutView.___())'

print(build_url("about/", AboutView))

# EXPECTED OUTPUT:
# path("about/", AboutView.as_view())

Common Errors (And How to Fix Them)

❌ TypeError: view must be a callable

You passed the class itself to path() instead of the callable it produces.

✅ Fix: call .as_view() in the URL — GreetingView.as_view(), not GreetingView.

❌ KeyError / missing template variables

You overrode get_context_data but never called super(), so the base context was lost.

✅ Fix: start with context = super().get_context_data(**kwargs) before adding your own keys.

❌ 405 Method Not Allowed

You named the method something other than the HTTP verb — for example get_request instead of get.

✅ Fix: name handler methods exactly after the verb: get, post, put, delete.

📋 Quick Reference

ItemWhat It Does
from django.views import ViewImport the base class for class-based views
def get(self, request)Handle a GET request inside the view
MyView.as_view()Return the callable used in urls.py
TemplateViewRender a template with minimal code
get_context_data(**kwargs)Build the variables passed to the template

🏆 Mini Challenge: A Two-Verb View

Build a small View subclass that answers both GET and POST, then route a fake request through your own dispatch to prove it picks the right method.

# 🏆 Mini Challenge: a contact view that handles GET and POST.

class View:
    def dispatch(self, request):
        verb = request["method"].lower()
        handler = getattr(self, verb, self.not_allowed)
        return handler(request)

    def not_allowed(self, request):
        return "405 Method Not Allowed"

class ContactView(View):
    def get(self, request):
        return "Showing the contact form"

    def post(self, request):
        name = request.get("data", {}).get("name", "friend")
        return f"Thanks, {name}! Your message was sent."

view = ContactView()
print(view.dispatch({"method": "GET"}))
print(view.dispatch({"method": "POST", "data": {"name": "Sam"}}))
print(view.dispatch({"method": "PUT"}))

# EXPECTED: a form message, a thank-you to Sam, then 405 for PUT.

❓ Frequently Asked Questions

Lesson complete — you can write class-based views!

You now know how the base View class dispatches GET and POST, how .as_view() wires a class into a URL, how TemplateView and get_context_data render data, and when a CBV beats a function — plus how mixins layer reusable behaviour.

Practice quiz

In a class-based view, which method handles a GET request?

  • get()
  • handle_get()
  • on_get()
  • render()

Answer: get(). A CBV uses a method named after each HTTP verb, so get() handles GET requests.

Which base class do most class-based views inherit from?

  • models.Model
  • forms.Form
  • django.views.View
  • TemplateBase

Answer: django.views.View. django.views.View is the base class for class-based views.

What must you call in urls.py to route a class-based view?

  • MyView.render()
  • MyView.as_view()
  • MyView.dispatch()
  • MyView()

Answer: MyView.as_view(). .as_view() returns the callable the URL resolver needs to dispatch requests.

Which generic view renders a template with minimal code?

  • ListView
  • FormView
  • RedirectView
  • TemplateView

Answer: TemplateView. TemplateView renders a template; you set template_name and override get_context_data.

What must get_context_data() call first to keep the base context?

  • self.render()
  • self.get()
  • super().get_context_data(**kwargs)
  • self.dispatch()

Answer: super().get_context_data(**kwargs). Calling super() first preserves the context Django already built.

What error happens if you pass the class instead of .as_view() to path()?

  • TypeError: view must be a callable
  • 404 Not Found
  • ImportError
  • It works fine

Answer: TypeError: view must be a callable. The URL resolver needs a callable; the class itself is not one without .as_view().

What is a mixin in class-based views?

  • A migration file
  • A URL converter
  • A template tag
  • A small class adding one reusable behavior via multiple inheritance

Answer: A small class adding one reusable behavior via multiple inheritance. Mixins supply one slice of behavior to combine with a view through inheritance.

Where do you list a mixin like LoginRequiredMixin?

  • After the base view
  • Before the base view
  • In settings.py
  • In the template

Answer: Before the base view. Mixins are listed before the base view so their behavior wraps the view.

What causes a 405 Method Not Allowed in a CBV?

  • Forgetting super()
  • Wrong template_name
  • Naming a handler method something other than the HTTP verb
  • Missing as_view()

Answer: Naming a handler method something other than the HTTP verb. Handler methods must be named exactly after the verb (get, post, put, delete).

When is a function-based view a good choice over a CBV?

  • Short, custom, one-off logic read top to bottom
  • Standard CRUD pages
  • Reusing behavior across many pages
  • Building on generic views

Answer: Short, custom, one-off logic read top to bottom. FBVs are simpler and explicit for short, one-off, or unusual logic.

Continue this course

Frequently asked questions

What is the difference between a class-based view and a function-based view?

A function-based view (FBV) is a single Python function that receives the request and returns a response, using if/elif branches to handle different HTTP methods. A class-based view (CBV) is a Python class where each HTTP verb gets its own method — get() handles GET, post() handles POST, and so on. CBVs let you organise logic by verb, reuse behaviour through inheritance, and mix in ready-made functionality, while FBVs are simpler and more explicit for small, one-off views.

Why do I need to call .as_view() in the URL configuration?

Django's URL resolver expects a callable that takes a request and returns a response. A class itself is not that callable — it needs to be instantiated and have its dispatch logic wired up. The .as_view() classmethod returns exactly such a callable: when a request arrives it creates an instance of your view, looks at the request method, and dispatches to the matching get() or post() method. Forgetting .as_view() is the single most common CBV mistake.

What is get_context_data used for in a TemplateView?

get_context_data() builds the dictionary of variables that your template can access. You override it to add your own data, but you must call super().get_context_data(**kwargs) first to keep the context Django already provides (like the view object and URL parameters). You add your keys to that dictionary and return it, and every key becomes available in the template.

What are mixins in class-based views?

A mixin is a small class that provides one piece of reusable behaviour and is designed to be combined with a view through multiple inheritance. For example, LoginRequiredMixin adds authentication checks, and ContextMixin supplies get_context_data. You layer mixins before the base view class in the inheritance list, so their behaviour wraps around the view. Mixins are how Django's generic views compose features without duplicating code.