After a MIDDLEWARE reshuffle, a Django project's custom audit middleware fails with 'WSGIRequest' object has no attribute 'user'; how do you diagnose and fix it?
answer
- who sets the attribute, and when
- position relative to authentication
- early returns above it
- process_view runs later than you think
basics
~20 srequest.user exists only after AuthenticationMiddleware has run on the way in. The audit middleware is listed above it, AuthenticationMiddleware was removed, or a layer above it returned early. Move the audit middleware below AuthenticationMiddleware, or read the user defensively.
solid answer
~40 s`request.user` is not built into the request: `AuthenticationMiddleware` sets it, as a lazy object, in its request phase. So I check three things. First, whether the audit middleware now sits **above** `AuthenticationMiddleware`; its before-phase then runs first and the attribute does not exist yet. Second, whether `AuthenticationMiddleware` is still in the list at all. Third, whether the failure is in the after-phase for requests that a layer above authentication answered early, such as `SecurityMiddleware`'s HTTPS redirect, `CommonMiddleware`'s `PREPEND_WWW` redirect or its `DISALLOWED_USER_AGENTS` 403; those requests never reach it. The fix is to list the audit middleware below `AuthenticationMiddleware`, read the user with `getattr(request, "user", None)` in the after-phase, or do the check in `process_view`, which runs after every before-phase. A swapped Session/Authentication pair gives a different error, `ImproperlyConfigured`, which helps tell the cases apart.
code
python · 16 linesimport logging
logger = logging.getLogger("audit")
class AuditMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
response = self.get_response(request)
# Requests answered above AuthenticationMiddleware have no user attribute.
user = getattr(request, "user", None)
if user is not None and user.is_authenticated and request.method == "POST":
logger.info("user=%s path=%s status=%s", user.pk, request.path, response.status_code)
return responsego deeper
Recall that AuthenticationMiddleware sets request.user, so middleware that reads it must be listed after it.
Explain before-phase versus after-phase access to request.user and how an early return above authentication leaves it unset.
Diagnose from the traceback and the list, distinguish the missing-attribute case from ImproperlyConfigured, and add a check or defensive read so the next reshuffle cannot regress.
Decide how shared middleware declares its dependencies, for example through project checks, so ordering stays correct as teams add entries.
## Where request.user comes from A Django `HttpRequest` has no `user` attribute of its own. `AuthenticationMiddleware.process_request()` adds it on the way in, as a `SimpleLazyObject` that loads the user from the session the first time it is touched. Anything that runs **before** that point, or on a request that **never reached** it, sees no attribute, and Python raises `AttributeError: 'WSGIRequest' object has no attribute 'user'` (or `'ASGIRequest'` under ASGI). ## Diagnosis, step by step 1. **Read the traceback.** Note which middleware and which method raised: `__call__` before `get_response`, after it, or a hook. 2. **Check the position.** Find the audit middleware and `AuthenticationMiddleware` in `MIDDLEWARE`. If the audit middleware is higher, its before-phase runs first and the attribute does not exist yet. 3. **Check presence.** A reshuffle sometimes drops or comments out `AuthenticationMiddleware`; then no request ever gets `request.user`. With the admin installed, `manage.py check` reports `admin.E408` in that case. 4. **Check for early returns.** If the failure is in the after-phase and only for some requests, look for layers **above** `AuthenticationMiddleware` that answer on their own: `SecurityMiddleware`'s redirect when `SECURE_SSL_REDIRECT` is on, `CommonMiddleware`'s `PREPEND_WWW` redirect, or its 403 for `DISALLOWED_USER_AGENTS`. Those requests turn back before authentication runs, so they have no `user` when they pass the audit middleware on the way out. 5. **Rule out the other ordering bug.** If `AuthenticationMiddleware` sits above `SessionMiddleware`, the error is different: `ImproperlyConfigured`, saying the authentication middleware requires session middleware. ## Symptoms and causes | Symptom | Likely cause | Fix | |---|---|---| | Every request fails in the audit before-phase | audit middleware listed above `AuthenticationMiddleware` | move it below | | Every request fails, admin check reports `admin.E408` | `AuthenticationMiddleware` missing | restore it after `SessionMiddleware` | | Only redirects or 403s fail, in the after-phase | a layer above authentication returned early | read the user defensively, or accept those requests have no user | | `ImproperlyConfigured` about session middleware | `AuthenticationMiddleware` above `SessionMiddleware` | put `SessionMiddleware` first | ## The fixes - **Reorder.** List the audit middleware **after** `AuthenticationMiddleware`. That also places it after `SessionMiddleware`, which authentication needs. - **Be defensive in the after-phase.** `user = getattr(request, "user", None)` handles requests that were answered before authentication ran. - **Use `process_view` when you need the view too.** `process_view` hooks run inside the innermost handler, after every middleware's before-phase, so even a middleware listed above `AuthenticationMiddleware` finds `request.user` there, as long as nothing returned early. - **Enforce it.** A small project system check registered with `django.core.checks.register` can assert that the audit middleware's index is greater than `AuthenticationMiddleware`'s, so the next reshuffle fails `manage.py check` instead of production. ## A cost worth knowing `request.user` is lazy for a reason: until something touches it, no session read or user query happens. An audit middleware that reads `request.user` on **every** request forces that work even for requests that never needed a user. If the audit only matters for some paths or views, check `request.user` only there, for example inside `process_view` after deciding the view is relevant.
- Why can a middleware listed above AuthenticationMiddleware still read request.user in process_view?`process_view` hooks are not part of the before-phase. Django calls them from the innermost handler, after the URL is resolved and after every middleware's `__call__` has passed the request down, including `AuthenticationMiddleware`. By then `request.user` exists, unless a layer returned early, in which case no `process_view` hook runs at all.
- What does reading request.user in middleware cost on every request?`request.user` is a lazy object. Touching it loads the session to find the user's id and backend, then loads the user; with the default database session backend that is a session query plus a user query. A middleware that reads it unconditionally forces that work on requests that never needed a user, so restrict it to the paths or views that matter.
saying these in an interview costs you the question
- request.user is always present because Django attaches it to every request.
- Listing a middleware anywhere after SessionMiddleware is enough to read request.user.
- A middleware above AuthenticationMiddleware cannot read request.user even in process_view.
- The admin system checks will flag a middleware placed above AuthenticationMiddleware.
- Reading request.user in middleware is free because it was loaded by the session middleware.