Cross-cutting concerns like timing, headers, tenancy and request IDs belong in middleware, not every view.
Middleware is a chain of wrappers around your views. Each one is a factory taking get_response (the next layer) and returning a callable that handles a request:
def timing_middleware(get_response):
# one-time setup when the server starts
def middleware(request):
# before the view
response = get_response(request)
# after the view
return response
return middlewareClasses work the same way with __init__(self, get_response) and __call__(self, request).
List middleware in settings.MIDDLEWARE. Order matters: requests pass through top to bottom, responses bottom to top, like layers of an onion. AuthenticationMiddleware needs SessionMiddleware above it, and SecurityMiddleware usually comes first.
A middleware can short-circuit by returning a response without calling get_response, for example a maintenance page or an IP block, in which case inner layers and the view never run.
Optional hooks on class-based middleware:
process_view(request, view_func, view_args, view_kwargs): after URL resolution, before the viewprocess_exception(request, exception): when the view raises; return a response to handle itprocess_template_response(request, response): modify aTemplateResponsebefore it renders
Built-in middleware handles sessions, auth, CSRF, messages, clickjacking headers, GZip, locale and (in Django 6.0) Content Security Policy.
Middleware runs on every request, so keep it fast. Mark middleware async_capable and provide an async path if you serve async views under ASGI, to avoid sync/async switching overhead.
Example
# core/middleware.py
import logging, time, uuid
log = logging.getLogger(__name__)
class RequestIdMiddleware:
def __init__(self, get_response):
self.get_response = get_response # called once at startup
def __call__(self, request):
request.id = request.headers.get("X-Request-ID") or uuid.uuid4().hex
start = time.perf_counter()
response = self.get_response(request) # inner middleware + view
response["X-Request-ID"] = request.id
log.info("%s %s %s %.1fms", request.id, request.path, response.status_code,
(time.perf_counter() - start) * 1000)
return response
def process_exception(self, request, exception):
log.exception("request %s failed", request.id)
return None # let Django's handling continue
# settings.py
MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware",
"core.middleware.RequestIdMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
# ... CommonMiddleware, CsrfViewMiddleware, AuthenticationMiddleware, ...
]Remember
- Middleware wraps get_response: code before runs on the request, after on the response
- MIDDLEWARE order: top-down for requests, bottom-up for responses
- Returning early short-circuits the inner layers and the view
- process_view / process_exception hooks for finer control
Watch out for
- Placing a middleware that uses request.user above AuthenticationMiddleware.