Django

Django Class-Based Views👨‍💻

Class-Based Views (CBVs) are Django's answer to repetitive view code. Instead of writing the same list-filter-paginate or create-validate-save logic in every function-based view, you subclass a generic view that already handles the common pattern and override only what differs.

Here is the honest take: CBVs are not universally better than function-based views (FBVs). They shine when your view fits a well-known pattern — listing objects, showing a detail page, handling a form submission. For those cases, a CBV can reduce a 30-line function to a 5-line class. But for views with complex, non-standard logic — an endpoint that talks to three external APIs and conditionally redirects based on business rules — a plain function is usually clearer. The rule of thumb: if you are fighting the generic view more than using it, switch to an FBV.

Django 6 ships the same CBV hierarchy that has been stable since Django 1.3, but now requires Python 3.12+ and works seamlessly with Django's new Content Security Policy middleware and the async view support introduced in Django 4.1. The fundamentals have not changed: View is the base class, dispatch() routes requests to get()/post() methods, and generic views like ListView and CreateView handle the heavy lifting.

Key Takeaways

  • 1CBVs use Python classes with methods for each HTTP verb — `dispatch()` routes the request to `get()`, `post()`, `put()`, etc.
  • 2Generic views (`ListView`, `DetailView`, `CreateView`, `UpdateView`, `DeleteView`) implement common CRUD patterns so you only configure what differs
  • 3Override `get_queryset()` for filtering, `get_context_data()` for extra template context, and `form_valid()` for pre-save logic
  • 4Mixins add cross-cutting behavior (auth, permissions, messages) through multiple inheritance — always place them before the base view class
  • 5Use `reverse_lazy()` (not `reverse()`) for class-level `success_url` because URLs are not loaded at class definition time
  • 6Every CBV request creates a fresh instance — there is no shared state between requests, so class attributes are safe to use as defaults

Master django class-based views

Take the Django Foundations course with hands-on lessons and challenges.

Examples

ListView with filtering and pagination

python

ListView handles queryset fetching, pagination, and template rendering. Override get_queryset() to add filtering — never replace the queryset attribute directly when you need request-dependent logic. get_context_data() injects extra variables into the template without touching the main queryset.

DetailView with related objects

python

DetailView fetches a single object by pk or slug from the URL. Override get_queryset() — not get_object() — to add access control or eager loading. Django calls get_object() internally, which applies the URL kwargs to whatever get_queryset() returns.

CreateView and UpdateView with form_valid()

python

CreateView and UpdateView share the same template. form_valid() is the hook for injecting data the user should not control (like the author). Use form_class instead of fields when you need custom validation or widgets. Notice get_queryset() on UpdateView for row-level permissions — if the user does not own the article, Django returns a 404 rather than a 403, which avoids leaking that the object exists.

Custom mixins — reusable cross-cutting behavior

python

Mixins are small classes that each do one thing. The order matters: Python's MRO processes left to right, so LoginRequiredMixin runs before OwnerRequiredMixin. Always place mixins before the base view class. Every mixin that overrides a method must call super() to keep the chain intact.

The CBV method flow — dispatch, setup, and HTTP handlers

python

Understanding the method flow is critical for debugging CBVs. setup() is for per-request initialization (added in Django 2.2 to replace __init__ hacks). dispatch() is the central router — override it for logic that applies to all HTTP methods. The HTTP handler methods (get, post, etc.) contain the actual business logic. A new instance is created for every request, so there is no shared mutable state.

Common Mistakes

Mistake:

Overriding `get_object()` to filter the queryset instead of overriding `get_queryset()`

Fix:

Override `get_queryset()` and let Django's built-in `get_object()` apply the pk/slug lookup on top of it. This keeps the filtering logic in one place and works correctly with both DetailView and editing views. Reserve `get_object()` overrides for truly unusual lookup patterns.

Mistake:

Setting `queryset = Model.objects.filter(...)` as a class attribute when the filter depends on the request (e.g., `request.user`)

Fix:

Class attributes are evaluated once at import time, not per request. Use `get_queryset()` for any filtering that depends on the request: `def get_queryset(self): return Article.objects.filter(author=self.request.user)`. The `queryset` attribute is only safe for static filters.

Mistake:

Forgetting to call `super()` in overridden methods like `get_context_data()` or `form_valid()`, which breaks the mixin chain

Fix:

Always call `super()` and build on top of its return value. For `get_context_data`: `context = super().get_context_data(**kwargs)` then add your keys. For `form_valid`: `return super().form_valid(form)` after your custom logic. Skipping `super()` silently disables every mixin above you in the MRO.

Mistake:

Placing the base view class before mixins in the inheritance list — e.g., `class MyView(ListView, LoginRequiredMixin)`

Fix:

Mixins must come first: `class MyView(LoginRequiredMixin, ListView)`. Python's MRO resolves methods left to right. If `ListView` comes first, its `dispatch()` runs before `LoginRequiredMixin` gets a chance to check authentication, making the mixin useless.

Best Practices

  • Use CBVs for standard CRUD patterns (list, detail, create, update, delete) and FBVs for one-off views with complex branching logic — do not force every view into a class
  • Always set `context_object_name` on ListView and DetailView to give template variables meaningful names instead of the generic `object_list` and `object`
  • Use `form_class` with a dedicated Form or ModelForm instead of the `fields` attribute when you need custom validation, widgets, or field ordering
  • Keep mixins small and single-purpose — a mixin that does three things should be three mixins
  • Prefer `get_queryset()` over `get_object()` for access control so that unauthorized lookups return 404, not 403, avoiding object existence leaks
  • Use `reverse_lazy()` for any URL reference in class-level attributes (`success_url`, `login_url`) since the URL configuration is not loaded at class definition time

Summary

Django's class-based views eliminate boilerplate for common patterns. ListView, DetailView, CreateView, UpdateView, and DeleteView cover standard CRUD operations with minimal configuration. The key extension points are `get_queryset()` for filtering, `get_context_data()` for template variables, and `form_valid()` for pre-save logic. Mixins add reusable behavior like authentication and permissions — always place them before the base view in the inheritance list and always call `super()`. Use CBVs when your view fits a well-known pattern; reach for function-based views when the generic view fights you more than it helps.

Practice Django with hands-on challenges

Learn django class-based views hands-on in your IDE

Interactive lessons and challenges on Stanza, practice in VS Code, Cursor, or the web.

Related Concepts

Related Cheatsheets

Master Django with Stanza

Interactive lessons and challenges, right in your code editor.

Check the free courses. No credit card.