skip to content

In Django, how does returning a TemplateResponse differ from returning the HttpResponse built by render(), and why does its deferred rendering matter?

level: middleimportance: should knowfreq 34%

answer

  1. render now versus render later
  2. template_name and context_data stay editable
  3. process_template_response hook
  4. ContentNotRenderedError

basics

~10 s

render() produces an HttpResponse whose body is already rendered, while TemplateResponse keeps the template and context and renders only after the view returns. Decorators, middleware and tests can change or inspect them before rendering.

solid answer

~30 s

`render()` loads the template, renders it and returns an `HttpResponse` with finished bytes; nothing downstream can change the template or context. `TemplateResponse(request, template, context)` stores `template_name` and `context_data` and renders later: after the view returns, Django's handler runs any middleware `process_template_response()` hooks and then calls `response.render()`. That lets a decorator or middleware swap the template or add context, and lets a unit test call the view and assert on `context_data` without rendering. Reading `.content` before rendering raises `ContentNotRenderedError`, and `add_post_render_callback()` runs code once the bytes exist. Generic class-based views return `TemplateResponse` through `response_class`, which is why their responses behave this way.

code

python · 21 lines
python
from functools import wraps

from django.template.response import TemplateResponse

from .models import StockItem


def with_warehouse_banner(view):
    @wraps(view)
    def wrapper(request, *args, **kwargs):
        response = view(request, *args, **kwargs)
        if isinstance(response, TemplateResponse) and not response.is_rendered:
            response.context_data["banner"] = "Stocktake on Friday"
        return response
    return wrapper


@with_warehouse_banner
def stock_list(request):
    items = StockItem.objects.order_by("sku")
    return TemplateResponse(request, "stock/list.html", {"items": items})

go deeper

for a junior

Recall that render() returns a finished HttpResponse and TemplateResponse renders later, after the view returns.

for a middle

Explain where rendering happens: process_template_response hooks, then render(), then the normal response middleware, and what ContentNotRenderedError means.

for a senior

Use deferred rendering for reusable decorators and fast context-level tests, and know that rendering errors and pickling happen outside the view.

for a principal

Set conventions for when views return TemplateResponse so shared decorators and middleware can extend pages without forking templates.

## Two ways to answer with a template Most function views end with `render(request, "stock/list.html", context)`. That shortcut does everything immediately: it loads the template, renders it with the context and the request, and returns an `HttpResponse` whose `content` is final bytes. From that point the template and the context are gone. `django.template.response.TemplateResponse` is an `HttpResponse` subclass that postpones that work. Its constructor is `TemplateResponse(request, template, context=None, content_type=None, status=None, charset=None, using=None, headers=None)`, and it keeps: - `template_name`: a name, a list of candidate names, or a template object; - `context_data`: the dict you passed; - the request, for context processors. `SimpleTemplateResponse` is the same idea without a request. ## When rendering actually happens The request handler checks whether the view returned an object with a callable `render()`. If so, in `BaseHandler._get_response()` it: 1. runs every middleware's `process_template_response(request, response)` hook, in reverse middleware order, each of which may modify the response or return a new one; 2. calls `response.render()`, which renders once, sets `content`, and runs any post-render callbacks; 3. returns the rendered response to the ordinary response phase of the middleware stack. So by the time normal middleware sees it, the body is concrete; the window for changes is between the view's `return` and step 2. ## Why the delay is useful | Need | With `render()` | With `TemplateResponse` | |---|---|---| | A decorator adds a context key | impossible without re-rendering | set `response.context_data["key"]` | | Middleware swaps the template (e.g. a compact mobile variant) | impossible | set `response.template_name` | | Unit test checks what the view passed | parse HTML or use the test client | call the view, assert on `context_data` | | Run code after rendering (e.g. store the bytes) | put it after the call | `add_post_render_callback(fn)` | Generic class-based views get this for free: `TemplateResponseMixin.response_class` is `TemplateResponse`, so a `ListView` of stock lines returns an unrendered response that a decorator can still adjust. ## Rules and traps - **Reading content too early.** Accessing `response.content` or iterating the response before it is rendered raises `ContentNotRenderedError`. Code that post-processes the HTML must run after rendering: in a post-render callback, in `process_response`, or after calling `render()` yourself. - **Rendering is one-shot.** `render()` is a no-op once rendered. Changing `context_data` after that has no effect unless you set `content` again, which also marks it rendered. - **Assigning content marks it rendered.** Setting `response.content = ...` bypasses the template entirely. - **Pickling.** An unrendered response cannot be pickled; the cache framework and anything else that stores responses must render first, which is one reason `add_post_render_callback()` exists. - **Rendering errors surface late.** A missing template or a broken tag raises during `render()` in the handler, after the view has returned, so a `try/except` inside the view does not catch it. ## Testing with deferred rendering Because the view returns an object that still knows its template and context, a unit test can skip the HTML entirely: - build a request with `RequestFactory`, call the view function directly, and assert on `response.template_name` and `response.context_data`; - call `response.render()` yourself only when the markup matters; - through the full test client, the response is already rendered by the handler, and `response.context` and `response.templates` are filled in for both `render()` and `TemplateResponse` views. The direct-call style is faster and does not break when a designer changes the markup, which is why reusable apps often return `TemplateResponse` even from function views. ## When to choose which Use `render()` for simple function views where nothing downstream needs to intervene. Return `TemplateResponse` when you write reusable views, decorators or middleware that customise another view's output, or when you want cheap unit tests that assert on context rather than markup.

  • A decorator reads response.content from a TemplateResponse to inject HTML and crashes. Why, and how do you fix it?
    The response has not been rendered yet when the decorator runs, so `.content` raises `ContentNotRenderedError`. Either call `response.render()` first and then edit the bytes, or register `response.add_post_render_callback(fn)` so the edit runs right after the handler renders it.
  • Why can a unit test call a TemplateResponse view directly and inspect context without rendering HTML?
    The view returns an object that still holds `template_name` and `context_data`. A test built with `RequestFactory` can call the view and assert on `response.context_data["items"]` without rendering anything, which is faster and less brittle than matching markup.

render() hands over a sealed, printed letter; TemplateResponse hands over the draft and the address list, and anyone in the mailroom may still edit them before the printer runs once.

saying these in an interview costs you the question

  • TemplateResponse renders the template inside its constructor
  • render() returns a TemplateResponse, so both behave the same
  • context_data can still be changed after the response is rendered
  • Ordinary process_response middleware sees the unrendered TemplateResponse
  • A try/except in the view catches template rendering errors from TemplateResponse