skip to content

In Django middleware, when do process_view, process_exception and process_template_response run, and in what order across the MIDDLEWARE list?

level: middleimportance: should knowfreq 45%

answer

  1. three optional methods on the class
  2. around the view, not the chain
  3. one goes down, two come up
  4. first response wins

basics

~20 s

process_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 s

They 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 lines
python
import 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 response

go deeper

for a junior

Recall the three hook names, that they are optional methods on a middleware class, and that process_view runs just before the view.

for a middle

Explain the order of each hook list, what returning a response does in each, and why process_exception never sees middleware exceptions.

for a senior

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.

for a principal

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.