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

  1. as_view() returns a function Django can route to.
  2. dispatch() inspects the HTTP method and calls get(), post(), etc.
  3. For a display view: get_queryset()get_object()get_context_data() → render.
  4. 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 ctx

Mixin 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

Example · python
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.

Example · python
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 response

Compose multiple mixins

Stacks LoginRequired, Permission, and custom Audit mixins; MRO ensures they all fire in order.

Example · python
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.

Example · python
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

  • Be the first to comment on this lesson.