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-levelquerysetwhen it depends on the request.get_context_data()— add extra variables for the template. Always callsuper()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
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.
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.
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.
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