Class-Based Views & Mixins, In Depth

Understand the CBV method-resolution flow, the mixin MRO, and how to compose your own reusable view behaviour instead of copy-pasting logic.

Generic CBVs feel like magic until they don't — then you need to know the flow. Every class-based view starts at as_view(), which returns a function that calls dispatch(). dispatch() looks at the HTTP method and routes to get(), post(), etc. Knowing this lets you hook in at exactly the right level.

The key override points

  • get_queryset() — scope the data (e.g. only the current user's objects). Prefer this over a class-level queryset when it depends on the request.
  • get_context_data() — add extra variables for the template. Always call super() first.
  • form_valid() / form_invalid() — the seam for "attach the current user before saving".
  • dispatch() — the earliest hook, for cross-cutting checks.

Mixins and the MRO

Mixins add behaviour through multiple inheritance, and order matters. Python resolves methods left-to-right (the MRO), so mixins must come before the base generic view: class V(LoginRequiredMixin, UserPassesTestMixin, UpdateView). Put it the other way round and the mixin's dispatch never runs.

The senior move is to factor your own recurring needs into a mixin — an OwnerQuerysetMixin that limits get_queryset() to request.user, reused across every view that should only show a user their own rows.

Example

Example · python
from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic import ListView, UpdateView
from django.urls import reverse_lazy
from .models import Invoice


class OwnerQuerysetMixin:
    """Reusable: restrict any generic view to the current user's own rows."""
    owner_field = "user"

    def get_queryset(self):
        return super().get_queryset().filter(**{self.owner_field: self.request.user})


# MRO: mixins first, generic view LAST. Access + scoping happen before the view logic.
class InvoiceListView(LoginRequiredMixin, OwnerQuerysetMixin, ListView):
    model = Invoice
    paginate_by = 20

    def get_context_data(self, **kwargs):
        ctx = super().get_context_data(**kwargs)          # ALWAYS super() first
        ctx["total_due"] = sum(i.amount for i in ctx["object_list"])
        return ctx


class InvoiceUpdateView(LoginRequiredMixin, OwnerQuerysetMixin, UpdateView):
    model = Invoice
    fields = ["customer", "amount", "due_date"]
    success_url = reverse_lazy("invoice-list")

    def form_valid(self, form):
        form.instance.last_edited_by = self.request.user   # the classic seam
        return super().form_valid(form)

# Debugging tip: print(InvoiceUpdateView.__mro__) to SEE the resolution order.

When to use it

  • A developer overrides get_form_kwargs() in a CreateView to inject the current user into the form's constructor.
  • A team writes a mixin that overrides get_queryset() to scope every CBV to the logged-in user's own data.
  • A developer uses FormMixin with ProcessFormView to handle forms in a TemplateView without switching to CreateView.

More examples

Inject user into form via get_form_kwargs

Injects the current user into the form via get_form_kwargs and sets it on the instance in form_valid.

Example · python
from django.views.generic.edit import CreateView

class PostCreateView(CreateView):
    model = Post
    fields = ["title", "body"]

    def get_form_kwargs(self):
        kwargs = super().get_form_kwargs()
        kwargs["initial"]["author"] = self.request.user
        return kwargs

    def form_valid(self, form):
        form.instance.author = self.request.user
        return super().form_valid(form)

Scoped queryset mixin

A mixin that scopes the queryset to the current user; reusable across List, Detail, Update, and Delete views.

Example · python
class UserScopedMixin:
    def get_queryset(self):
        return super().get_queryset().filter(author=self.request.user)

class MyPostListView(UserScopedMixin, ListView):
    model = Post
    template_name = "blog/my_posts.html"

class MyPostUpdateView(UserScopedMixin, UpdateView):
    model = Post
    fields = ["title", "body"]

FormMixin in a TemplateView

Mixes FormMixin into TemplateView to add form handling on a page that is primarily a static template.

Example · python
from django.views.generic import TemplateView
from django.views.generic.edit import FormMixin

class HomeWithContactView(FormMixin, TemplateView):
    template_name = "home.html"
    form_class = ContactForm
    success_url = "/thanks/"

    def post(self, request, *args, **kwargs):
        form = self.get_form()
        if form.is_valid():
            return self.form_valid(form)
        return self.form_invalid(form)

Discussion

  • Be the first to comment on this lesson.