skip to content

Mixins & MRO

Access mixins such as LoginRequiredMixin go left-most because super() follows the method resolution order. Interviewers ask when a mixin stack gets harder to follow than a plain function.

part ofDjangooverview, primer and where to startread it →
on this pageshow

explore

questions

5

In Django, why must LoginRequiredMixin be listed before UpdateView in a class-based view's base classes?

level: middleimportance: must knowfreq 62%

answer

  1. left to right lookup
  2. the check lives in dispatch()
  3. View.dispatch() does not call super()
  4. wrong order fails open, silently

basics

~20 s

Access mixins do their check in dispatch() and then call super().dispatch(). Python looks methods up left to right, and View.dispatch() never calls super(), so a mixin placed after UpdateView is never reached and the page is served unprotected.

solid answer

~30 s

`LoginRequiredMixin`, `PermissionRequiredMixin` and `UserPassesTestMixin` all override `dispatch()`: run the check, call `handle_no_permission()` on failure, otherwise `super().dispatch()`. Which `dispatch()` runs first is decided by the class's MRO, built left to right from the bases. With `class EpisodeUpdateView(LoginRequiredMixin, UpdateView)` the mixin's `dispatch()` runs first and hands on to `View.dispatch()`. Reverse the order and `View.dispatch()` is found first; it routes straight to `get()` or `post()` and never calls `super()`, so the mixin is skipped. Nothing raises; the view just fails open. The same rule orders mixins among themselves: `LoginRequiredMixin` first, then permission or test mixins, then the generic view.

code

python · 18 lines
python
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
from django.views.generic.edit import UpdateView

from .models import Episode


class BrokenEpisodeUpdateView(UpdateView, LoginRequiredMixin):
    # View.dispatch() is found first and never calls super(): no login check
    model = Episode
    fields = ['title', 'show_notes']


class EpisodeUpdateView(LoginRequiredMixin, UserPassesTestMixin, UpdateView):
    model = Episode
    fields = ['title', 'show_notes']

    def test_func(self):
        return self.request.user.is_staff

go deeper

for a junior

Remember the placement rule: access mixins go to the left of the generic view, LoginRequiredMixin leftmost.

for a middle

Explain why: the checks run in dispatch() and call super() on success, while View.dispatch() never calls super(), so a mixin to its right is never reached.

for a senior

Show how to prove it with the class's mro, why the failure is silent, and how an anonymous-access test per view catches a reordering.

for a principal

Argue for fail-closed defaults: project-wide login enforcement plus declarative per-view access, so a mistake in inheritance order cannot publish a private page.

## Where access mixins hook in Django's access mixins live in `django.contrib.auth.mixins` and share a base, `AccessMixin`, which holds the configuration (`login_url`, `raise_exception`, `redirect_field_name`, `permission_denied_message`) and the failure handler `handle_no_permission()`. Each concrete mixin overrides one method, **`dispatch()`**: - `LoginRequiredMixin` checks `request.user.is_authenticated`. - `PermissionRequiredMixin` checks `has_permission()`, which calls `request.user.has_perms()` with `permission_required`. - `UserPassesTestMixin` calls the method returned by `get_test_func()`, normally `test_func()`. On failure each returns `self.handle_no_permission()`; on success each returns `super().dispatch(request, *args, **kwargs)`. ## Why order decides whether the check runs A class-based view is one class built from several bases. When Django calls `view.dispatch()`, Python finds the **first** `dispatch` in the method resolution order (MRO), which lists the class, then its bases broadly from left to right. `super()` then continues along that same list. The generic views end in `django.views.generic.base.View`, whose `dispatch()` picks the handler for the HTTP method and calls it. It **does not call `super().dispatch()`**. That is the whole bug: | Declaration | First `dispatch()` found | Check runs? | |---|---|---| | `class V(LoginRequiredMixin, UpdateView)` | `LoginRequiredMixin.dispatch` | Yes, then `View.dispatch` | | `class V(UpdateView, LoginRequiredMixin)` | `View.dispatch` | No, the chain stops there | The wrong order does not raise an error at import or at request time. The podcast episode edit page simply renders for anonymous visitors. You can see the order with `EpisodeUpdateView.__mro__`. ## Ordering several access mixins The same logic orders the mixins among themselves. They run left to right, each only if the one before passed: 1. `LoginRequiredMixin` first, so an anonymous visitor is sent to the login page before anything inspects `request.user` further. 2. Then `PermissionRequiredMixin` or `UserPassesTestMixin`, whose checks may assume a real user (a `test_func` that reads a user profile would fail on `AnonymousUser`). 3. Then your own mixins, then the generic view (`UpdateView`, `ListView`, ...) last. `PermissionRequiredMixin` alone does not let anonymous users in: an anonymous user has no permissions, and `handle_no_permission()` redirects unauthenticated users to the login page. Putting `LoginRequiredMixin` first mainly keeps the checks readable and protects code in `test_func()`. ## Seeing the MRO for yourself Printing `EpisodeUpdateView.__mro__` for the correct declaration lists `LoginRequiredMixin` and `AccessMixin` before `UpdateView` and its chain, ending in `View` and `object`. For the broken declaration the two access classes appear after `View`. That one line settles most interview follow-ups: whichever class appears first in the list and defines `dispatch()` is the one Django calls, and everything after it runs only if that method calls `super()`. ## The broader rule Anything that must wrap the request (access checks, timing, setting attributes before the handler) belongs in a mixin placed to the **left** of the generic view, and must call `super()` to hand on. The Django documentation states it plainly for `LoginRequiredMixin`: it should be at the leftmost position in the inheritance list. Two practical guards: - A test per protected view that requests it anonymously and asserts the redirect or 403, so a reordering is caught in CI. - A project-wide requirement for authentication (Django 5.1 added a middleware for this) makes a forgotten or misplaced mixin fail closed instead of open; how that works belongs to the authentication topic.

  • Does putting a mixin after the generic view break every method it defines?
    No. Only methods that the generic chain also defines and ends without calling `super()` are shadowed, such as `dispatch()` in `View` and `get_context_data()` in `ContextMixin`. A helper method that no other base defines is still found. Access checks live in `dispatch()`, which is why they are the classic casualty.
  • How would you catch a misordered access mixin before it ships?
    Write a test per protected view that requests it without logging in and asserts the login redirect or 403. Requiring authentication project-wide, with Django's login-required middleware since 5.1, also makes a missing check fail closed rather than open.

saying these in an interview costs you the question

  • Mixin order does not matter because Python merges all the dispatch methods
  • A wrongly ordered access mixin raises an error at startup
  • Access mixins go last so the view is set up before checking
  • PermissionRequiredMixin alone lets anonymous users through
  • UpdateView calls super().dispatch() so any mixin order works
open as a page

When writing a custom Django class-based view mixin that adds template context, why must get_context_data() call super() and return the merged dict?

level: middleimportance: should knowfreq 44%

basics

~10 s

Several classes in a Django view contribute context through one cooperative get_context_data() chain. A mixin that skips super() or returns its own dict drops what the others add, such as object, form or page_obj.

open as a page

In a Django UpdateView for podcast episodes guarded by UserPassesTestMixin, why does an ownership test_func load the episode twice, and how do you fix it?

level: seniorimportance: should knowfreq 34%

basics

~10 s

UserPassesTestMixin runs test_func() inside dispatch(), before UpdateView's get() or post() sets self.object, so test_func must call get_object() and the handler then calls it again. Memoise get_object(), or scope get_queryset() to the owner.

open as a page

What rule would you set for a Django team on when to use generic class-based views with mixins versus plain function views?

level: principalimportance: should knowfreq 40%

basics

~20 s

Use a generic class-based view when the page really is list, detail or create/update/delete and needs only a few hook overrides; write a function view when the flow is bespoke. Cap mixin depth, keep access mixins declarative, and review against that.

open as a page

How do you combine Django's SingleObjectMixin with ListView to paginate one podcast's episodes, and which pitfalls must you handle?

level: seniorimportance: nice to knowfreq 22%

basics

~10 s

Subclass SingleObjectMixin and ListView, set self.object = self.get_object(queryset=Podcast.objects.all()) in get() before super().get(), return that podcast's episodes from get_queryset(), and add the podcast to the context yourself.

open as a page