In Django, why must LoginRequiredMixin be listed before UpdateView in a class-based view's base classes?
answer
- left to right lookup
- the check lives in dispatch()
- View.dispatch() does not call super()
- wrong order fails open, silently
basics
~20 sAccess 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 linesfrom 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_staffgo deeper
Remember the placement rule: access mixins go to the left of the generic view, LoginRequiredMixin leftmost.
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.
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.
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