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 ofHttpResponsethat stores the template and context data but does not require a request object.TemplateResponse: A subclass ofSimpleTemplateResponsethat explicitly requires arequestobject, 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.
- Initialization: The response is created with
_is_rendered = False. - Deferred Access: If you attempt to access
response.contentor iterate over the response before it is rendered, it raises aContentNotRenderedError. - 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.contentto the result, which flips the_is_renderedflag toTrue.
- Resolves the template using
# 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 totemplate_nameorcontext_dataafter the firstrender()call will have no effect. - Pickling: You cannot pickle a
TemplateResponseuntil it has been rendered. The__getstate__method explicitly checksself._is_renderedand raises an error if it isFalse. 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_nameandcontext_data(rather thantemplateandcontext) to avoid collisions with the internal API of the Django testClient.