Browse Source

Fixed #12815 -- Added TemplateResponse, a lazy-evaluated Response class. Thanks to Simon Willison for the original idea, and to Mikhail Korobov and Ivan Sagalaev for their assistance, including the draft patch from Mikhail.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@14850 bcc190cf-cafb-0310-a4f2-bffc1f526a37
Russell Keith-Magee 14 years ago
parent
commit
e0dcd7666a

+ 1 - 0
AUTHORS

@@ -273,6 +273,7 @@ answer newbie questions, and generally made Django that much better:
     Igor Kolar <ike@email.si>
     Igor Kolar <ike@email.si>
     Tomáš Kopeček <permonik@m6.cz>
     Tomáš Kopeček <permonik@m6.cz>
     Gasper Koren
     Gasper Koren
+    Mikhail Korobov <kmike84@googlemail.com>
     Martin Kosír <martin@martinkosir.net>
     Martin Kosír <martin@martinkosir.net>
     Arthur Koziel <http://arthurkoziel.com>
     Arthur Koziel <http://arthurkoziel.com>
     Meir Kriheli <http://mksoft.co.il/>
     Meir Kriheli <http://mksoft.co.il/>

+ 21 - 1
django/contrib/messages/tests/base.py

@@ -103,7 +103,7 @@ class BaseTest(TestCase):
         storage = self.get_storage()
         storage = self.get_storage()
         self.assertFalse(storage.added_new)
         self.assertFalse(storage.added_new)
         storage.add(constants.INFO, 'Test message 1')
         storage.add(constants.INFO, 'Test message 1')
-        self.assert_(storage.added_new)
+        self.assertTrue(storage.added_new)
         storage.add(constants.INFO, 'Test message 2', extra_tags='tag')
         storage.add(constants.INFO, 'Test message 2', extra_tags='tag')
         self.assertEqual(len(storage), 2)
         self.assertEqual(len(storage), 2)
 
 
@@ -180,6 +180,26 @@ class BaseTest(TestCase):
             for msg in data['messages']:
             for msg in data['messages']:
                 self.assertContains(response, msg)
                 self.assertContains(response, msg)
 
 
+    def test_with_template_response(self):
+        settings.MESSAGE_LEVEL = constants.DEBUG
+        data = {
+            'messages': ['Test message %d' % x for x in xrange(10)],
+        }
+        show_url = reverse('django.contrib.messages.tests.urls.show_template_response')
+        for level in self.levels.keys():
+            add_url = reverse('django.contrib.messages.tests.urls.add_template_response',
+                              args=(level,))
+            response = self.client.post(add_url, data, follow=True)
+            self.assertRedirects(response, show_url)
+            self.assertTrue('messages' in response.context)
+            for msg in data['messages']:
+                self.assertContains(response, msg)
+
+            # there shouldn't be any messages on second GET request
+            response = self.client.get(show_url)
+            for msg in data['messages']:
+                self.assertNotContains(response, msg)
+
     def test_multiple_posts(self):
     def test_multiple_posts(self):
         """
         """
         Tests that messages persist properly when multiple POSTs are made
         Tests that messages persist properly when multiple POSTs are made

+ 24 - 10
django/contrib/messages/tests/urls.py

@@ -2,9 +2,20 @@ from django.conf.urls.defaults import *
 from django.contrib import messages
 from django.contrib import messages
 from django.core.urlresolvers import reverse
 from django.core.urlresolvers import reverse
 from django.http import HttpResponseRedirect, HttpResponse
 from django.http import HttpResponseRedirect, HttpResponse
-from django.shortcuts import render_to_response
+from django.shortcuts import render_to_response, redirect
 from django.template import RequestContext, Template
 from django.template import RequestContext, Template
+from django.template.response import TemplateResponse
 
 
+TEMPLATE = """{% if messages %}
+<ul class="messages">
+    {% for message in messages %}
+    <li{% if message.tags %} class="{{ message.tags }}"{% endif %}>
+        {{ message }}
+    </li>
+    {% endfor %}
+</ul>
+{% endif %}
+"""
 
 
 def add(request, message_type):
 def add(request, message_type):
     # don't default to False here, because we want to test that it defaults
     # don't default to False here, because we want to test that it defaults
@@ -16,24 +27,27 @@ def add(request, message_type):
                                             fail_silently=fail_silently)
                                             fail_silently=fail_silently)
         else:
         else:
             getattr(messages, message_type)(request, msg)
             getattr(messages, message_type)(request, msg)
+
     show_url = reverse('django.contrib.messages.tests.urls.show')
     show_url = reverse('django.contrib.messages.tests.urls.show')
     return HttpResponseRedirect(show_url)
     return HttpResponseRedirect(show_url)
 
 
+def add_template_response(request, message_type):
+    for msg in request.POST.getlist('messages'):
+        getattr(messages, message_type)(request, msg)
+
+    show_url = reverse('django.contrib.messages.tests.urls.show_template_response')
+    return HttpResponseRedirect(show_url)
 
 
 def show(request):
 def show(request):
-    t = Template("""{% if messages %}
-<ul class="messages">
-    {% for message in messages %}
-    <li{% if message.tags %} class="{{ message.tags }}"{% endif %}>
-        {{ message }}
-    </li>
-    {% endfor %}
-</ul>
-{% endif %}""")
+    t = Template(TEMPLATE)
     return HttpResponse(t.render(RequestContext(request)))
     return HttpResponse(t.render(RequestContext(request)))
 
 
+def show_template_response(request):
+    return TemplateResponse(request, Template(TEMPLATE))
 
 
 urlpatterns = patterns('',
 urlpatterns = patterns('',
     ('^add/(debug|info|success|warning|error)/$', add),
     ('^add/(debug|info|success|warning|error)/$', add),
     ('^show/$', show),
     ('^show/$', show),
+    ('^template_response/add/(debug|info|success|warning|error)/$', add_template_response),
+    ('^template_response/show/$', show_template_response),
 )
 )

+ 12 - 3
django/core/handlers/base.py

@@ -21,6 +21,7 @@ class BaseHandler(object):
     def __init__(self):
     def __init__(self):
         self._request_middleware = self._view_middleware = self._response_middleware = self._exception_middleware = None
         self._request_middleware = self._view_middleware = self._response_middleware = self._exception_middleware = None
 
 
+
     def load_middleware(self):
     def load_middleware(self):
         """
         """
         Populate middleware lists from settings.MIDDLEWARE_CLASSES.
         Populate middleware lists from settings.MIDDLEWARE_CLASSES.
@@ -30,16 +31,16 @@ class BaseHandler(object):
         from django.conf import settings
         from django.conf import settings
         from django.core import exceptions
         from django.core import exceptions
         self._view_middleware = []
         self._view_middleware = []
+        self._template_response_middleware = []
         self._response_middleware = []
         self._response_middleware = []
         self._exception_middleware = []
         self._exception_middleware = []
 
 
         request_middleware = []
         request_middleware = []
         for middleware_path in settings.MIDDLEWARE_CLASSES:
         for middleware_path in settings.MIDDLEWARE_CLASSES:
             try:
             try:
-                dot = middleware_path.rindex('.')
+                mw_module, mw_classname = middleware_path.rsplit('.', 1)
             except ValueError:
             except ValueError:
                 raise exceptions.ImproperlyConfigured('%s isn\'t a middleware module' % middleware_path)
                 raise exceptions.ImproperlyConfigured('%s isn\'t a middleware module' % middleware_path)
-            mw_module, mw_classname = middleware_path[:dot], middleware_path[dot+1:]
             try:
             try:
                 mod = import_module(mw_module)
                 mod = import_module(mw_module)
             except ImportError, e:
             except ImportError, e:
@@ -48,7 +49,6 @@ class BaseHandler(object):
                 mw_class = getattr(mod, mw_classname)
                 mw_class = getattr(mod, mw_classname)
             except AttributeError:
             except AttributeError:
                 raise exceptions.ImproperlyConfigured('Middleware module "%s" does not define a "%s" class' % (mw_module, mw_classname))
                 raise exceptions.ImproperlyConfigured('Middleware module "%s" does not define a "%s" class' % (mw_module, mw_classname))
-
             try:
             try:
                 mw_instance = mw_class()
                 mw_instance = mw_class()
             except exceptions.MiddlewareNotUsed:
             except exceptions.MiddlewareNotUsed:
@@ -58,6 +58,8 @@ class BaseHandler(object):
                 request_middleware.append(mw_instance.process_request)
                 request_middleware.append(mw_instance.process_request)
             if hasattr(mw_instance, 'process_view'):
             if hasattr(mw_instance, 'process_view'):
                 self._view_middleware.append(mw_instance.process_view)
                 self._view_middleware.append(mw_instance.process_view)
+            if hasattr(mw_instance, 'process_template_response'):
+                self._template_response_middleware.insert(0, mw_instance.process_template_response)
             if hasattr(mw_instance, 'process_response'):
             if hasattr(mw_instance, 'process_response'):
                 self._response_middleware.insert(0, mw_instance.process_response)
                 self._response_middleware.insert(0, mw_instance.process_response)
             if hasattr(mw_instance, 'process_exception'):
             if hasattr(mw_instance, 'process_exception'):
@@ -164,6 +166,13 @@ class BaseHandler(object):
             urlresolvers.set_urlconf(None)
             urlresolvers.set_urlconf(None)
 
 
         try:
         try:
+            # If the response supports deferred rendering, apply template
+            # response middleware and the render the response
+            if hasattr(response, 'render') and callable(response.render):
+                for middleware_method in self._template_response_middleware:
+                    response = middleware_method(request, response)
+                response.render()
+
             # Apply response middleware, regardless of the response
             # Apply response middleware, regardless of the response
             for middleware_method in self._response_middleware:
             for middleware_method in self._response_middleware:
                 response = middleware_method(request, response)
                 response = middleware_method(request, response)

+ 108 - 0
django/template/response.py

@@ -0,0 +1,108 @@
+from django.http import HttpResponse
+from django.template import loader, Context, RequestContext
+
+class ContentNotRenderedError(Exception):
+    pass
+
+class SimpleTemplateResponse(HttpResponse):
+
+    def __init__(self, template, context=None, mimetype=None, status=None,
+            content_type=None):
+        # It would seem obvious to call these next two members 'template' and
+        # 'context', but those names are reserved as part of the test Client API.
+        # To avoid the name collision, we use
+        # tricky-to-debug problems
+        self.template_name = template
+        self.context_data = context
+
+        # _is_rendered tracks whether the template and context has been baked into
+        # a final response.
+        self._is_rendered = False
+
+        # content argument doesn't make sense here because it will be replaced
+        # with rendered template so we always pass empty string in order to
+        # prevent errors and provide shorter signature.
+        super(SimpleTemplateResponse, self).__init__('', mimetype, status,
+                                                     content_type)
+
+    def resolve_template(self, template):
+        "Accepts a template object, path-to-template or list of paths"
+        if isinstance(template, (list, tuple)):
+            return loader.select_template(template)
+        elif isinstance(template, basestring):
+            return loader.get_template(template)
+        else:
+            return template
+
+    def resolve_context(self, context):
+        """Convert context data into a full Context object
+        (assuming it isn't already a Context object).
+        """
+        if isinstance(context, Context):
+            return context
+        else:
+            return Context(context)
+
+    @property
+    def rendered_content(self):
+        """Returns the freshly rendered content for the template and context
+        described by the TemplateResponse.
+
+        This *does not* set the final content of the response. To set the
+        response content, you must either call render(), or set the
+        content explicitly using the value of this property.
+        """
+        template = self.resolve_template(self.template_name)
+        context = self.resolve_context(self.context_data)
+        content = template.render(context)
+        return content
+
+    def render(self):
+        """Render (thereby finalizing) the content of the response.
+
+        If the content has already been rendered, this is a no-op.
+
+        Returns the baked response instance.
+        """
+        if not self._is_rendered:
+            self._set_content(self.rendered_content)
+        return self
+
+    is_rendered = property(lambda self: self._is_rendered)
+
+    def __iter__(self):
+        if not self._is_rendered:
+            raise ContentNotRenderedError('The response content must be rendered before it can be iterated over.')
+        return super(SimpleTemplateResponse, self).__iter__()
+
+    def _get_content(self):
+        if not self._is_rendered:
+            raise ContentNotRenderedError('The response content must be rendered before it can be accessed.')
+        return super(SimpleTemplateResponse, self)._get_content()
+
+    def _set_content(self, value):
+        "Overrides rendered content, unless you later call render()"
+        super(SimpleTemplateResponse, self)._set_content(value)
+        self._is_rendered = True
+
+    content = property(_get_content, _set_content)
+
+
+class TemplateResponse(SimpleTemplateResponse):
+    def __init__(self, request, template, context=None, mimetype=None,
+            status=None, content_type=None):
+        # self.request gets over-written by django.test.client.Client - and
+        # unlike context_data and template_name the _request should not
+        # be considered part of the public API.
+        self._request = request
+        super(TemplateResponse, self).__init__(
+            template, context, mimetype, status, content_type)
+
+    def resolve_context(self, context):
+        """Convert context data into a full RequestContext object
+        (assuming it isn't already a Context object).
+        """
+        if isinstance(context, Context):
+            return context
+        else:
+            return RequestContext(self._request, context)

+ 11 - 40
django/views/generic/base.py

@@ -1,6 +1,7 @@
 from django import http
 from django import http
 from django.core.exceptions import ImproperlyConfigured
 from django.core.exceptions import ImproperlyConfigured
 from django.template import RequestContext, loader
 from django.template import RequestContext, loader
+from django.template.response import TemplateResponse
 from django.utils.functional import update_wrapper
 from django.utils.functional import update_wrapper
 from django.utils.log import getLogger
 from django.utils.log import getLogger
 from django.utils.decorators import classonlymethod
 from django.utils.decorators import classonlymethod
@@ -81,59 +82,29 @@ class TemplateResponseMixin(object):
     A mixin that can be used to render a template.
     A mixin that can be used to render a template.
     """
     """
     template_name = None
     template_name = None
+    response_class = TemplateResponse
 
 
-    def render_to_response(self, context):
+    def render_to_response(self, context, **response_kwargs):
         """
         """
         Returns a response with a template rendered with the given context.
         Returns a response with a template rendered with the given context.
         """
         """
-        return self.get_response(self.render_template(context))
-
-    def get_response(self, content, **httpresponse_kwargs):
-        """
-        Construct an `HttpResponse` object.
-        """
-        return http.HttpResponse(content, **httpresponse_kwargs)
-
-    def render_template(self, context):
-        """
-        Render the template with a given context.
-        """
-        context_instance = self.get_context_instance(context)
-        return self.get_template().render(context_instance)
-
-    def get_context_instance(self, context):
-        """
-        Get the template context instance. Must return a Context (or subclass)
-        instance.
-        """
-        return RequestContext(self.request, context)
-
-    def get_template(self):
-        """
-        Get a ``Template`` object for the given request.
-        """
-        names = self.get_template_names()
-        if not names:
-            raise ImproperlyConfigured(u"'%s' must provide template_name."
-                                       % self.__class__.__name__)
-        return self.load_template(names)
+        return self.response_class(
+            request = self.request,
+            template = self.get_template_names(),
+            context = context,
+            **response_kwargs
+        )
 
 
     def get_template_names(self):
     def get_template_names(self):
         """
         """
-        Return a list of template names to be used for the request. Must return
-        a list. May not be called if get_template is overridden.
+        Returns a list of template names to be used for the request. Must return
+        a list. May not be called if render_to_response is overridden.
         """
         """
         if self.template_name is None:
         if self.template_name is None:
             return []
             return []
         else:
         else:
             return [self.template_name]
             return [self.template_name]
 
 
-    def load_template(self, names):
-        """
-        Load a list of templates using the default template loader.
-        """
-        return loader.select_template(names)
-
 
 
 class TemplateView(TemplateResponseMixin, View):
 class TemplateView(TemplateResponseMixin, View):
     """
     """

+ 3 - 1
docs/index.txt

@@ -93,7 +93,9 @@ The view layer
       :doc:`View functions <topics/http/views>` |
       :doc:`View functions <topics/http/views>` |
       :doc:`Shortcuts <topics/http/shortcuts>`
       :doc:`Shortcuts <topics/http/shortcuts>`
 
 
-    * **Reference:**  :doc:`Request/response objects <ref/request-response>`
+    * **Reference:**
+      :doc:`Request/response objects <ref/request-response>` |
+      :doc:`TemplateResponse objects <ref/template-response>`
 
 
     * **File uploads:**
     * **File uploads:**
       :doc:`Overview <topics/http/file-uploads>` |
       :doc:`Overview <topics/http/file-uploads>` |

+ 14 - 32
docs/ref/class-based-views.txt

@@ -76,39 +76,25 @@ TemplateResponseMixin
 
 
         The path to the template to use when rendering the view.
         The path to the template to use when rendering the view.
 
 
-    .. method:: render_to_response(context)
+    .. attribute:: response_class
 
 
-        Returns a full composed HttpResponse instance, ready to be returned to
-        the user.
+        The response class to be returned by ``render_to_response`` method.
+        Default is
+        :class:`TemplateResponse <django.template.response.TemplateResponse>`.
+        The template and context of TemplateResponse instances can be
+        altered later (e.g. in
+        :ref:`template response middleware <template-response-middleware>`).
 
 
-        Calls :meth:`~TemplateResponseMixin.render_template()` to build the
-        content of the response, and
-        :meth:`~TemplateResponseMixin.get_response()` to construct the
-        :class:`~django.http.HttpResponse` object.
+        Create TemplateResponse subclass and pass set it to
+        ``template_response_class`` if you need custom template loading or
+        custom context object instantiation.
 
 
-    .. method:: get_response(content, **httpresponse_kwargs)
+    .. method:: render_to_response(context, **response_kwargs)
 
 
-        Constructs the :class:`~django.http.HttpResponse` object around the
-        given content. If any keyword arguments are provided, they will be
-        passed to the constructor of the :class:`~django.http.HttpResponse`
-        instance.
+        Returns a ``self.template_response_class`` instance.
 
 
-    .. method:: render_template(context)
-
-        Calls :meth:`~TemplateResponseMixin.get_context_instance()` to obtain
-        the :class:`Context` instance to use for rendering, and calls
-        :meth:`TemplateReponseMixin.get_template()` to load the template that
-        will be used to render the final content.
-
-    .. method:: get_context_instance(context)
-
-        Turns the data dictionary ``context`` into an actual context instance
-        that can be used for rendering.
-
-        By default, constructs a :class:`~django.template.RequestContext`
-        instance.
-
-    .. method:: get_template()
+        If any keyword arguments are provided, they will be
+        passed to the constructor of the response instance.
 
 
         Calls :meth:`~TemplateResponseMixin.get_template_names()` to obtain the
         Calls :meth:`~TemplateResponseMixin.get_template_names()` to obtain the
         list of template names that will be searched looking for an existent
         list of template names that will be searched looking for an existent
@@ -123,10 +109,6 @@ TemplateResponseMixin
         default implementation will return a list containing
         default implementation will return a list containing
         :attr:`TemplateResponseMixin.template_name` (if it is specified).
         :attr:`TemplateResponseMixin.template_name` (if it is specified).
 
 
-    .. method:: load_template(names)
-
-        Loads and returns a template found by searching the list of ``names``
-        for a match. Uses Django's default template loader.
 
 
 Single object mixins
 Single object mixins
 --------------------
 --------------------

+ 1 - 0
docs/ref/index.txt

@@ -16,6 +16,7 @@ API Reference
    middleware
    middleware
    models/index
    models/index
    request-response
    request-response
+   template-response
    settings
    settings
    signals
    signals
    templates/index
    templates/index

+ 211 - 0
docs/ref/template-response.txt

@@ -0,0 +1,211 @@
+===========================================
+TemplateResponse and SimpleTemplateResponse
+===========================================
+
+.. versionadded:: 1.3
+
+.. module:: django.template.response
+   :synopsis: Classes dealing with lazy-rendered HTTP responses.
+
+Standard HttpResponse objects are static structures. They are provided
+with a block of pre-rendered content at time of construction, and
+while that content can be modified, it isn't in a form that makes it
+easy to perform modifications.
+
+However, it can sometimes be beneficial to allow decorators or
+middleware to modify a response *after* it has been constructed by the
+view. For example, you may want to change the template that is used,
+or put additional data into the context.
+
+TemplateResponse provides a way to do just that. Unlike basic
+HttpResponse objects, TemplateResponse objects retain the details of
+the template and context that was provided by the view to compute the
+response. The final output of the response is not computed until
+it is needed, later in the response process.
+
+TemplateResponse objects
+========================
+
+.. class:: SimpleTemplateResponse()
+
+Attributes
+----------
+
+.. attribute:: SimpleTemplateResponse.template_name
+
+    The name of the template to be rendered. Accepts
+    :class:`django.template.Template` object, path to template or list
+    of paths.
+
+    Example: ``['foo.html', 'path/to/bar.html']``
+
+.. attribute:: SimpleTemplateResponse.context_data
+
+    The context data to be used when rendering the template. It can be
+    a dictionary or a context object.
+
+    Example: ``{'foo': 123}``
+
+.. attr:: SimpleTemplateResponse.rendered_content:
+
+    The current rendered value of the response content, using the current
+    template and context data.
+
+.. attr:: SimpleTemplateResponse.is_rendered:
+
+    A boolean indicating whether the response content has been rendered.
+
+
+Methods
+-------
+
+.. method:: SimpleTemplateResponse.__init__(template, context=None, mimetype=None, status=None, content_type=None)
+
+    Instantiates an
+    :class:`~django.template.response.SimpleTemplateResponse` object
+    with the given template, context, MIME type and HTTP status.
+
+    ``template`` is a full name of a template, or a sequence of
+    template names. :class:`django.template.Template` instances can
+    also be used.
+
+    ``context`` is a dictionary of values to add to the template
+    context. By default, this is an empty dictionary.
+    :class:`~django.template.Context` objects are also accepted as
+    ``context`` values.
+
+    ``status`` is the HTTP Status code for the response.
+
+    ``content_type`` is an alias for ``mimetype``. Historically, this
+    parameter was only called ``mimetype``, but since this is actually
+    the value included in the HTTP ``Content-Type`` header, it can
+    also include the character set encoding, which makes it more than
+    just a MIME type specification. If ``mimetype`` is specified (not
+    ``None``), that value is used. Otherwise, ``content_type`` is
+    used. If neither is given, the ``DEFAULT_CONTENT_TYPE`` setting is
+    used.
+
+
+.. method:: SimpleTemplateResponse.resolve_context(context)
+
+    Converts context data into a context instance that can be used for
+    rendering a template. Accepts a dictionary of context data or a
+    context object. Returns a :class:`~django.template.Context`
+    instance containing the provided data.
+
+    Override this method in order to customize context instantiation.
+
+.. method:: SimpleTemplateResponse.resolve_template(template)
+
+    Resolves the template instance to use for rendering. Accepts a
+    path of a template to use, or a sequence of template paths.
+    :class:`~django.template.Template` instances may also be provided.
+    Returns the :class:`~django.template.Template` instance to be
+    rendered.
+
+    Override this method in order to customize template rendering.
+
+.. method:: SimpleTemplateResponse.render():
+
+    Sets :attr:`response.content` to the result obtained by
+    :attr:`SimpleTemplateResponse.rendered_content`.
+
+    :meth:`~SimpleTemplateResponse.render()` will only have an effect
+    the first time it is called. On subsequent calls, it will return
+    the result obtained from the first call.
+
+
+.. class:: TemplateResponse()
+
+   TemplateResponse is a subclass of :class:`SimpleTemplateResponse
+   <django.template.response.SimpleTemplateResponse>` that uses
+   RequestContext instead of Context.
+
+.. method:: TemplateResponse.__init__(request, template, context=None, mimetype=None, status=None, content_type=None)
+
+   Instantiates an ``TemplateResponse`` object with the given
+   template, context, MIME type and HTTP status.
+
+   ``request`` is a HttpRequest instance.
+
+   ``template`` is a full name of a template to use or sequence of
+   template names. :class:`django.template.Template` instances are
+   also accepted.
+
+   ``context`` is a dictionary of values to add to the template
+   context. By default, this is an empty dictionary; context objects
+   are also accepted as ``context`` values.
+
+   ``status`` is the HTTP Status code for the response.
+
+   ``content_type`` is an alias for ``mimetype``. Historically, this
+   parameter was only called ``mimetype``, but since this is actually
+   the value included in the HTTP ``Content-Type`` header, it can also
+   include the character set encoding, which makes it more than just a
+   MIME type specification. If ``mimetype`` is specified (not
+   ``None``), that value is used. Otherwise, ``content_type`` is used.
+   If neither is given, the ``DEFAULT_CONTENT_TYPE`` setting is used.
+
+
+The rendering process
+=====================
+
+Before a :class:`TemplateResponse()` instance can be returned to the
+client, it must be rendered. The rendering process takes the
+intermediate representation of template and context, and turns it into
+the final byte stream that can be served to the client.
+
+There are three circumstances under which a TemplateResponse will be
+rendered:
+
+    * When the TemplateResponse instance is explicitly rendered, using
+      the :meth:`SimpleTemplateResponse.render()` method.
+
+    * When the content of the response is explicitly set by assigning
+      :attr:`response.content`.
+
+    * After passing through template response middleware, but before
+      passing through response middleware.
+
+A TemplateResponse can only be rendered once. The first call to
+:meth:`SimpleTemplateResponse.render()` sets the content of the
+response; subsequent rendering calls do not change the response
+content.
+
+However, when :attr:`response.content` is explicitly assigned, the
+change is always applied. If you want to force the content to be
+re-rendered, you can re-evaluate the rendered content, and assign
+the content of the response manually::
+
+    # Set up a baked TemplateResponse
+    >>> t = TemplateResponse(request, 'original.html', {})
+    >>> t.render()
+    >>> print t.content
+    Original content
+
+    # Rebaking doesn't change content
+    >>> t.template_name = 'new.html'
+    >>> t.render()
+    >>> print t.content
+    Original content
+
+    # Assigning content does change, no render() call required
+    >>> t.content = t.rendered_content
+    >>> print t.content
+    New content
+
+Using TemplateResponse and SimpleTemplateResponse
+=================================================
+
+A TemplateResponse object can be used anywhere that a normal
+HttpResponse can be used. It can also be used as an alternative to
+calling :method:`~django.shortcuts.render_to_response()`.
+
+For example, the following simple view returns a
+:class:`TemplateResponse()` with a simple template, and a context
+containing a queryset::
+
+    from django.template.response import TemplateResponse
+
+    def blog_index(request):
+        return TemplateResponse(request, 'entry_list.html', {'entries': Entry.objects.all()})

+ 21 - 0
docs/releases/1.3.txt

@@ -133,6 +133,27 @@ can also add special translator comments in the source.
 For more information, see :ref:`contextual-markers` and
 For more information, see :ref:`contextual-markers` and
 :ref:`translator-comments`.
 :ref:`translator-comments`.
 
 
+TemplateResponse
+~~~~~~~~~~~~~~~~
+
+It can sometimes be beneficial to allow decorators or middleware to
+modify a response *after* it has been constructed by the view. For
+example, you may want to change the template that is used, or put
+additional data into the context.
+
+However, you can't (easily) modify the content of a basic
+:class:`~django.http.HttpResponse` after it has been constructed. To
+overcome this limitation, Django 1.3 adds a new
+:class:`~django.template.TemplateResponse` class. Unlike basic
+:class:`~django.http.HttpResponse` objects,
+:class:`~django.template.TemplateResponse` objects retain the details
+of the template and context that was provided by the view to compute
+the response. The final output of the response is not computed until
+it is needed, later in the response process.
+
+For more details, see the :ref:`documentation </ref/template-response>`
+on the :class:`~django.template.TemplateResponse` class.
+
 Everything else
 Everything else
 ~~~~~~~~~~~~~~~
 ~~~~~~~~~~~~~~~
 
 

+ 35 - 1
docs/topics/http/middleware.txt

@@ -97,6 +97,39 @@ calling ANY other request, view or exception middleware, or the appropriate
 view; it'll return that :class:`~django.http.HttpResponse`. Response
 view; it'll return that :class:`~django.http.HttpResponse`. Response
 middleware is always called on every response.
 middleware is always called on every response.
 
 
+.. _template-response-middleware:
+
+``process_template_response``
+-----------------------------
+
+.. versionadded:: 1.3
+
+.. method:: process_template_response(self, request, response)
+
+``request`` is an :class:`~django.http.HttpRequest` object. ``response`` is the
+:class:`~django.template.response.SimpleTemplateResponse` subclass (e.g.
+:class:`~django.template.response.TemplateResponse`) object returned by a
+Django view.
+
+``process_template_response()`` must return an
+:class:`~django.template.response.SimpleTemplateResponse` (or it's subclass)
+object. It could alter the given ``response`` by changing
+``response.template_name`` and ``response.template_context``, or it could
+create and return a brand-new
+:class:`~django.template.response.SimpleTemplateResponse` (or it's subclass)
+instance.
+
+``process_template_response()`` will only be called if the response
+instance has a ``render()`` method, indicating that it is a
+:class:`~django.template.response.TemplateResponse`.
+
+You don't need to explicitly render responses -- responses will be
+automatically rendered once all template response middleware has been
+called.
+
+Middleware are run in reverse order during the response phase, which
+includes process_template_response.
+
 .. _response-middleware:
 .. _response-middleware:
 
 
 ``process_response``
 ``process_response``
@@ -120,6 +153,7 @@ an earlier middleware method returned an :class:`~django.http.HttpResponse`
 classes are applied in reverse order, from the bottom up. This means classes
 classes are applied in reverse order, from the bottom up. This means classes
 defined at the end of :setting:`MIDDLEWARE_CLASSES` will be run first.
 defined at the end of :setting:`MIDDLEWARE_CLASSES` will be run first.
 
 
+
 .. _exception-middleware:
 .. _exception-middleware:
 
 
 ``process_exception``
 ``process_exception``
@@ -137,7 +171,7 @@ Django calls ``process_exception()`` when a view raises an exception.
 the browser. Otherwise, default exception handling kicks in.
 the browser. Otherwise, default exception handling kicks in.
 
 
 Again, middleware are run in reverse order during the response phase, which
 Again, middleware are run in reverse order during the response phase, which
-includes ``process_exception``. If an exception middleware return a response,
+includes ``process_exception``. If an exception middleware returns a response,
 the middleware classes above that middleware will not be called at all.
 the middleware classes above that middleware will not be called at all.
 
 
 ``__init__``
 ``__init__``

+ 1 - 0
tests/regressiontests/generic_views/base.py

@@ -156,6 +156,7 @@ class TemplateViewTest(TestCase):
     rf = RequestFactory()
     rf = RequestFactory()
 
 
     def _assert_about(self, response):
     def _assert_about(self, response):
+        response.render()
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.content, '<h1>About</h1>')
         self.assertEqual(response.content, '<h1>About</h1>')
 
 

+ 191 - 122
tests/regressiontests/middleware_exceptions/tests.py

@@ -3,9 +3,10 @@ import sys
 from django.conf import settings
 from django.conf import settings
 from django.core.signals import got_request_exception
 from django.core.signals import got_request_exception
 from django.http import HttpResponse
 from django.http import HttpResponse
+from django.template.response import TemplateResponse
+from django.template import Template
 from django.test import TestCase
 from django.test import TestCase
 
 
-
 class TestException(Exception):
 class TestException(Exception):
     pass
     pass
 
 
@@ -16,6 +17,7 @@ class TestMiddleware(object):
         self.process_request_called = False
         self.process_request_called = False
         self.process_view_called = False
         self.process_view_called = False
         self.process_response_called = False
         self.process_response_called = False
+        self.process_template_response_called = False
         self.process_exception_called = False
         self.process_exception_called = False
 
 
     def process_request(self, request):
     def process_request(self, request):
@@ -24,6 +26,10 @@ class TestMiddleware(object):
     def process_view(self, request, view_func, view_args, view_kwargs):
     def process_view(self, request, view_func, view_args, view_kwargs):
         self.process_view_called = True
         self.process_view_called = True
 
 
+    def process_template_response(self, request, response):
+        self.process_template_response_called = True
+        return response
+
     def process_response(self, request, response):
     def process_response(self, request, response):
         self.process_response_called = True
         self.process_response_called = True
         return response
         return response
@@ -48,6 +54,11 @@ class ResponseMiddleware(TestMiddleware):
         super(ResponseMiddleware, self).process_response(request, response)
         super(ResponseMiddleware, self).process_response(request, response)
         return HttpResponse('Response Middleware')
         return HttpResponse('Response Middleware')
 
 
+class TemplateResponseMiddleware(TestMiddleware):
+    def process_template_response(self, request, response):
+        super(TemplateResponseMiddleware, self).process_template_response(request, response)
+        return TemplateResponse(request, Template('Template Response Middleware'))
+
 class ExceptionMiddleware(TestMiddleware):
 class ExceptionMiddleware(TestMiddleware):
     def process_exception(self, request, exception):
     def process_exception(self, request, exception):
         super(ExceptionMiddleware, self).process_exception(request, exception)
         super(ExceptionMiddleware, self).process_exception(request, exception)
@@ -66,6 +77,11 @@ class BadViewMiddleware(TestMiddleware):
         super(BadViewMiddleware, self).process_view(request, view_func, view_args, view_kwargs)
         super(BadViewMiddleware, self).process_view(request, view_func, view_args, view_kwargs)
         raise TestException('Test View Exception')
         raise TestException('Test View Exception')
 
 
+class BadTemplateResponseMiddleware(TestMiddleware):
+    def process_template_response(self, request, response):
+        super(BadTemplateResponseMiddleware, self).process_template_response(request, response)
+        raise TestException('Test Template Response Exception')
+
 class BadResponseMiddleware(TestMiddleware):
 class BadResponseMiddleware(TestMiddleware):
     def process_response(self, request, response):
     def process_response(self, request, response):
         super(BadResponseMiddleware, self).process_response(request, response)
         super(BadResponseMiddleware, self).process_response(request, response)
@@ -93,6 +109,7 @@ class BaseMiddlewareExceptionTest(TestCase):
     def _add_middleware(self, middleware):
     def _add_middleware(self, middleware):
         self.client.handler._request_middleware.insert(0, middleware.process_request)
         self.client.handler._request_middleware.insert(0, middleware.process_request)
         self.client.handler._view_middleware.insert(0, middleware.process_view)
         self.client.handler._view_middleware.insert(0, middleware.process_view)
+        self.client.handler._template_response_middleware.append(middleware.process_template_response)
         self.client.handler._response_middleware.append(middleware.process_response)
         self.client.handler._response_middleware.append(middleware.process_response)
         self.client.handler._exception_middleware.append(middleware.process_exception)
         self.client.handler._exception_middleware.append(middleware.process_exception)
 
 
@@ -113,9 +130,10 @@ class BaseMiddlewareExceptionTest(TestCase):
             exception, value, tb = self.exceptions[i]
             exception, value, tb = self.exceptions[i]
             self.assertEquals(value.args, (error, ))
             self.assertEquals(value.args, (error, ))
 
 
-    def assert_middleware_usage(self, middleware, request, view, response, exception):
+    def assert_middleware_usage(self, middleware, request, view, template_response, response, exception):
         self.assertEqual(middleware.process_request_called, request)
         self.assertEqual(middleware.process_request_called, request)
         self.assertEqual(middleware.process_view_called, view)
         self.assertEqual(middleware.process_view_called, view)
+        self.assertEqual(middleware.process_template_response_called, template_response)
         self.assertEqual(middleware.process_response_called, response)
         self.assertEqual(middleware.process_response_called, response)
         self.assertEqual(middleware.process_exception_called, exception)
         self.assertEqual(middleware.process_exception_called, exception)
 
 
@@ -132,9 +150,9 @@ class MiddlewareTests(BaseMiddlewareExceptionTest):
         self.assert_exceptions_handled('/middleware_exceptions/view/', [])
         self.assert_exceptions_handled('/middleware_exceptions/view/', [])
 
 
         # Check that the right middleware methods have been invoked
         # Check that the right middleware methods have been invoked
-        self.assert_middleware_usage(pre_middleware,  True,  False, True,  False)
-        self.assert_middleware_usage(middleware,      True,  False, True,  False)
-        self.assert_middleware_usage(post_middleware, False, False, True,  False)
+        self.assert_middleware_usage(pre_middleware,  True,  False, False, True, False)
+        self.assert_middleware_usage(middleware,      True,  False, False, True, False)
+        self.assert_middleware_usage(post_middleware, False, False, False, True, False)
 
 
     def test_process_view_middleware(self):
     def test_process_view_middleware(self):
         pre_middleware = TestMiddleware()
         pre_middleware = TestMiddleware()
@@ -146,9 +164,9 @@ class MiddlewareTests(BaseMiddlewareExceptionTest):
         self.assert_exceptions_handled('/middleware_exceptions/view/', [])
         self.assert_exceptions_handled('/middleware_exceptions/view/', [])
 
 
         # Check that the right middleware methods have been invoked
         # Check that the right middleware methods have been invoked
-        self.assert_middleware_usage(pre_middleware,  True,  True,  True, False)
-        self.assert_middleware_usage(middleware,      True,  True,  True, False)
-        self.assert_middleware_usage(post_middleware, True,  False, True, False)
+        self.assert_middleware_usage(pre_middleware,  True, True,  False, True, False)
+        self.assert_middleware_usage(middleware,      True, True,  False, True, False)
+        self.assert_middleware_usage(post_middleware, True, False, False, True, False)
 
 
     def test_process_response_middleware(self):
     def test_process_response_middleware(self):
         pre_middleware = TestMiddleware()
         pre_middleware = TestMiddleware()
@@ -160,9 +178,23 @@ class MiddlewareTests(BaseMiddlewareExceptionTest):
         self.assert_exceptions_handled('/middleware_exceptions/view/', [])
         self.assert_exceptions_handled('/middleware_exceptions/view/', [])
 
 
         # Check that the right middleware methods have been invoked
         # Check that the right middleware methods have been invoked
-        self.assert_middleware_usage(pre_middleware,  True,  True,  True,  False)
-        self.assert_middleware_usage(middleware,      True,  True,  True,  False)
-        self.assert_middleware_usage(post_middleware, True,  True,  True,  False)
+        self.assert_middleware_usage(pre_middleware,  True, True, False, True, False)
+        self.assert_middleware_usage(middleware,      True, True, False, True, False)
+        self.assert_middleware_usage(post_middleware, True, True, False, True, False)
+
+    def test_process_template_response_middleware(self):
+        pre_middleware = TestMiddleware()
+        middleware = TemplateResponseMiddleware()
+        post_middleware = TestMiddleware()
+        self._add_middleware(post_middleware)
+        self._add_middleware(middleware)
+        self._add_middleware(pre_middleware)
+        self.assert_exceptions_handled('/middleware_exceptions/template_response/', [])
+
+        # Check that the right middleware methods have been invoked
+        self.assert_middleware_usage(pre_middleware,  True, True, True, True, False)
+        self.assert_middleware_usage(middleware,      True, True, True, True, False)
+        self.assert_middleware_usage(post_middleware, True, True, True, True, False)
 
 
     def test_process_exception_middleware(self):
     def test_process_exception_middleware(self):
         pre_middleware = TestMiddleware()
         pre_middleware = TestMiddleware()
@@ -174,9 +206,9 @@ class MiddlewareTests(BaseMiddlewareExceptionTest):
         self.assert_exceptions_handled('/middleware_exceptions/view/', [])
         self.assert_exceptions_handled('/middleware_exceptions/view/', [])
 
 
         # Check that the right middleware methods have been invoked
         # Check that the right middleware methods have been invoked
-        self.assert_middleware_usage(pre_middleware,  True,  True,  True, False)
-        self.assert_middleware_usage(middleware,      True,  True,  True, False)
-        self.assert_middleware_usage(post_middleware, True,  True,  True, False)
+        self.assert_middleware_usage(pre_middleware,  True, True, False, True, False)
+        self.assert_middleware_usage(middleware,      True, True, False, True, False)
+        self.assert_middleware_usage(post_middleware, True, True, False, True, False)
 
 
     def test_process_request_middleware_not_found(self):
     def test_process_request_middleware_not_found(self):
         pre_middleware = TestMiddleware()
         pre_middleware = TestMiddleware()
@@ -188,9 +220,9 @@ class MiddlewareTests(BaseMiddlewareExceptionTest):
         self.assert_exceptions_handled('/middleware_exceptions/not_found/', [])
         self.assert_exceptions_handled('/middleware_exceptions/not_found/', [])
 
 
         # Check that the right middleware methods have been invoked
         # Check that the right middleware methods have been invoked
-        self.assert_middleware_usage(pre_middleware,  True,  False, True,  False)
-        self.assert_middleware_usage(middleware,      True,  False, True,  False)
-        self.assert_middleware_usage(post_middleware, False, False, True,  False)
+        self.assert_middleware_usage(pre_middleware,  True,  False, False, True, False)
+        self.assert_middleware_usage(middleware,      True,  False, False, True, False)
+        self.assert_middleware_usage(post_middleware, False, False, False, True, False)
 
 
     def test_process_view_middleware_not_found(self):
     def test_process_view_middleware_not_found(self):
         pre_middleware = TestMiddleware()
         pre_middleware = TestMiddleware()
@@ -202,9 +234,23 @@ class MiddlewareTests(BaseMiddlewareExceptionTest):
         self.assert_exceptions_handled('/middleware_exceptions/not_found/', [])
         self.assert_exceptions_handled('/middleware_exceptions/not_found/', [])
 
 
         # Check that the right middleware methods have been invoked
         # Check that the right middleware methods have been invoked
-        self.assert_middleware_usage(pre_middleware,  True,  True,  True, False)
-        self.assert_middleware_usage(middleware,      True,  True,  True, False)
-        self.assert_middleware_usage(post_middleware, True,  False, True, False)
+        self.assert_middleware_usage(pre_middleware,  True, True,  False, True, False)
+        self.assert_middleware_usage(middleware,      True, True,  False, True, False)
+        self.assert_middleware_usage(post_middleware, True, False, False, True, False)
+
+    def test_process_template_response_middleware_not_found(self):
+        pre_middleware = TestMiddleware()
+        middleware = TemplateResponseMiddleware()
+        post_middleware = TestMiddleware()
+        self._add_middleware(post_middleware)
+        self._add_middleware(middleware)
+        self._add_middleware(pre_middleware)
+        self.assert_exceptions_handled('/middleware_exceptions/not_found/', [])
+
+        # Check that the right middleware methods have been invoked
+        self.assert_middleware_usage(pre_middleware,  True, True, False, True, True)
+        self.assert_middleware_usage(middleware,      True, True, False, True, True)
+        self.assert_middleware_usage(post_middleware, True, True, False, True, True)
 
 
     def test_process_response_middleware_not_found(self):
     def test_process_response_middleware_not_found(self):
         pre_middleware = TestMiddleware()
         pre_middleware = TestMiddleware()
@@ -216,9 +262,9 @@ class MiddlewareTests(BaseMiddlewareExceptionTest):
         self.assert_exceptions_handled('/middleware_exceptions/not_found/', [])
         self.assert_exceptions_handled('/middleware_exceptions/not_found/', [])
 
 
         # Check that the right middleware methods have been invoked
         # Check that the right middleware methods have been invoked
-        self.assert_middleware_usage(pre_middleware,  True,  True,  True,  True)
-        self.assert_middleware_usage(middleware,      True,  True,  True,  True)
-        self.assert_middleware_usage(post_middleware, True,  True,  True,  True)
+        self.assert_middleware_usage(pre_middleware,  True, True, False, True, True)
+        self.assert_middleware_usage(middleware,      True, True, False, True, True)
+        self.assert_middleware_usage(post_middleware, True, True, False, True, True)
 
 
     def test_process_exception_middleware_not_found(self):
     def test_process_exception_middleware_not_found(self):
         pre_middleware = TestMiddleware()
         pre_middleware = TestMiddleware()
@@ -230,9 +276,9 @@ class MiddlewareTests(BaseMiddlewareExceptionTest):
         self.assert_exceptions_handled('/middleware_exceptions/not_found/', [])
         self.assert_exceptions_handled('/middleware_exceptions/not_found/', [])
 
 
         # Check that the right middleware methods have been invoked
         # Check that the right middleware methods have been invoked
-        self.assert_middleware_usage(pre_middleware,  True,  True,  True, False)
-        self.assert_middleware_usage(middleware,      True,  True,  True,  True)
-        self.assert_middleware_usage(post_middleware, True,  True,  True,  True)
+        self.assert_middleware_usage(pre_middleware,  True, True, False, True, False)
+        self.assert_middleware_usage(middleware,      True, True, False, True, True)
+        self.assert_middleware_usage(post_middleware, True, True, False, True, True)
 
 
     def test_process_request_middleware_exception(self):
     def test_process_request_middleware_exception(self):
         pre_middleware = TestMiddleware()
         pre_middleware = TestMiddleware()
@@ -244,9 +290,9 @@ class MiddlewareTests(BaseMiddlewareExceptionTest):
         self.assert_exceptions_handled('/middleware_exceptions/error/', [])
         self.assert_exceptions_handled('/middleware_exceptions/error/', [])
 
 
         # Check that the right middleware methods have been invoked
         # Check that the right middleware methods have been invoked
-        self.assert_middleware_usage(pre_middleware,  True,  False, True,  False)
-        self.assert_middleware_usage(middleware,  True,  False, True,  False)
-        self.assert_middleware_usage(post_middleware, False, False, True,  False)
+        self.assert_middleware_usage(pre_middleware,  True,  False, False, True, False)
+        self.assert_middleware_usage(middleware,      True,  False, False, True, False)
+        self.assert_middleware_usage(post_middleware, False, False, False, True, False)
 
 
     def test_process_view_middleware_exception(self):
     def test_process_view_middleware_exception(self):
         pre_middleware = TestMiddleware()
         pre_middleware = TestMiddleware()
@@ -258,9 +304,9 @@ class MiddlewareTests(BaseMiddlewareExceptionTest):
         self.assert_exceptions_handled('/middleware_exceptions/error/', [])
         self.assert_exceptions_handled('/middleware_exceptions/error/', [])
 
 
         # Check that the right middleware methods have been invoked
         # Check that the right middleware methods have been invoked
-        self.assert_middleware_usage(pre_middleware,  True,  True,  True, False)
-        self.assert_middleware_usage(middleware,      True,  True,  True, False)
-        self.assert_middleware_usage(post_middleware, True,  False, True, False)
+        self.assert_middleware_usage(pre_middleware,  True, True,  False, True, False)
+        self.assert_middleware_usage(middleware,      True, True,  False, True, False)
+        self.assert_middleware_usage(post_middleware, True, False, False, True, False)
 
 
     def test_process_response_middleware_exception(self):
     def test_process_response_middleware_exception(self):
         pre_middleware = TestMiddleware()
         pre_middleware = TestMiddleware()
@@ -272,9 +318,9 @@ class MiddlewareTests(BaseMiddlewareExceptionTest):
         self.assert_exceptions_handled('/middleware_exceptions/error/', ['Error in view'], Exception())
         self.assert_exceptions_handled('/middleware_exceptions/error/', ['Error in view'], Exception())
 
 
         # Check that the right middleware methods have been invoked
         # Check that the right middleware methods have been invoked
-        self.assert_middleware_usage(pre_middleware,  True,  True,  True,  True)
-        self.assert_middleware_usage(middleware,      True,  True,  True,  True)
-        self.assert_middleware_usage(post_middleware, True,  True,  True,  True)
+        self.assert_middleware_usage(pre_middleware,  True, True, False, True, True)
+        self.assert_middleware_usage(middleware,      True, True, False, True, True)
+        self.assert_middleware_usage(post_middleware, True, True, False, True, True)
 
 
     def test_process_exception_middleware_exception(self):
     def test_process_exception_middleware_exception(self):
         pre_middleware = TestMiddleware()
         pre_middleware = TestMiddleware()
@@ -286,9 +332,9 @@ class MiddlewareTests(BaseMiddlewareExceptionTest):
         self.assert_exceptions_handled('/middleware_exceptions/error/', [])
         self.assert_exceptions_handled('/middleware_exceptions/error/', [])
 
 
         # Check that the right middleware methods have been invoked
         # Check that the right middleware methods have been invoked
-        self.assert_middleware_usage(pre_middleware,  True,  True,  True, False)
-        self.assert_middleware_usage(middleware,      True,  True,  True,  True)
-        self.assert_middleware_usage(post_middleware, True,  True,  True,  True)
+        self.assert_middleware_usage(pre_middleware,  True, True, False, True, False)
+        self.assert_middleware_usage(middleware,      True, True, False, True, True)
+        self.assert_middleware_usage(post_middleware, True, True, False, True, True)
 
 
     def test_process_request_middleware_null_view(self):
     def test_process_request_middleware_null_view(self):
         pre_middleware = TestMiddleware()
         pre_middleware = TestMiddleware()
@@ -300,9 +346,9 @@ class MiddlewareTests(BaseMiddlewareExceptionTest):
         self.assert_exceptions_handled('/middleware_exceptions/null_view/', [])
         self.assert_exceptions_handled('/middleware_exceptions/null_view/', [])
 
 
         # Check that the right middleware methods have been invoked
         # Check that the right middleware methods have been invoked
-        self.assert_middleware_usage(pre_middleware,  True,  False, True,  False)
-        self.assert_middleware_usage(middleware,      True,  False, True,  False)
-        self.assert_middleware_usage(post_middleware, False, False, True,  False)
+        self.assert_middleware_usage(pre_middleware,  True,  False, False, True, False)
+        self.assert_middleware_usage(middleware,      True,  False, False, True, False)
+        self.assert_middleware_usage(post_middleware, False, False, False, True, False)
 
 
     def test_process_view_middleware_null_view(self):
     def test_process_view_middleware_null_view(self):
         pre_middleware = TestMiddleware()
         pre_middleware = TestMiddleware()
@@ -314,9 +360,9 @@ class MiddlewareTests(BaseMiddlewareExceptionTest):
         self.assert_exceptions_handled('/middleware_exceptions/null_view/', [])
         self.assert_exceptions_handled('/middleware_exceptions/null_view/', [])
 
 
         # Check that the right middleware methods have been invoked
         # Check that the right middleware methods have been invoked
-        self.assert_middleware_usage(pre_middleware,  True,  True,  True, False)
-        self.assert_middleware_usage(middleware,      True,  True,  True, False)
-        self.assert_middleware_usage(post_middleware, True,  False, True, False)
+        self.assert_middleware_usage(pre_middleware,  True, True,  False, True, False)
+        self.assert_middleware_usage(middleware,      True, True,  False, True, False)
+        self.assert_middleware_usage(post_middleware, True, False, False, True, False)
 
 
     def test_process_response_middleware_null_view(self):
     def test_process_response_middleware_null_view(self):
         pre_middleware = TestMiddleware()
         pre_middleware = TestMiddleware()
@@ -331,9 +377,9 @@ class MiddlewareTests(BaseMiddlewareExceptionTest):
             ValueError())
             ValueError())
 
 
         # Check that the right middleware methods have been invoked
         # Check that the right middleware methods have been invoked
-        self.assert_middleware_usage(pre_middleware,  True,  True,  True,  False)
-        self.assert_middleware_usage(middleware,      True,  True,  True,  False)
-        self.assert_middleware_usage(post_middleware, True,  True,  True,  False)
+        self.assert_middleware_usage(pre_middleware,  True, True, False, True, False)
+        self.assert_middleware_usage(middleware,      True, True, False, True, False)
+        self.assert_middleware_usage(post_middleware, True, True, False, True, False)
 
 
     def test_process_exception_middleware_null_view(self):
     def test_process_exception_middleware_null_view(self):
         pre_middleware = TestMiddleware()
         pre_middleware = TestMiddleware()
@@ -348,9 +394,9 @@ class MiddlewareTests(BaseMiddlewareExceptionTest):
             ValueError())
             ValueError())
 
 
         # Check that the right middleware methods have been invoked
         # Check that the right middleware methods have been invoked
-        self.assert_middleware_usage(pre_middleware,  True,  True,  True, False)
-        self.assert_middleware_usage(middleware,      True,  True,  True, False)
-        self.assert_middleware_usage(post_middleware, True,  True,  True, False)
+        self.assert_middleware_usage(pre_middleware,  True, True, False, True, False)
+        self.assert_middleware_usage(middleware,      True, True, False, True, False)
+        self.assert_middleware_usage(post_middleware, True, True, False, True, False)
 
 
     def test_process_request_middleware_permission_denied(self):
     def test_process_request_middleware_permission_denied(self):
         pre_middleware = TestMiddleware()
         pre_middleware = TestMiddleware()
@@ -362,9 +408,9 @@ class MiddlewareTests(BaseMiddlewareExceptionTest):
         self.assert_exceptions_handled('/middleware_exceptions/permission_denied/', [])
         self.assert_exceptions_handled('/middleware_exceptions/permission_denied/', [])
 
 
         # Check that the right middleware methods have been invoked
         # Check that the right middleware methods have been invoked
-        self.assert_middleware_usage(pre_middleware,  True,  False, True,  False)
-        self.assert_middleware_usage(middleware,      True,  False, True,  False)
-        self.assert_middleware_usage(post_middleware, False, False, True,  False)
+        self.assert_middleware_usage(pre_middleware,  True,  False, False, True, False)
+        self.assert_middleware_usage(middleware,      True,  False, False, True, False)
+        self.assert_middleware_usage(post_middleware, False, False, False, True, False)
 
 
     def test_process_view_middleware_permission_denied(self):
     def test_process_view_middleware_permission_denied(self):
         pre_middleware = TestMiddleware()
         pre_middleware = TestMiddleware()
@@ -376,9 +422,9 @@ class MiddlewareTests(BaseMiddlewareExceptionTest):
         self.assert_exceptions_handled('/middleware_exceptions/permission_denied/', [])
         self.assert_exceptions_handled('/middleware_exceptions/permission_denied/', [])
 
 
         # Check that the right middleware methods have been invoked
         # Check that the right middleware methods have been invoked
-        self.assert_middleware_usage(pre_middleware,  True,  True,  True, False)
-        self.assert_middleware_usage(middleware,      True,  True,  True, False)
-        self.assert_middleware_usage(post_middleware, True,  False, True, False)
+        self.assert_middleware_usage(pre_middleware,  True, True,  False, True, False)
+        self.assert_middleware_usage(middleware,      True, True,  False, True, False)
+        self.assert_middleware_usage(post_middleware, True, False, False, True, False)
 
 
     def test_process_response_middleware_permission_denied(self):
     def test_process_response_middleware_permission_denied(self):
         pre_middleware = TestMiddleware()
         pre_middleware = TestMiddleware()
@@ -390,9 +436,9 @@ class MiddlewareTests(BaseMiddlewareExceptionTest):
         self.assert_exceptions_handled('/middleware_exceptions/permission_denied/', [])
         self.assert_exceptions_handled('/middleware_exceptions/permission_denied/', [])
 
 
         # Check that the right middleware methods have been invoked
         # Check that the right middleware methods have been invoked
-        self.assert_middleware_usage(pre_middleware,  True,  True,  True,  True)
-        self.assert_middleware_usage(middleware,      True,  True,  True,  True)
-        self.assert_middleware_usage(post_middleware, True,  True,  True,  True)
+        self.assert_middleware_usage(pre_middleware,  True, True, False, True, True)
+        self.assert_middleware_usage(middleware,      True, True, False, True, True)
+        self.assert_middleware_usage(post_middleware, True, True, False, True, True)
 
 
     def test_process_exception_middleware_permission_denied(self):
     def test_process_exception_middleware_permission_denied(self):
         pre_middleware = TestMiddleware()
         pre_middleware = TestMiddleware()
@@ -404,9 +450,18 @@ class MiddlewareTests(BaseMiddlewareExceptionTest):
         self.assert_exceptions_handled('/middleware_exceptions/permission_denied/', [])
         self.assert_exceptions_handled('/middleware_exceptions/permission_denied/', [])
 
 
         # Check that the right middleware methods have been invoked
         # Check that the right middleware methods have been invoked
-        self.assert_middleware_usage(pre_middleware,  True,  True,  True, False)
-        self.assert_middleware_usage(middleware,  True,  True,  True,  True)
-        self.assert_middleware_usage(post_middleware, True,  True,  True,  True)
+        self.assert_middleware_usage(pre_middleware,  True, True, False, True, False)
+        self.assert_middleware_usage(middleware,      True, True, False, True, True)
+        self.assert_middleware_usage(post_middleware, True, True, False, True, True)
+
+    def test_process_template_response_error(self):
+        middleware = TestMiddleware()
+        self._add_middleware(middleware)
+        self.assert_exceptions_handled('/middleware_exceptions/template_response_error/', [])
+
+        # Check that the right middleware methods have been invoked
+        self.assert_middleware_usage(middleware, True, True, True, True, False)
+
 
 
 class BadMiddlewareTests(BaseMiddlewareExceptionTest):
 class BadMiddlewareTests(BaseMiddlewareExceptionTest):
 
 
@@ -420,9 +475,9 @@ class BadMiddlewareTests(BaseMiddlewareExceptionTest):
         self.assert_exceptions_handled('/middleware_exceptions/view/', ['Test Request Exception'])
         self.assert_exceptions_handled('/middleware_exceptions/view/', ['Test Request Exception'])
 
 
         # Check that the right middleware methods have been invoked
         # Check that the right middleware methods have been invoked
-        self.assert_middleware_usage(pre_middleware,  True,  False, True,  False)
-        self.assert_middleware_usage(bad_middleware,  True,  False, True,  False)
-        self.assert_middleware_usage(post_middleware, False, False, True,  False)
+        self.assert_middleware_usage(pre_middleware,  True,  False, False, True, False)
+        self.assert_middleware_usage(bad_middleware,  True,  False, False, True, False)
+        self.assert_middleware_usage(post_middleware, False, False, False, True, False)
 
 
     def test_process_view_bad_middleware(self):
     def test_process_view_bad_middleware(self):
         pre_middleware = TestMiddleware()
         pre_middleware = TestMiddleware()
@@ -434,9 +489,23 @@ class BadMiddlewareTests(BaseMiddlewareExceptionTest):
         self.assert_exceptions_handled('/middleware_exceptions/view/', ['Test View Exception'])
         self.assert_exceptions_handled('/middleware_exceptions/view/', ['Test View Exception'])
 
 
         # Check that the right middleware methods have been invoked
         # Check that the right middleware methods have been invoked
-        self.assert_middleware_usage(pre_middleware,  True,  True,  True, False)
-        self.assert_middleware_usage(bad_middleware,  True,  True,  True, False)
-        self.assert_middleware_usage(post_middleware, True,  False, True, False)
+        self.assert_middleware_usage(pre_middleware,  True, True,  False, True, False)
+        self.assert_middleware_usage(bad_middleware,  True, True,  False, True, False)
+        self.assert_middleware_usage(post_middleware, True, False, False, True, False)
+
+    def test_process_template_response_bad_middleware(self):
+        pre_middleware = TestMiddleware()
+        bad_middleware = BadTemplateResponseMiddleware()
+        post_middleware = TestMiddleware()
+        self._add_middleware(post_middleware)
+        self._add_middleware(bad_middleware)
+        self._add_middleware(pre_middleware)
+        self.assert_exceptions_handled('/middleware_exceptions/template_response/', ['Test Template Response Exception'])
+
+        # Check that the right middleware methods have been invoked
+        self.assert_middleware_usage(pre_middleware,  True, True, False, False, False)
+        self.assert_middleware_usage(bad_middleware,  True, True, True,  False, False)
+        self.assert_middleware_usage(post_middleware, True, True, True,  False, False)
 
 
     def test_process_response_bad_middleware(self):
     def test_process_response_bad_middleware(self):
         pre_middleware = TestMiddleware()
         pre_middleware = TestMiddleware()
@@ -448,9 +517,9 @@ class BadMiddlewareTests(BaseMiddlewareExceptionTest):
         self.assert_exceptions_handled('/middleware_exceptions/view/', ['Test Response Exception'])
         self.assert_exceptions_handled('/middleware_exceptions/view/', ['Test Response Exception'])
 
 
         # Check that the right middleware methods have been invoked
         # Check that the right middleware methods have been invoked
-        self.assert_middleware_usage(pre_middleware,  True,  True,  False, False)
-        self.assert_middleware_usage(bad_middleware,  True,  True,  True,  False)
-        self.assert_middleware_usage(post_middleware, True,  True,  True,  False)
+        self.assert_middleware_usage(pre_middleware,  True, True, False, False, False)
+        self.assert_middleware_usage(bad_middleware,  True, True, False, True,  False)
+        self.assert_middleware_usage(post_middleware, True, True, False, True,  False)
 
 
     def test_process_exception_bad_middleware(self):
     def test_process_exception_bad_middleware(self):
         pre_middleware = TestMiddleware()
         pre_middleware = TestMiddleware()
@@ -462,9 +531,9 @@ class BadMiddlewareTests(BaseMiddlewareExceptionTest):
         self.assert_exceptions_handled('/middleware_exceptions/view/', [])
         self.assert_exceptions_handled('/middleware_exceptions/view/', [])
 
 
         # Check that the right middleware methods have been invoked
         # Check that the right middleware methods have been invoked
-        self.assert_middleware_usage(pre_middleware,  True,  True,  True, False)
-        self.assert_middleware_usage(bad_middleware,  True,  True,  True, False)
-        self.assert_middleware_usage(post_middleware, True,  True,  True, False)
+        self.assert_middleware_usage(pre_middleware,  True, True, False, True, False)
+        self.assert_middleware_usage(bad_middleware,  True, True, False, True, False)
+        self.assert_middleware_usage(post_middleware, True, True, False, True, False)
 
 
     def test_process_request_bad_middleware_not_found(self):
     def test_process_request_bad_middleware_not_found(self):
         pre_middleware = TestMiddleware()
         pre_middleware = TestMiddleware()
@@ -476,9 +545,9 @@ class BadMiddlewareTests(BaseMiddlewareExceptionTest):
         self.assert_exceptions_handled('/middleware_exceptions/not_found/', ['Test Request Exception'])
         self.assert_exceptions_handled('/middleware_exceptions/not_found/', ['Test Request Exception'])
 
 
         # Check that the right middleware methods have been invoked
         # Check that the right middleware methods have been invoked
-        self.assert_middleware_usage(pre_middleware,  True,  False, True,  False)
-        self.assert_middleware_usage(bad_middleware,  True,  False, True,  False)
-        self.assert_middleware_usage(post_middleware, False, False, True,  False)
+        self.assert_middleware_usage(pre_middleware,  True,  False, False, True, False)
+        self.assert_middleware_usage(bad_middleware,  True,  False, False, True, False)
+        self.assert_middleware_usage(post_middleware, False, False, False, True, False)
 
 
     def test_process_view_bad_middleware_not_found(self):
     def test_process_view_bad_middleware_not_found(self):
         pre_middleware = TestMiddleware()
         pre_middleware = TestMiddleware()
@@ -490,9 +559,9 @@ class BadMiddlewareTests(BaseMiddlewareExceptionTest):
         self.assert_exceptions_handled('/middleware_exceptions/not_found/', ['Test View Exception'])
         self.assert_exceptions_handled('/middleware_exceptions/not_found/', ['Test View Exception'])
 
 
         # Check that the right middleware methods have been invoked
         # Check that the right middleware methods have been invoked
-        self.assert_middleware_usage(pre_middleware,  True,  True,  True, False)
-        self.assert_middleware_usage(bad_middleware,  True,  True,  True, False)
-        self.assert_middleware_usage(post_middleware, True,  False, True, False)
+        self.assert_middleware_usage(pre_middleware,  True, True,  False, True, False)
+        self.assert_middleware_usage(bad_middleware,  True, True,  False, True, False)
+        self.assert_middleware_usage(post_middleware, True, False, False, True, False)
 
 
     def test_process_response_bad_middleware_not_found(self):
     def test_process_response_bad_middleware_not_found(self):
         pre_middleware = TestMiddleware()
         pre_middleware = TestMiddleware()
@@ -504,9 +573,9 @@ class BadMiddlewareTests(BaseMiddlewareExceptionTest):
         self.assert_exceptions_handled('/middleware_exceptions/not_found/', ['Test Response Exception'])
         self.assert_exceptions_handled('/middleware_exceptions/not_found/', ['Test Response Exception'])
 
 
         # Check that the right middleware methods have been invoked
         # Check that the right middleware methods have been invoked
-        self.assert_middleware_usage(pre_middleware,  True,  True,  False, True)
-        self.assert_middleware_usage(bad_middleware,  True,  True,  True,  True)
-        self.assert_middleware_usage(post_middleware, True,  True,  True,  True)
+        self.assert_middleware_usage(pre_middleware,  True, True, False, False, True)
+        self.assert_middleware_usage(bad_middleware,  True, True, False, True,  True)
+        self.assert_middleware_usage(post_middleware, True, True, False, True,  True)
 
 
     def test_process_exception_bad_middleware_not_found(self):
     def test_process_exception_bad_middleware_not_found(self):
         pre_middleware = TestMiddleware()
         pre_middleware = TestMiddleware()
@@ -518,9 +587,9 @@ class BadMiddlewareTests(BaseMiddlewareExceptionTest):
         self.assert_exceptions_handled('/middleware_exceptions/not_found/', ['Test Exception Exception'])
         self.assert_exceptions_handled('/middleware_exceptions/not_found/', ['Test Exception Exception'])
 
 
         # Check that the right middleware methods have been invoked
         # Check that the right middleware methods have been invoked
-        self.assert_middleware_usage(pre_middleware,  True,  True,  True, False)
-        self.assert_middleware_usage(bad_middleware,  True,  True,  True,  True)
-        self.assert_middleware_usage(post_middleware, True,  True,  True,  True)
+        self.assert_middleware_usage(pre_middleware,  True, True, False, True, False)
+        self.assert_middleware_usage(bad_middleware,  True, True, False, True, True)
+        self.assert_middleware_usage(post_middleware, True, True, False, True, True)
 
 
     def test_process_request_bad_middleware_exception(self):
     def test_process_request_bad_middleware_exception(self):
         pre_middleware = TestMiddleware()
         pre_middleware = TestMiddleware()
@@ -532,9 +601,9 @@ class BadMiddlewareTests(BaseMiddlewareExceptionTest):
         self.assert_exceptions_handled('/middleware_exceptions/error/', ['Test Request Exception'])
         self.assert_exceptions_handled('/middleware_exceptions/error/', ['Test Request Exception'])
 
 
         # Check that the right middleware methods have been invoked
         # Check that the right middleware methods have been invoked
-        self.assert_middleware_usage(pre_middleware,  True,  False, True,  False)
-        self.assert_middleware_usage(bad_middleware,  True,  False, True,  False)
-        self.assert_middleware_usage(post_middleware, False, False, True,  False)
+        self.assert_middleware_usage(pre_middleware,  True,  False, False, True, False)
+        self.assert_middleware_usage(bad_middleware,  True,  False, False, True, False)
+        self.assert_middleware_usage(post_middleware, False, False, False, True, False)
 
 
     def test_process_view_bad_middleware_exception(self):
     def test_process_view_bad_middleware_exception(self):
         pre_middleware = TestMiddleware()
         pre_middleware = TestMiddleware()
@@ -546,9 +615,9 @@ class BadMiddlewareTests(BaseMiddlewareExceptionTest):
         self.assert_exceptions_handled('/middleware_exceptions/error/', ['Test View Exception'])
         self.assert_exceptions_handled('/middleware_exceptions/error/', ['Test View Exception'])
 
 
         # Check that the right middleware methods have been invoked
         # Check that the right middleware methods have been invoked
-        self.assert_middleware_usage(pre_middleware,  True,  True,  True, False)
-        self.assert_middleware_usage(bad_middleware,  True,  True,  True, False)
-        self.assert_middleware_usage(post_middleware, True,  False, True, False)
+        self.assert_middleware_usage(pre_middleware,  True, True,  False, True, False)
+        self.assert_middleware_usage(bad_middleware,  True, True,  False, True, False)
+        self.assert_middleware_usage(post_middleware, True, False, False, True, False)
 
 
     def test_process_response_bad_middleware_exception(self):
     def test_process_response_bad_middleware_exception(self):
         pre_middleware = TestMiddleware()
         pre_middleware = TestMiddleware()
@@ -560,9 +629,9 @@ class BadMiddlewareTests(BaseMiddlewareExceptionTest):
         self.assert_exceptions_handled('/middleware_exceptions/error/', ['Error in view', 'Test Response Exception'])
         self.assert_exceptions_handled('/middleware_exceptions/error/', ['Error in view', 'Test Response Exception'])
 
 
         # Check that the right middleware methods have been invoked
         # Check that the right middleware methods have been invoked
-        self.assert_middleware_usage(pre_middleware,  True,  True,  False, True)
-        self.assert_middleware_usage(bad_middleware,  True,  True,  True,  True)
-        self.assert_middleware_usage(post_middleware, True,  True,  True,  True)
+        self.assert_middleware_usage(pre_middleware,  True, True, False, False, True)
+        self.assert_middleware_usage(bad_middleware,  True, True, False, True,  True)
+        self.assert_middleware_usage(post_middleware, True, True, False, True,  True)
 
 
     def test_process_exception_bad_middleware_exception(self):
     def test_process_exception_bad_middleware_exception(self):
         pre_middleware = TestMiddleware()
         pre_middleware = TestMiddleware()
@@ -574,9 +643,9 @@ class BadMiddlewareTests(BaseMiddlewareExceptionTest):
         self.assert_exceptions_handled('/middleware_exceptions/error/', ['Test Exception Exception'])
         self.assert_exceptions_handled('/middleware_exceptions/error/', ['Test Exception Exception'])
 
 
         # Check that the right middleware methods have been invoked
         # Check that the right middleware methods have been invoked
-        self.assert_middleware_usage(pre_middleware,  True,  True,  True, False)
-        self.assert_middleware_usage(bad_middleware,  True,  True,  True,  True)
-        self.assert_middleware_usage(post_middleware, True,  True,  True,  True)
+        self.assert_middleware_usage(pre_middleware,  True, True, False, True, False)
+        self.assert_middleware_usage(bad_middleware,  True, True, False, True, True)
+        self.assert_middleware_usage(post_middleware, True, True, False, True, True)
 
 
     def test_process_request_bad_middleware_null_view(self):
     def test_process_request_bad_middleware_null_view(self):
         pre_middleware = TestMiddleware()
         pre_middleware = TestMiddleware()
@@ -588,9 +657,9 @@ class BadMiddlewareTests(BaseMiddlewareExceptionTest):
         self.assert_exceptions_handled('/middleware_exceptions/null_view/', ['Test Request Exception'])
         self.assert_exceptions_handled('/middleware_exceptions/null_view/', ['Test Request Exception'])
 
 
         # Check that the right middleware methods have been invoked
         # Check that the right middleware methods have been invoked
-        self.assert_middleware_usage(pre_middleware,  True,  False, True,  False)
-        self.assert_middleware_usage(bad_middleware,  True,  False, True,  False)
-        self.assert_middleware_usage(post_middleware, False, False, True,  False)
+        self.assert_middleware_usage(pre_middleware,  True,  False, False, True, False)
+        self.assert_middleware_usage(bad_middleware,  True,  False, False, True, False)
+        self.assert_middleware_usage(post_middleware, False, False, False, True, False)
 
 
     def test_process_view_bad_middleware_null_view(self):
     def test_process_view_bad_middleware_null_view(self):
         pre_middleware = TestMiddleware()
         pre_middleware = TestMiddleware()
@@ -602,9 +671,9 @@ class BadMiddlewareTests(BaseMiddlewareExceptionTest):
         self.assert_exceptions_handled('/middleware_exceptions/null_view/', ['Test View Exception'])
         self.assert_exceptions_handled('/middleware_exceptions/null_view/', ['Test View Exception'])
 
 
         # Check that the right middleware methods have been invoked
         # Check that the right middleware methods have been invoked
-        self.assert_middleware_usage(pre_middleware,  True,  True,  True, False)
-        self.assert_middleware_usage(bad_middleware,  True,  True,  True, False)
-        self.assert_middleware_usage(post_middleware, True,  False, True, False)
+        self.assert_middleware_usage(pre_middleware,  True, True,  False, True, False)
+        self.assert_middleware_usage(bad_middleware,  True, True,  False, True, False)
+        self.assert_middleware_usage(post_middleware, True, False, False, True, False)
 
 
     def test_process_response_bad_middleware_null_view(self):
     def test_process_response_bad_middleware_null_view(self):
         pre_middleware = TestMiddleware()
         pre_middleware = TestMiddleware()
@@ -619,9 +688,9 @@ class BadMiddlewareTests(BaseMiddlewareExceptionTest):
             ])
             ])
 
 
         # Check that the right middleware methods have been invoked
         # Check that the right middleware methods have been invoked
-        self.assert_middleware_usage(pre_middleware,  True,  True,  False, False)
-        self.assert_middleware_usage(bad_middleware,  True,  True,  True,  False)
-        self.assert_middleware_usage(post_middleware, True,  True,  True,  False)
+        self.assert_middleware_usage(pre_middleware,  True, True, False, False, False)
+        self.assert_middleware_usage(bad_middleware,  True, True, False, True,  False)
+        self.assert_middleware_usage(post_middleware, True, True, False, True,  False)
 
 
     def test_process_exception_bad_middleware_null_view(self):
     def test_process_exception_bad_middleware_null_view(self):
         pre_middleware = TestMiddleware()
         pre_middleware = TestMiddleware()
@@ -636,9 +705,9 @@ class BadMiddlewareTests(BaseMiddlewareExceptionTest):
             ValueError())
             ValueError())
 
 
         # Check that the right middleware methods have been invoked
         # Check that the right middleware methods have been invoked
-        self.assert_middleware_usage(pre_middleware,  True,  True,  True, False)
-        self.assert_middleware_usage(bad_middleware,  True,  True,  True, False)
-        self.assert_middleware_usage(post_middleware, True,  True,  True, False)
+        self.assert_middleware_usage(pre_middleware,  True, True, False, True, False)
+        self.assert_middleware_usage(bad_middleware,  True, True, False, True, False)
+        self.assert_middleware_usage(post_middleware, True, True, False, True, False)
 
 
     def test_process_request_bad_middleware_permission_denied(self):
     def test_process_request_bad_middleware_permission_denied(self):
         pre_middleware = TestMiddleware()
         pre_middleware = TestMiddleware()
@@ -650,9 +719,9 @@ class BadMiddlewareTests(BaseMiddlewareExceptionTest):
         self.assert_exceptions_handled('/middleware_exceptions/permission_denied/', ['Test Request Exception'])
         self.assert_exceptions_handled('/middleware_exceptions/permission_denied/', ['Test Request Exception'])
 
 
         # Check that the right middleware methods have been invoked
         # Check that the right middleware methods have been invoked
-        self.assert_middleware_usage(pre_middleware,  True,  False, True,  False)
-        self.assert_middleware_usage(bad_middleware,  True,  False, True,  False)
-        self.assert_middleware_usage(post_middleware, False, False, True,  False)
+        self.assert_middleware_usage(pre_middleware,  True,  False, False, True, False)
+        self.assert_middleware_usage(bad_middleware,  True,  False, False, True, False)
+        self.assert_middleware_usage(post_middleware, False, False, False, True, False)
 
 
     def test_process_view_bad_middleware_permission_denied(self):
     def test_process_view_bad_middleware_permission_denied(self):
         pre_middleware = TestMiddleware()
         pre_middleware = TestMiddleware()
@@ -664,9 +733,9 @@ class BadMiddlewareTests(BaseMiddlewareExceptionTest):
         self.assert_exceptions_handled('/middleware_exceptions/permission_denied/', ['Test View Exception'])
         self.assert_exceptions_handled('/middleware_exceptions/permission_denied/', ['Test View Exception'])
 
 
         # Check that the right middleware methods have been invoked
         # Check that the right middleware methods have been invoked
-        self.assert_middleware_usage(pre_middleware,  True,  True,  True, False)
-        self.assert_middleware_usage(bad_middleware,  True,  True,  True, False)
-        self.assert_middleware_usage(post_middleware, True,  False, True, False)
+        self.assert_middleware_usage(pre_middleware,  True, True,  False, True, False)
+        self.assert_middleware_usage(bad_middleware,  True, True,  False, True, False)
+        self.assert_middleware_usage(post_middleware, True, False, False, True, False)
 
 
     def test_process_response_bad_middleware_permission_denied(self):
     def test_process_response_bad_middleware_permission_denied(self):
         pre_middleware = TestMiddleware()
         pre_middleware = TestMiddleware()
@@ -678,9 +747,9 @@ class BadMiddlewareTests(BaseMiddlewareExceptionTest):
         self.assert_exceptions_handled('/middleware_exceptions/permission_denied/', ['Test Response Exception'])
         self.assert_exceptions_handled('/middleware_exceptions/permission_denied/', ['Test Response Exception'])
 
 
         # Check that the right middleware methods have been invoked
         # Check that the right middleware methods have been invoked
-        self.assert_middleware_usage(pre_middleware,  True,  True,  False, True)
-        self.assert_middleware_usage(bad_middleware,  True,  True,  True,  True)
-        self.assert_middleware_usage(post_middleware, True,  True,  True,  True)
+        self.assert_middleware_usage(pre_middleware,  True, True, False, False, True)
+        self.assert_middleware_usage(bad_middleware,  True, True, False, True,  True)
+        self.assert_middleware_usage(post_middleware, True, True, False, True,  True)
 
 
     def test_process_exception_bad_middleware_permission_denied(self):
     def test_process_exception_bad_middleware_permission_denied(self):
         pre_middleware = TestMiddleware()
         pre_middleware = TestMiddleware()
@@ -692,9 +761,9 @@ class BadMiddlewareTests(BaseMiddlewareExceptionTest):
         self.assert_exceptions_handled('/middleware_exceptions/permission_denied/', ['Test Exception Exception'])
         self.assert_exceptions_handled('/middleware_exceptions/permission_denied/', ['Test Exception Exception'])
 
 
         # Check that the right middleware methods have been invoked
         # Check that the right middleware methods have been invoked
-        self.assert_middleware_usage(pre_middleware,  True,  True,  True, False)
-        self.assert_middleware_usage(bad_middleware,  True,  True,  True,  True)
-        self.assert_middleware_usage(post_middleware, True,  True,  True,  True)
+        self.assert_middleware_usage(pre_middleware,  True, True, False, True, False)
+        self.assert_middleware_usage(bad_middleware,  True, True, False, True, True)
+        self.assert_middleware_usage(post_middleware, True, True, False, True, True)
 
 
 
 
 _missing = object()
 _missing = object()

+ 3 - 0
tests/regressiontests/middleware_exceptions/urls.py

@@ -9,4 +9,7 @@ urlpatterns = patterns('',
     (r'^error/$', views.server_error),
     (r'^error/$', views.server_error),
     (r'^null_view/$', views.null_view),
     (r'^null_view/$', views.null_view),
     (r'^permission_denied/$', views.permission_denied),
     (r'^permission_denied/$', views.permission_denied),
+
+    (r'^template_response/$', views.template_response),
+    (r'^template_response_error/$', views.template_response_error),
 )
 )

+ 9 - 0
tests/regressiontests/middleware_exceptions/views.py

@@ -1,9 +1,18 @@
 from django import http
 from django import http
 from django.core.exceptions import PermissionDenied
 from django.core.exceptions import PermissionDenied
+from django.template import Template
+from django.template.response import TemplateResponse
+
 
 
 def normal_view(request):
 def normal_view(request):
     return http.HttpResponse('OK')
     return http.HttpResponse('OK')
 
 
+def template_response(request):
+    return TemplateResponse(request, Template('OK'))
+
+def template_response_error(request):
+    return TemplateResponse(request, Template('{%'))
+
 def not_found(request):
 def not_found(request):
     raise http.Http404()
     raise http.Http404()
 
 

+ 174 - 0
tests/regressiontests/templates/response.py

@@ -0,0 +1,174 @@
+import os
+from django.utils import unittest
+from django.test import RequestFactory
+from django.conf import settings
+import django.template.context
+from django.template import Template, Context, RequestContext
+from django.template.response import (TemplateResponse, SimpleTemplateResponse,
+                                      ContentNotRenderedError)
+
+def test_processor(request):
+    return {'processors': 'yes'}
+test_processor_name = 'regressiontests.templates.response.test_processor'
+
+class BaseTemplateResponseTest(unittest.TestCase):
+    # tests rely on fact that global context
+    # processors should only work when RequestContext is used.
+
+    def setUp(self):
+        self.factory = RequestFactory()
+        self._old_processors = settings.TEMPLATE_CONTEXT_PROCESSORS
+        self._old_TEMPLATE_DIRS = settings.TEMPLATE_DIRS
+        settings.TEMPLATE_CONTEXT_PROCESSORS = [test_processor_name]
+        settings.TEMPLATE_DIRS = (
+            os.path.join(
+                os.path.dirname(__file__),
+                'templates'
+            ),
+        )
+        # Force re-evaluation of the contex processor list
+        django.template.context._standard_context_processors = None
+
+    def tearDown(self):
+        settings.TEMPLATE_DIRS = self._old_TEMPLATE_DIRS
+        settings.TEMPLATE_CONTEXT_PROCESSORS = self._old_processors
+        # Force re-evaluation of the contex processor list
+        django.template.context._standard_context_processors = None
+
+
+class SimpleTemplateResponseTest(BaseTemplateResponseTest):
+
+    def _response(self, template='foo', *args, **kwargs):
+        return SimpleTemplateResponse(Template(template), *args, **kwargs)
+
+    def test_template_resolving(self):
+        response = SimpleTemplateResponse('first/test.html')
+        response.render()
+        self.assertEqual('First template\n', response.content)
+
+        templates = ['foo.html', 'second/test.html', 'first/test.html']
+        response = SimpleTemplateResponse(templates)
+        response.render()
+        self.assertEqual('Second template\n', response.content)
+
+        response = self._response()
+        response.render()
+        self.assertEqual(response.content, 'foo')
+
+    def test_explicit_baking(self):
+        # explicit baking
+        response = self._response()
+        self.assertFalse(response.is_rendered)
+        response.render()
+        self.assertTrue(response.is_rendered)
+
+    def test_render(self):
+        # response is not re-rendered without the render call
+        response = self._response().render()
+        self.assertEqual(response.content, 'foo')
+
+        # rebaking doesn't change the rendered content
+        response.template_name = Template('bar{{ baz }}')
+        response.render()
+        self.assertEqual(response.content, 'foo')
+
+        # but rendered content can be overridden by manually
+        # setting content
+        response.content = 'bar'
+        self.assertEqual(response.content, 'bar')
+
+    def test_iteration_unrendered(self):
+        # unrendered response raises an exception on iteration
+        response = self._response()
+        self.assertFalse(response.is_rendered)
+
+        def iteration():
+            for x in response:
+                pass
+        self.assertRaises(ContentNotRenderedError, iteration)
+        self.assertFalse(response.is_rendered)
+
+    def test_iteration_rendered(self):
+        # iteration works for rendered responses
+        response = self._response().render()
+        res = [x for x in response]
+        self.assertEqual(res, ['foo'])
+
+    def test_content_access_unrendered(self):
+        # unrendered response raises an exception when content is accessed
+        response = self._response()
+        self.assertFalse(response.is_rendered)
+        self.assertRaises(ContentNotRenderedError, lambda: response.content)
+        self.assertFalse(response.is_rendered)
+
+    def test_content_access_rendered(self):
+        # rendered response content can be accessed
+        response = self._response().render()
+        self.assertEqual(response.content, 'foo')
+
+    def test_set_content(self):
+        # content can be overriden
+        response = self._response()
+        self.assertFalse(response.is_rendered)
+        response.content = 'spam'
+        self.assertTrue(response.is_rendered)
+        self.assertEqual(response.content, 'spam')
+        response.content = 'baz'
+        self.assertEqual(response.content, 'baz')
+
+    def test_dict_context(self):
+        response = self._response('{{ foo }}{{ processors }}',
+                                  {'foo': 'bar'})
+        self.assertEqual(response.context_data, {'foo': 'bar'})
+        response.render()
+        self.assertEqual(response.content, 'bar')
+
+    def test_context_instance(self):
+        response = self._response('{{ foo }}{{ processors }}',
+                                  Context({'foo': 'bar'}))
+        self.assertEqual(response.context_data.__class__, Context)
+        response.render()
+        self.assertEqual(response.content, 'bar')
+
+    def test_kwargs(self):
+        response = self._response(content_type = 'application/json', status=504)
+        self.assertEqual(response['content-type'], 'application/json')
+        self.assertEqual(response.status_code, 504)
+
+    def test_args(self):
+        response = SimpleTemplateResponse('', {}, 'application/json', 504)
+        self.assertEqual(response['content-type'], 'application/json')
+        self.assertEqual(response.status_code, 504)
+
+
+class TemplateResponseTest(BaseTemplateResponseTest):
+
+    def _response(self, template='foo', *args, **kwargs):
+        return TemplateResponse(self.factory.get('/'), Template(template),
+                                *args, **kwargs)
+
+    def test_render(self):
+        response = self._response('{{ foo }}{{ processors }}').render()
+        self.assertEqual(response.content, 'yes')
+
+    def test_render_with_requestcontext(self):
+        response = self._response('{{ foo }}{{ processors }}',
+                                  {'foo': 'bar'}).render()
+        self.assertEqual(response.content, 'baryes')
+
+    def test_render_with_context(self):
+        response = self._response('{{ foo }}{{ processors }}',
+                                  Context({'foo': 'bar'})).render()
+        self.assertEqual(response.content, 'bar')
+
+    def test_kwargs(self):
+        response = self._response(content_type = 'application/json',
+                                  status=504)
+        self.assertEqual(response['content-type'], 'application/json')
+        self.assertEqual(response.status_code, 504)
+
+    def test_args(self):
+        response = TemplateResponse(self.factory.get('/'), '', {},
+                                    'application/json', 504)
+        self.assertEqual(response['content-type'], 'application/json')
+        self.assertEqual(response.status_code, 504)

+ 1 - 0
tests/regressiontests/templates/tests.py

@@ -28,6 +28,7 @@ from parser import ParserTests
 from unicode import UnicodeTests
 from unicode import UnicodeTests
 from nodelist import NodelistTest
 from nodelist import NodelistTest
 from smartif import *
 from smartif import *
+from response import *
 
 
 try:
 try:
     from loaders import *
     from loaders import *

+ 1 - 0
tests/regressiontests/views/templates/debug/render_test.html

@@ -0,0 +1 @@
+{{ foo }}.{{ bar }}.{{ baz }}.{{ processors }}