Skip to main content

Template-Driven Responses

In this project, template-driven responses allow views to return a response object where the actual rendering of the template is deferred until the last possible moment. This approach provides a significant advantage: middleware or other components can intercept the response and modify the template or context before the final HTML is generated.

Core Response Classes​

The implementation centers around two primary classes in django/template/response.py:

  • SimpleTemplateResponse: A subclass of HttpResponse that stores the template and context data but does not require a request object.
  • TemplateResponse: A subclass of SimpleTemplateResponse that explicitly requires a request object, which is passed to the template engine during rendering.

SimpleTemplateResponse​

The SimpleTemplateResponse class initializes with a template name and a context dictionary. It overrides the standard HttpResponse behavior by setting an initial empty content and marking itself as unrendered.

# django/template/response.py

class SimpleTemplateResponse(HttpResponse):
rendering_attrs = ["template_name", "context_data", "_post_render_callbacks"]

def __init__(self, template, context=None, content_type=None, status=None, ...):
self.template_name = template
self.context_data = context
self._post_render_callbacks = []
super().__init__("", content_type, status, ...)
self._is_rendered = False

TemplateResponse​

TemplateResponse is the standard class used in most Django views. It ensures the request is available for context processors and template rendering.

# django/template/response.py

class TemplateResponse(SimpleTemplateResponse):
def __init__(self, request, template, context=None, ...):
super().__init__(template, context, ...)
self._request = request

The Rendering Lifecycle​

A template-driven response exists in two states: unrendered and rendered.

  1. Initialization: The response is created with _is_rendered = False.
  2. Deferred Access: If you attempt to access response.content or iterate over the response before it is rendered, it raises a ContentNotRenderedError.
  3. Rendering: Rendering is triggered by calling the render() method. This method:
    • Resolves the template using resolve_template().
    • Resolves the context using resolve_context().
    • Calls template.render(context, request).
    • Executes any registered post-render callbacks.
    • Sets self.content to the result, which flips the _is_rendered flag to True.
# django/template/response.py

def render(self):
if not self._is_rendered:
self.content = self.rendered_content
for post_callback in self._post_render_callbacks:
newretval = post_callback(self)
if newretval is not None:
# Callbacks can optionally return a new response object
self = newretval
return self

Post-Render Callbacks​

You can register functions to run immediately after the template has been rendered using add_post_render_callback(callback). This is useful for logging, instrumentation, or final content modifications.

def my_callback(response):
# Perform actions after rendering
pass

response = TemplateResponse(request, "my_template.html", context)
response.add_post_render_callback(my_callback)

If render() has already been called, the callback is invoked immediately upon registration.

Integration with Class-Based Views​

The TemplateResponseMixin in django/views/generic/base.py provides the standard bridge between Django's generic views and TemplateResponse. It defines render_to_response, which instantiates the response_class.

# django/views/generic/base.py

class TemplateResponseMixin:
template_name = None
response_class = TemplateResponse

def render_to_response(self, context, **response_kwargs):
return self.response_class(
request=self.request,
template=self.get_template_names(),
context=context,
**response_kwargs,
)

Important Constraints​

  • Single Rendering: A response can only be rendered once. Subsequent calls to render() are no-ops. Changes made to template_name or context_data after the first render() call will have no effect.
  • Pickling: You cannot pickle a TemplateResponse until it has been rendered. The __getstate__ method explicitly checks self._is_rendered and raises an error if it is False. Once rendered, the template-specific attributes (template_name, context_data) are removed from the pickled state to ensure only the final content is preserved.
  • Attribute Naming: The attributes are named template_name and context_data (rather than template and context) to avoid collisions with the internal API of the Django test Client.