In Django middleware, when do process_view, process_exception and process_template_response run, and in what order across the MIDDLEWARE list?
answer
- three optional methods on the class
- around the view, not the chain
- one goes down, two come up
- first response wins
basics
~20 sprocess_view runs top-down after URL resolution, just before the view; process_exception runs bottom-up only when the view raises; process_template_response runs bottom-up when the response has render(). A process_view or process_exception response stops the remaining hooks of that kind.
solid answer
~40 sThey are optional methods on a class-based middleware, called from the innermost handler around the view. `process_view(request, view_func, view_args, view_kwargs)` runs after the URL is resolved and before the view, in `MIDDLEWARE` order; returning `None` continues, returning an `HttpResponse` skips the remaining `process_view` hooks and the view. `process_exception(request, exception)` runs only when the view, or rendering a `TemplateResponse`, raises; hooks run in **reverse** list order, the first one to return a response wins, and if none does Django's default handling produces the 404, 403 or 500. `process_template_response(request, response)` runs, also in reverse order, only when the response has a `render()` method, such as the `TemplateResponse` generic views return; it must return a renderable response and may change `template_name` or `context_data`, and Django renders after all such hooks. Exceptions raised in middleware never reach `process_exception`.
code
python · 25 linesimport os
from django.http import JsonResponse
class UpstreamTimeout(Exception):
pass
class ViewHooksMiddleware:
def __init__(self, get_response):
self.get_response = get_response
self.build_id = os.environ.get("BUILD_ID", "dev")
def __call__(self, request):
return self.get_response(request)
def process_exception(self, request, exception):
if isinstance(exception, UpstreamTimeout):
return JsonResponse({"detail": "upstream timeout"}, status=504)
return None # let other hooks or Django's default handling respond
def process_template_response(self, request, response):
response.context_data = {**(response.context_data or {}), "build_id": self.build_id}
return responsego deeper
Recall the three hook names, that they are optional methods on a middleware class, and that process_view runs just before the view.
Explain the order of each hook list, what returning a response does in each, and why process_exception never sees middleware exceptions.
Pick the right hook for error mapping or template context, predict which middleware's exception handler wins, and avoid hooks that fire for only some response types.
Weigh putting error translation in middleware against handling it in views or custom error handlers, considering how visible the behaviour is to other teams.
## Where the three hooks live Besides `__init__` and `__call__`, a class-based Django middleware may define three **optional hook methods**. Django checks for them with `hasattr` when it builds the chain and registers each one in its own list. They are not called from your `__call__`; they are called by the innermost part of the handler, the code that sits between the last middleware and the view: 1. resolve the URL and set `request.resolver_match`; 2. run the `process_view()` hooks; 3. call the view, sending any exception to the `process_exception()` hooks; 4. if the response can be rendered later, run the `process_template_response()` hooks, then render it. ## The order of each list While building the chain Django walks `MIDDLEWARE` from the bottom up. It **inserts** each `process_view` at the front of its list, and **appends** each `process_exception` and `process_template_response`. The effect: | Hook | Called when | Order across `MIDDLEWARE` | Return value | |---|---|---|---| | `process_view` | after URL resolution, just before the view | top-down | `None` to continue; an `HttpResponse` skips later `process_view` hooks and the view | | `process_exception` | the view, or `TemplateResponse.render()`, raised | bottom-up | `None` to pass; the first response returned wins and hooks above it are skipped | | `process_template_response` | the response has a `render()` method | bottom-up | must return a response with `render()`; `None` is an error | This mirrors the onion: `process_view` belongs to the way in, the other two to the way out. ## `process_view` - It receives the resolved **view function** and its arguments (without `request`), which lets middleware react to markers placed on views by decorators. Django's own `CsrfViewMiddleware` does its checking here, which is why `csrf_exempt` works as a view attribute. - It is the first point at which middleware can know which view will run. - Reading `request.POST` here stops the view from changing upload handlers, so general-purpose middleware should avoid it. ## `process_exception` - It is called with the exception object raised by the **view** or by rendering a `TemplateResponse`, including `Http404` and `PermissionDenied` before Django converts them. - It is **not** called for exceptions raised in any middleware's `__call__` or `process_view()`; those are converted to responses between layers. - Returning a response replaces the error: that response then gets `process_template_response` treatment if it is renderable, and passes out through every after-phase. Returning `None` lets the next hook up try, and finally Django's default handling produces the error response. - A typical use is translating a domain exception into a specific status, such as mapping an upstream timeout to a 504. ## `process_template_response` - It only fires for responses with a `render()` method, in practice `TemplateResponse` and subclasses, which the generic class-based views return. A plain `HttpResponse` returned by the `render()` shortcut is already rendered and skips these hooks. - It can change `response.template_name` or `response.context_data`, or return a new renderable response. - It must not return `None`; Django checks and raises an error naming the middleware. - Rendering happens once, after all these hooks, so they can cooperate on the context without re-rendering. ## Choosing the right place - Need the request before anything else happens: `__call__` before `get_response`. - Need to know the view: `process_view`. - Need to turn a view's exception into a response: `process_exception`. - Need to adjust a template's context before rendering: `process_template_response`. - Need every response, whatever produced it: `__call__` after `get_response`.
- Why does a process_template_response hook not fire for a view that returns render(request, ...)?The `render()` shortcut returns an `HttpResponse` whose content is already rendered, and it has no `render()` method. Django only runs `process_template_response()` hooks when the response has one, which is the case for `TemplateResponse`, the type generic class-based views return.
- Two middleware both define process_exception and both could handle an exception; which one wins?The one listed lower in `MIDDLEWARE`. `process_exception` hooks run bottom-up, and the first one that returns a response stops the loop, so hooks of middleware listed above it never see the exception. If all return `None`, Django's default handling produces the error response.
saying these in an interview costs you the question
- process_view hooks run in reverse MIDDLEWARE order.
- process_exception catches exceptions raised anywhere in the middleware chain.
- process_template_response runs for every response, including plain HttpResponse.
- process_template_response may return None to leave the response unchanged.
- All process_exception hooks run even after one returns a response.