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.