RizTech Academy logo
RizTech Academy
Views, Templates and FormsLesson 3 of 630 min

Class-based views and when they help

Django offers a second way to write views: class-based views (CBVs). They package the common patterns — "list these objects", "show this one", "create/update/delete via a form" — into reusable classes you configure rather than write from scratch. Used for what they are good at, they remove a lot of boilerplate; reached for reflexively, they hide behaviour behind inheritance and confuse more than they save. This lesson is what they are, when they genuinely help, and when a plain function view is the better call.

The same view, two ways

Here is the Nidaan patient list as a function view and as a class-based ListView:

# function-based
def patient_list(request):
    city = request.GET.get("city", "Pune")
    patients = Patient.objects.filter(city=city).order_by("name")
    return render(request, "patients/patient_list.html", {"patients": patients, "city": city})
# class-based
from django.views.generic import ListView

class PatientListView(ListView):
    model = Patient
    template_name = "patients/patient_list.html"
    context_object_name = "patients"

    def get_queryset(self):
        return Patient.objects.filter(city="Pune").order_by("name")

Both render the same page (verified: the ListView returns 200 with the same template and data). The CBV has no explicit request-handling code — it inherits the "fetch a list, render a template with it" logic from ListView and you just configure the pieces: which model, which template, what the context variable is called, and (optionally) which queryset. You wire it to a URL with .as_view():

path("", PatientListView.as_view(), name="patient_list"),

The generic views worth knowing

Django ships a handful of generic CBVs covering the CRUD lifecycle. These are the ones that earn their place:

View Does You provide
ListView List objects model/get_queryset, template_name
DetailView Show one object model, template_name
CreateView Form to create model, fields/form_class, success_url
UpdateView Form to edit model, fields/form_class, success_url
DeleteView Confirm + delete model, success_url

CreateView and UpdateView are the biggest win: they build the form, handle GET (show it) and POST (validate, save, redirect), and re-render with errors — the entire form dance from the function-view lesson, in a class you configure with a model and a success_url. For standard CRUD on a model, a CBV can replace a dozen lines of predictable view code.

Customising: the hook methods

You adjust a CBV by overriding hook methods rather than rewriting it:

class PatientListView(ListView):
    model = Patient
    context_object_name = "patients"

    def get_queryset(self):                     # customise what is listed
        city = self.request.GET.get("city", "Pune")
        return Patient.objects.filter(city=city).order_by("name")

    def get_context_data(self, **kwargs):        # add extra context
        ctx = super().get_context_data(**kwargs)
        ctx["city"] = self.request.GET.get("city", "Pune")
        return ctx

get_queryset controls the data; get_context_data adds to the context (always call super() first, then add). The request is available as self.request. Verified: this ListView with these overrides renders "Patients in Pune" exactly like the function view. These hooks are the CBV's flexibility — but notice you have to know which hook to override, and that is precisely the learning curve people complain about.

The honest trade-off: when CBVs help and when they hurt

CBVs are not universally better or worse — they trade explicitness for reuse, and the right choice depends on the view:

Use a class-based view when:

  • The view is standard CRUD on a model — ListView, DetailView, CreateView, UpdateView, DeleteView do exactly what you need with minimal configuration. This is their sweet spot.
  • You have many similar views and the shared behaviour is worth factoring into a base class.

Use a function-based view when:

  • The logic is custom or unusual — multiple models, conditional flows, anything that does not map to a generic view. Forcing it into a CBV means overriding so many hooks that you have written more code than a function, in a harder-to-follow shape.
  • Readability matters more than reuse — a function view's logic is right there, top to bottom, with no need to know the inheritance chain. For a one-off or a junior-friendly codebase, that clarity is worth a few extra lines.

The real cost of CBVs is that behaviour is inherited and implicit: to know what CreateView does you must know its parent classes and their method resolution order, which is genuinely hard to trace (the community built the "Classy Class-Based Views" site precisely because the inheritance is so hard to read). A function view hides nothing. So the honest rule: reach for a generic CBV when your view is the standard pattern it implements; write a function view when it is not. Do not convert a clear function view into a maze of overridden hooks for the sake of using classes — that is the common over-correction, and it makes code worse, not better.

Check your work

What a CBV is. A class that packages a common view pattern; you configure attributes/hooks instead of writing request handling. Verified: ListView renders the same page as the function view with no explicit handler code.

The generic views. ListView, DetailView, CreateView, UpdateView, DeleteView — CRUD; Create/ Update handle the whole form GET/POST/validate/redirect dance from configuration.

How to wire and customise. .as_view() in the URL; override get_queryset (data) and get_context_data (extra context, call super() first); the request is self.request.

When CBVs help. Standard CRUD on a model, and families of similar views worth a shared base — their sweet spot.

When function views are better. Custom/unusual logic and readability-first code — where forcing a CBV means overriding many hooks and hiding behaviour in inheritance.

The core trade-off. CBVs trade explicitness for reuse; their cost is implicit, inherited behaviour that is hard to trace — use a generic CBV when your view is that pattern, a function view when it is not.

Practice

  1. Rewrite patient_list as a ListView with get_queryset and get_context_data; confirm it renders identically (200, same template).
  2. Build a DetailView for a patient and wire it with <int:pk>; confirm it shows one patient.
  3. Build a CreateView for Patient with fields and a success_url; confirm it handles GET and POST with no explicit method code.
  4. Take a view with custom multi-model logic and try to express it as a CBV; note how many hooks you must override, then compare with the function version. Decide which is clearer.
  5. Look up one generic view on the "Classy Class-Based Views" reference and trace one method through its parents — feel the implicit inheritance the lesson warns about.
  6. For three Nidaan views, decide CBV or function view and justify each against the trade-off.

Official documentation

Next: forms and ModelForms.

Stuck on this lesson?

Being stuck is part of it — but being stuck alone for three days is not. Our internship programme pairs this curriculum with code review and one-to-one help from working developers, and it is free.

About the internship