Class-Based Views & Mixins In Depth
Understand the CBV method-resolution flow and compose your own mixins the right way.
Generic CBVs feel like magic until they don't — then you need to know exactly which method runs when. The whole system is a chain of small, overridable hooks. Master the flow and you can bend a generic view to almost any requirement without abandoning it.
The request lifecycle inside a CBV
as_view()returns a function Django can route to.dispatch()inspects the HTTP method and callsget(),post(), etc.- For a display view:
get_queryset()→get_object()→get_context_data()→ render. - For an editing view:
get_form_class()→form_valid()/form_invalid().
Each is a seam you can override. Need to inject the current user into a form? Override get_form_kwargs(). Need an extra context variable? Override get_context_data() and call super() first.
from django.views.generic import ListView
class PublishedPostList(ListView):
model = Post
paginate_by = 20
def get_queryset(self):
return super().get_queryset().filter(status='published')
def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)
ctx['featured'] = Post.objects.filter(featured=True)[:3]
return ctxMixin ordering rules (MRO)
Python resolves methods left-to-right, so mixins go before the base view: class V(LoginRequiredMixin, UserPassesTestMixin, UpdateView). Put the access-control mixins first so their dispatch() runs before any data is fetched. Always call super() in your overrides or you break the chain.
Example
from django.contrib.auth.mixins import LoginRequiredMixin
from django.core.exceptions import PermissionDenied
from django.views.generic import UpdateView, ListView
class OwnerQuerysetMixin:
"""Restrict the queryset to objects owned by the current user."""
owner_field = 'owner'
def get_queryset(self):
qs = super().get_queryset()
return qs.filter(**{self.owner_field: self.request.user})
class FormUserKwargMixin:
"""Pass request.user into the form so it can validate against it."""
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs['user'] = self.request.user
return kwargs
class AuditMixin:
"""Stamp who last touched the object right before saving."""
def form_valid(self, form):
form.instance.updated_by = self.request.user
return super().form_valid(form)
# Compose them — access control first, then data, then behaviour.
class ProjectUpdateView(
LoginRequiredMixin,
OwnerQuerysetMixin,
FormUserKwargMixin,
AuditMixin,
UpdateView,
):
model = Project
fields = ['name', 'description', 'is_public']
owner_field = 'created_by'
def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)
ctx['collaborators'] = self.object.collaborators.all()
return ctx
# Each mixin is testable in isolation and reusable across every editing view.When to use it
- A developer composes three mixins to add login-required, permission-check, and audit-logging to a single view class.
- A team writes a MultipleFormMixin to handle two independent forms on one page using a single CBV.
- A developer overrides dispatch() in a mixin to add request timing instrumentation to every CBV that includes it.
More examples
Mixin that overrides dispatch()
Wraps dispatch() to measure view execution time and add it as a response header for every subclass.
import time
class TimingMixin:
def dispatch(self, request, *args, **kwargs):
start = time.monotonic()
response = super().dispatch(request, *args, **kwargs)
elapsed = time.monotonic() - start
response["X-Request-Duration"] = f"{elapsed:.4f}s"
return responseCompose multiple mixins
Stacks LoginRequired, Permission, and custom Audit mixins; MRO ensures they all fire in order.
from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin
class AuditMixin:
def form_valid(self, form):
self.object = form.save(commit=False)
self.object.last_modified_by = self.request.user
self.object.save()
return super().form_valid(form)
class PostUpdateView(LoginRequiredMixin, PermissionRequiredMixin, AuditMixin, UpdateView):
model = Post
fields = ["title", "body"]
permission_required = "blog.change_post"Multi-form mixin pattern
Handles two independent forms on one page using form prefixes to separate POST data.
class TwoFormView(TemplateView):
template_name = "two_forms.html"
def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)
ctx["profile_form"] = ProfileForm(prefix="profile")
ctx["address_form"] = AddressForm(prefix="address")
return ctx
def post(self, request):
profile_form = ProfileForm(request.POST, prefix="profile")
address_form = AddressForm(request.POST, prefix="address")
if profile_form.is_valid() and address_form.is_valid():
profile_form.save()
address_form.save()
return redirect("success")
return self.render_to_response(self.get_context_data())
Discussion