Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Fixed #12815 -- Added TemplateResponse, a lazy-evaluated Response cla…

…ss. 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
  • Loading branch information...
commit e0dcd7666aec15a2348bef346b4ce683ddf376b3 1 parent 22fc30b
Russell Keith-Magee authored December 07, 2010
1  AUTHORS
@@ -273,6 +273,7 @@ answer newbie questions, and generally made Django that much better:
273 273
     Igor Kolar <ike@email.si>
274 274
     Tomáš Kopeček <permonik@m6.cz>
275 275
     Gasper Koren
  276
+    Mikhail Korobov <kmike84@googlemail.com>
276 277
     Martin Kosír <martin@martinkosir.net>
277 278
     Arthur Koziel <http://arthurkoziel.com>
278 279
     Meir Kriheli <http://mksoft.co.il/>
22  django/contrib/messages/tests/base.py
@@ -103,7 +103,7 @@ def test_add(self):
103 103
         storage = self.get_storage()
104 104
         self.assertFalse(storage.added_new)
105 105
         storage.add(constants.INFO, 'Test message 1')
106  
-        self.assert_(storage.added_new)
  106
+        self.assertTrue(storage.added_new)
107 107
         storage.add(constants.INFO, 'Test message 2', extra_tags='tag')
108 108
         self.assertEqual(len(storage), 2)
109 109
 
@@ -180,6 +180,26 @@ def test_full_request_response_cycle(self):
180 180
             for msg in data['messages']:
181 181
                 self.assertContains(response, msg)
182 182
 
  183
+    def test_with_template_response(self):
  184
+        settings.MESSAGE_LEVEL = constants.DEBUG
  185
+        data = {
  186
+            'messages': ['Test message %d' % x for x in xrange(10)],
  187
+        }
  188
+        show_url = reverse('django.contrib.messages.tests.urls.show_template_response')
  189
+        for level in self.levels.keys():
  190
+            add_url = reverse('django.contrib.messages.tests.urls.add_template_response',
  191
+                              args=(level,))
  192
+            response = self.client.post(add_url, data, follow=True)
  193
+            self.assertRedirects(response, show_url)
  194
+            self.assertTrue('messages' in response.context)
  195
+            for msg in data['messages']:
  196
+                self.assertContains(response, msg)
  197
+
  198
+            # there shouldn't be any messages on second GET request
  199
+            response = self.client.get(show_url)
  200
+            for msg in data['messages']:
  201
+                self.assertNotContains(response, msg)
  202
+
183 203
     def test_multiple_posts(self):
184 204
         """
185 205
         Tests that messages persist properly when multiple POSTs are made
34  django/contrib/messages/tests/urls.py
@@ -2,9 +2,20 @@
2 2
 from django.contrib import messages
3 3
 from django.core.urlresolvers import reverse
4 4
 from django.http import HttpResponseRedirect, HttpResponse
5  
-from django.shortcuts import render_to_response
  5
+from django.shortcuts import render_to_response, redirect
6 6
 from django.template import RequestContext, Template
  7
+from django.template.response import TemplateResponse
7 8
 
  9
+TEMPLATE = """{% if messages %}
  10
+<ul class="messages">
  11
+    {% for message in messages %}
  12
+    <li{% if message.tags %} class="{{ message.tags }}"{% endif %}>
  13
+        {{ message }}
  14
+    </li>
  15
+    {% endfor %}
  16
+</ul>
  17
+{% endif %}
  18
+"""
8 19
 
9 20
 def add(request, message_type):
10 21
     # don't default to False here, because we want to test that it defaults
@@ -16,24 +27,27 @@ def add(request, message_type):
16 27
                                             fail_silently=fail_silently)
17 28
         else:
18 29
             getattr(messages, message_type)(request, msg)
  30
+
19 31
     show_url = reverse('django.contrib.messages.tests.urls.show')
20 32
     return HttpResponseRedirect(show_url)
21 33
 
  34
+def add_template_response(request, message_type):
  35
+    for msg in request.POST.getlist('messages'):
  36
+        getattr(messages, message_type)(request, msg)
  37
+
  38
+    show_url = reverse('django.contrib.messages.tests.urls.show_template_response')
  39
+    return HttpResponseRedirect(show_url)
22 40
 
23 41
 def show(request):
24  
-    t = Template("""{% if messages %}
25  
-<ul class="messages">
26  
-    {% for message in messages %}
27  
-    <li{% if message.tags %} class="{{ message.tags }}"{% endif %}>
28  
-        {{ message }}
29  
-    </li>
30  
-    {% endfor %}
31  
-</ul>
32  
-{% endif %}""")
  42
+    t = Template(TEMPLATE)
33 43
     return HttpResponse(t.render(RequestContext(request)))
34 44
 
  45
+def show_template_response(request):
  46
+    return TemplateResponse(request, Template(TEMPLATE))
35 47
 
36 48
 urlpatterns = patterns('',
37 49
     ('^add/(debug|info|success|warning|error)/$', add),
38 50
     ('^show/$', show),
  51
+    ('^template_response/add/(debug|info|success|warning|error)/$', add_template_response),
  52
+    ('^template_response/show/$', show_template_response),
39 53
 )
15  django/core/handlers/base.py
@@ -21,6 +21,7 @@ class BaseHandler(object):
21 21
     def __init__(self):
22 22
         self._request_middleware = self._view_middleware = self._response_middleware = self._exception_middleware = None
23 23
 
  24
+
24 25
     def load_middleware(self):
25 26
         """
26 27
         Populate middleware lists from settings.MIDDLEWARE_CLASSES.
@@ -30,16 +31,16 @@ def load_middleware(self):
30 31
         from django.conf import settings
31 32
         from django.core import exceptions
32 33
         self._view_middleware = []
  34
+        self._template_response_middleware = []
33 35
         self._response_middleware = []
34 36
         self._exception_middleware = []
35 37
 
36 38
         request_middleware = []
37 39
         for middleware_path in settings.MIDDLEWARE_CLASSES:
38 40
             try:
39  
-                dot = middleware_path.rindex('.')
  41
+                mw_module, mw_classname = middleware_path.rsplit('.', 1)
40 42
             except ValueError:
41 43
                 raise exceptions.ImproperlyConfigured('%s isn\'t a middleware module' % middleware_path)
42  
-            mw_module, mw_classname = middleware_path[:dot], middleware_path[dot+1:]
43 44
             try:
44 45
                 mod = import_module(mw_module)
45 46
             except ImportError, e:
@@ -48,7 +49,6 @@ def load_middleware(self):
48 49
                 mw_class = getattr(mod, mw_classname)
49 50
             except AttributeError:
50 51
                 raise exceptions.ImproperlyConfigured('Middleware module "%s" does not define a "%s" class' % (mw_module, mw_classname))
51  
-
52 52
             try:
53 53
                 mw_instance = mw_class()
54 54
             except exceptions.MiddlewareNotUsed:
@@ -58,6 +58,8 @@ def load_middleware(self):
58 58
                 request_middleware.append(mw_instance.process_request)
59 59
             if hasattr(mw_instance, 'process_view'):
60 60
                 self._view_middleware.append(mw_instance.process_view)
  61
+            if hasattr(mw_instance, 'process_template_response'):
  62
+                self._template_response_middleware.insert(0, mw_instance.process_template_response)
61 63
             if hasattr(mw_instance, 'process_response'):
62 64
                 self._response_middleware.insert(0, mw_instance.process_response)
63 65
             if hasattr(mw_instance, 'process_exception'):
@@ -164,6 +166,13 @@ def get_response(self, request):
164 166
             urlresolvers.set_urlconf(None)
165 167
 
166 168
         try:
  169
+            # If the response supports deferred rendering, apply template
  170
+            # response middleware and the render the response
  171
+            if hasattr(response, 'render') and callable(response.render):
  172
+                for middleware_method in self._template_response_middleware:
  173
+                    response = middleware_method(request, response)
  174
+                response.render()
  175
+
167 176
             # Apply response middleware, regardless of the response
168 177
             for middleware_method in self._response_middleware:
169 178
                 response = middleware_method(request, response)
108  django/template/response.py
... ...
@@ -0,0 +1,108 @@
  1
+from django.http import HttpResponse
  2
+from django.template import loader, Context, RequestContext
  3
+
  4
+class ContentNotRenderedError(Exception):
  5
+    pass
  6
+
  7
+class SimpleTemplateResponse(HttpResponse):
  8
+
  9
+    def __init__(self, template, context=None, mimetype=None, status=None,
  10
+            content_type=None):
  11
+        # It would seem obvious to call these next two members 'template' and
  12
+        # 'context', but those names are reserved as part of the test Client API.
  13
+        # To avoid the name collision, we use
  14
+        # tricky-to-debug problems
  15
+        self.template_name = template
  16
+        self.context_data = context
  17
+
  18
+        # _is_rendered tracks whether the template and context has been baked into
  19
+        # a final response.
  20
+        self._is_rendered = False
  21
+
  22
+        # content argument doesn't make sense here because it will be replaced
  23
+        # with rendered template so we always pass empty string in order to
  24
+        # prevent errors and provide shorter signature.
  25
+        super(SimpleTemplateResponse, self).__init__('', mimetype, status,
  26
+                                                     content_type)
  27
+
  28
+    def resolve_template(self, template):
  29
+        "Accepts a template object, path-to-template or list of paths"
  30
+        if isinstance(template, (list, tuple)):
  31
+            return loader.select_template(template)
  32
+        elif isinstance(template, basestring):
  33
+            return loader.get_template(template)
  34
+        else:
  35
+            return template
  36
+
  37
+    def resolve_context(self, context):
  38
+        """Convert context data into a full Context object
  39
+        (assuming it isn't already a Context object).
  40
+        """
  41
+        if isinstance(context, Context):
  42
+            return context
  43
+        else:
  44
+            return Context(context)
  45
+
  46
+    @property
  47
+    def rendered_content(self):
  48
+        """Returns the freshly rendered content for the template and context
  49
+        described by the TemplateResponse.
  50
+
  51
+        This *does not* set the final content of the response. To set the
  52
+        response content, you must either call render(), or set the
  53
+        content explicitly using the value of this property.
  54
+        """
  55
+        template = self.resolve_template(self.template_name)
  56
+        context = self.resolve_context(self.context_data)
  57
+        content = template.render(context)
  58
+        return content
  59
+
  60
+    def render(self):
  61
+        """Render (thereby finalizing) the content of the response.
  62
+
  63
+        If the content has already been rendered, this is a no-op.
  64
+
  65
+        Returns the baked response instance.
  66
+        """
  67
+        if not self._is_rendered:
  68
+            self._set_content(self.rendered_content)
  69
+        return self
  70
+
  71
+    is_rendered = property(lambda self: self._is_rendered)
  72
+
  73
+    def __iter__(self):
  74
+        if not self._is_rendered:
  75
+            raise ContentNotRenderedError('The response content must be rendered before it can be iterated over.')
  76
+        return super(SimpleTemplateResponse, self).__iter__()
  77
+
  78
+    def _get_content(self):
  79
+        if not self._is_rendered:
  80
+            raise ContentNotRenderedError('The response content must be rendered before it can be accessed.')
  81
+        return super(SimpleTemplateResponse, self)._get_content()
  82
+
  83
+    def _set_content(self, value):
  84
+        "Overrides rendered content, unless you later call render()"
  85
+        super(SimpleTemplateResponse, self)._set_content(value)
  86
+        self._is_rendered = True
  87
+
  88
+    content = property(_get_content, _set_content)
  89
+
  90
+
  91
+class TemplateResponse(SimpleTemplateResponse):
  92
+    def __init__(self, request, template, context=None, mimetype=None,
  93
+            status=None, content_type=None):
  94
+        # self.request gets over-written by django.test.client.Client - and
  95
+        # unlike context_data and template_name the _request should not
  96
+        # be considered part of the public API.
  97
+        self._request = request
  98
+        super(TemplateResponse, self).__init__(
  99
+            template, context, mimetype, status, content_type)
  100
+
  101
+    def resolve_context(self, context):
  102
+        """Convert context data into a full RequestContext object
  103
+        (assuming it isn't already a Context object).
  104
+        """
  105
+        if isinstance(context, Context):
  106
+            return context
  107
+        else:
  108
+            return RequestContext(self._request, context)
51  django/views/generic/base.py
... ...
@@ -1,6 +1,7 @@
1 1
 from django import http
2 2
 from django.core.exceptions import ImproperlyConfigured
3 3
 from django.template import RequestContext, loader
  4
+from django.template.response import TemplateResponse
4 5
 from django.utils.functional import update_wrapper
5 6
 from django.utils.log import getLogger
6 7
 from django.utils.decorators import classonlymethod
@@ -81,59 +82,29 @@ class TemplateResponseMixin(object):
81 82
     A mixin that can be used to render a template.
82 83
     """
83 84
     template_name = None
  85
+    response_class = TemplateResponse
84 86
 
85  
-    def render_to_response(self, context):
  87
+    def render_to_response(self, context, **response_kwargs):
86 88
         """
87 89
         Returns a response with a template rendered with the given context.
88 90
         """
89  
-        return self.get_response(self.render_template(context))
90  
-
91  
-    def get_response(self, content, **httpresponse_kwargs):
92  
-        """
93  
-        Construct an `HttpResponse` object.
94  
-        """
95  
-        return http.HttpResponse(content, **httpresponse_kwargs)
96  
-
97  
-    def render_template(self, context):
98  
-        """
99  
-        Render the template with a given context.
100  
-        """
101  
-        context_instance = self.get_context_instance(context)
102  
-        return self.get_template().render(context_instance)
103  
-
104  
-    def get_context_instance(self, context):
105  
-        """
106  
-        Get the template context instance. Must return a Context (or subclass)
107  
-        instance.
108  
-        """
109  
-        return RequestContext(self.request, context)
110  
-
111  
-    def get_template(self):
112  
-        """
113  
-        Get a ``Template`` object for the given request.
114  
-        """
115  
-        names = self.get_template_names()
116  
-        if not names:
117  
-            raise ImproperlyConfigured(u"'%s' must provide template_name."
118  
-                                       % self.__class__.__name__)
119  
-        return self.load_template(names)
  91
+        return self.response_class(
  92
+            request = self.request,
  93
+            template = self.get_template_names(),
  94
+            context = context,
  95
+            **response_kwargs
  96
+        )
120 97
 
121 98
     def get_template_names(self):
122 99
         """
123  
-        Return a list of template names to be used for the request. Must return
124  
-        a list. May not be called if get_template is overridden.
  100
+        Returns a list of template names to be used for the request. Must return
  101
+        a list. May not be called if render_to_response is overridden.
125 102
         """
126 103
         if self.template_name is None:
127 104
             return []
128 105
         else:
129 106
             return [self.template_name]
130 107
 
131  
-    def load_template(self, names):
132  
-        """
133  
-        Load a list of templates using the default template loader.
134  
-        """
135  
-        return loader.select_template(names)
136  
-
137 108
 
138 109
 class TemplateView(TemplateResponseMixin, View):
139 110
     """
4  docs/index.txt
@@ -93,7 +93,9 @@ The view layer
93 93
       :doc:`View functions <topics/http/views>` |
94 94
       :doc:`Shortcuts <topics/http/shortcuts>`
95 95
 
96  
-    * **Reference:**  :doc:`Request/response objects <ref/request-response>`
  96
+    * **Reference:**
  97
+      :doc:`Request/response objects <ref/request-response>` |
  98
+      :doc:`TemplateResponse objects <ref/template-response>`
97 99
 
98 100
     * **File uploads:**
99 101
       :doc:`Overview <topics/http/file-uploads>` |
46  docs/ref/class-based-views.txt
@@ -76,39 +76,25 @@ TemplateResponseMixin
76 76
 
77 77
         The path to the template to use when rendering the view.
78 78
 
79  
-    .. method:: render_to_response(context)
  79
+    .. attribute:: response_class
80 80
 
81  
-        Returns a full composed HttpResponse instance, ready to be returned to
82  
-        the user.
  81
+        The response class to be returned by ``render_to_response`` method.
  82
+        Default is
  83
+        :class:`TemplateResponse <django.template.response.TemplateResponse>`.
  84
+        The template and context of TemplateResponse instances can be
  85
+        altered later (e.g. in
  86
+        :ref:`template response middleware <template-response-middleware>`).
83 87
 
84  
-        Calls :meth:`~TemplateResponseMixin.render_template()` to build the
85  
-        content of the response, and
86  
-        :meth:`~TemplateResponseMixin.get_response()` to construct the
87  
-        :class:`~django.http.HttpResponse` object.
  88
+        Create TemplateResponse subclass and pass set it to
  89
+        ``template_response_class`` if you need custom template loading or
  90
+        custom context object instantiation.
88 91
 
89  
-    .. method:: get_response(content, **httpresponse_kwargs)
  92
+    .. method:: render_to_response(context, **response_kwargs)
90 93
 
91  
-        Constructs the :class:`~django.http.HttpResponse` object around the
92  
-        given content. If any keyword arguments are provided, they will be
93  
-        passed to the constructor of the :class:`~django.http.HttpResponse`
94  
-        instance.
  94
+        Returns a ``self.template_response_class`` instance.
95 95
 
96  
-    .. method:: render_template(context)
97  
-
98  
-        Calls :meth:`~TemplateResponseMixin.get_context_instance()` to obtain
99  
-        the :class:`Context` instance to use for rendering, and calls
100  
-        :meth:`TemplateReponseMixin.get_template()` to load the template that
101  
-        will be used to render the final content.
102  
-
103  
-    .. method:: get_context_instance(context)
104  
-
105  
-        Turns the data dictionary ``context`` into an actual context instance
106  
-        that can be used for rendering.
107  
-
108  
-        By default, constructs a :class:`~django.template.RequestContext`
109  
-        instance.
110  
-
111  
-    .. method:: get_template()
  96
+        If any keyword arguments are provided, they will be
  97
+        passed to the constructor of the response instance.
112 98
 
113 99
         Calls :meth:`~TemplateResponseMixin.get_template_names()` to obtain the
114 100
         list of template names that will be searched looking for an existent
@@ -123,10 +109,6 @@ TemplateResponseMixin
123 109
         default implementation will return a list containing
124 110
         :attr:`TemplateResponseMixin.template_name` (if it is specified).
125 111
 
126  
-    .. method:: load_template(names)
127  
-
128  
-        Loads and returns a template found by searching the list of ``names``
129  
-        for a match. Uses Django's default template loader.
130 112
 
131 113
 Single object mixins
132 114
 --------------------
1  docs/ref/index.txt
@@ -16,6 +16,7 @@ API Reference
16 16
    middleware
17 17
    models/index
18 18
    request-response
  19
+   template-response
19 20
    settings
20 21
    signals
21 22
    templates/index
211  docs/ref/template-response.txt
... ...
@@ -0,0 +1,211 @@
  1
+===========================================
  2
+TemplateResponse and SimpleTemplateResponse
  3
+===========================================
  4
+
  5
+.. versionadded:: 1.3
  6
+
  7
+.. module:: django.template.response
  8
+   :synopsis: Classes dealing with lazy-rendered HTTP responses.
  9
+
  10
+Standard HttpResponse objects are static structures. They are provided
  11
+with a block of pre-rendered content at time of construction, and
  12
+while that content can be modified, it isn't in a form that makes it
  13
+easy to perform modifications.
  14
+
  15
+However, it can sometimes be beneficial to allow decorators or
  16
+middleware to modify a response *after* it has been constructed by the
  17
+view. For example, you may want to change the template that is used,
  18
+or put additional data into the context.
  19
+
  20
+TemplateResponse provides a way to do just that. Unlike basic
  21
+HttpResponse objects, TemplateResponse objects retain the details of
  22
+the template and context that was provided by the view to compute the
  23
+response. The final output of the response is not computed until
  24
+it is needed, later in the response process.
  25
+
  26
+TemplateResponse objects
  27
+========================
  28
+
  29
+.. class:: SimpleTemplateResponse()
  30
+
  31
+Attributes
  32
+----------
  33
+
  34
+.. attribute:: SimpleTemplateResponse.template_name
  35
+
  36
+    The name of the template to be rendered. Accepts
  37
+    :class:`django.template.Template` object, path to template or list
  38
+    of paths.
  39
+
  40
+    Example: ``['foo.html', 'path/to/bar.html']``
  41
+
  42
+.. attribute:: SimpleTemplateResponse.context_data
  43
+
  44
+    The context data to be used when rendering the template. It can be
  45
+    a dictionary or a context object.
  46
+
  47
+    Example: ``{'foo': 123}``
  48
+
  49
+.. attr:: SimpleTemplateResponse.rendered_content:
  50
+
  51
+    The current rendered value of the response content, using the current
  52
+    template and context data.
  53
+
  54
+.. attr:: SimpleTemplateResponse.is_rendered:
  55
+
  56
+    A boolean indicating whether the response content has been rendered.
  57
+
  58
+
  59
+Methods
  60
+-------
  61
+
  62
+.. method:: SimpleTemplateResponse.__init__(template, context=None, mimetype=None, status=None, content_type=None)
  63
+
  64
+    Instantiates an
  65
+    :class:`~django.template.response.SimpleTemplateResponse` object
  66
+    with the given template, context, MIME type and HTTP status.
  67
+
  68
+    ``template`` is a full name of a template, or a sequence of
  69
+    template names. :class:`django.template.Template` instances can
  70
+    also be used.
  71
+
  72
+    ``context`` is a dictionary of values to add to the template
  73
+    context. By default, this is an empty dictionary.
  74
+    :class:`~django.template.Context` objects are also accepted as
  75
+    ``context`` values.
  76
+
  77
+    ``status`` is the HTTP Status code for the response.
  78
+
  79
+    ``content_type`` is an alias for ``mimetype``. Historically, this
  80
+    parameter was only called ``mimetype``, but since this is actually
  81
+    the value included in the HTTP ``Content-Type`` header, it can
  82
+    also include the character set encoding, which makes it more than
  83
+    just a MIME type specification. If ``mimetype`` is specified (not
  84
+    ``None``), that value is used. Otherwise, ``content_type`` is
  85
+    used. If neither is given, the ``DEFAULT_CONTENT_TYPE`` setting is
  86
+    used.
  87
+
  88
+
  89
+.. method:: SimpleTemplateResponse.resolve_context(context)
  90
+
  91
+    Converts context data into a context instance that can be used for
  92
+    rendering a template. Accepts a dictionary of context data or a
  93
+    context object. Returns a :class:`~django.template.Context`
  94
+    instance containing the provided data.
  95
+
  96
+    Override this method in order to customize context instantiation.
  97
+
  98
+.. method:: SimpleTemplateResponse.resolve_template(template)
  99
+
  100
+    Resolves the template instance to use for rendering. Accepts a
  101
+    path of a template to use, or a sequence of template paths.
  102
+    :class:`~django.template.Template` instances may also be provided.
  103
+    Returns the :class:`~django.template.Template` instance to be
  104
+    rendered.
  105
+
  106
+    Override this method in order to customize template rendering.
  107
+
  108
+.. method:: SimpleTemplateResponse.render():
  109
+
  110
+    Sets :attr:`response.content` to the result obtained by
  111
+    :attr:`SimpleTemplateResponse.rendered_content`.
  112
+
  113
+    :meth:`~SimpleTemplateResponse.render()` will only have an effect
  114
+    the first time it is called. On subsequent calls, it will return
  115
+    the result obtained from the first call.
  116
+
  117
+
  118
+.. class:: TemplateResponse()
  119
+
  120
+   TemplateResponse is a subclass of :class:`SimpleTemplateResponse
  121
+   <django.template.response.SimpleTemplateResponse>` that uses
  122
+   RequestContext instead of Context.
  123
+
  124
+.. method:: TemplateResponse.__init__(request, template, context=None, mimetype=None, status=None, content_type=None)
  125
+
  126
+   Instantiates an ``TemplateResponse`` object with the given
  127
+   template, context, MIME type and HTTP status.
  128
+
  129
+   ``request`` is a HttpRequest instance.
  130
+
  131
+   ``template`` is a full name of a template to use or sequence of
  132
+   template names. :class:`django.template.Template` instances are
  133
+   also accepted.
  134
+
  135
+   ``context`` is a dictionary of values to add to the template
  136
+   context. By default, this is an empty dictionary; context objects
  137
+   are also accepted as ``context`` values.
  138
+
  139
+   ``status`` is the HTTP Status code for the response.
  140
+
  141
+   ``content_type`` is an alias for ``mimetype``. Historically, this
  142
+   parameter was only called ``mimetype``, but since this is actually
  143
+   the value included in the HTTP ``Content-Type`` header, it can also
  144
+   include the character set encoding, which makes it more than just a
  145
+   MIME type specification. If ``mimetype`` is specified (not
  146
+   ``None``), that value is used. Otherwise, ``content_type`` is used.
  147
+   If neither is given, the ``DEFAULT_CONTENT_TYPE`` setting is used.
  148
+
  149
+
  150
+The rendering process
  151
+=====================
  152
+
  153
+Before a :class:`TemplateResponse()` instance can be returned to the
  154
+client, it must be rendered. The rendering process takes the
  155
+intermediate representation of template and context, and turns it into
  156
+the final byte stream that can be served to the client.
  157
+
  158
+There are three circumstances under which a TemplateResponse will be
  159
+rendered:
  160
+
  161
+    * When the TemplateResponse instance is explicitly rendered, using
  162
+      the :meth:`SimpleTemplateResponse.render()` method.
  163
+
  164
+    * When the content of the response is explicitly set by assigning
  165
+      :attr:`response.content`.
  166
+
  167
+    * After passing through template response middleware, but before
  168
+      passing through response middleware.
  169
+
  170
+A TemplateResponse can only be rendered once. The first call to
  171
+:meth:`SimpleTemplateResponse.render()` sets the content of the
  172
+response; subsequent rendering calls do not change the response
  173
+content.
  174
+
  175
+However, when :attr:`response.content` is explicitly assigned, the
  176
+change is always applied. If you want to force the content to be
  177
+re-rendered, you can re-evaluate the rendered content, and assign
  178
+the content of the response manually::
  179
+
  180
+    # Set up a baked TemplateResponse
  181
+    >>> t = TemplateResponse(request, 'original.html', {})
  182
+    >>> t.render()
  183
+    >>> print t.content
  184
+    Original content
  185
+
  186
+    # Rebaking doesn't change content
  187
+    >>> t.template_name = 'new.html'
  188
+    >>> t.render()
  189
+    >>> print t.content
  190
+    Original content
  191
+
  192
+    # Assigning content does change, no render() call required
  193
+    >>> t.content = t.rendered_content
  194
+    >>> print t.content
  195
+    New content
  196
+
  197
+Using TemplateResponse and SimpleTemplateResponse
  198
+=================================================
  199
+
  200
+A TemplateResponse object can be used anywhere that a normal
  201
+HttpResponse can be used. It can also be used as an alternative to
  202
+calling :method:`~django.shortcuts.render_to_response()`.
  203
+
  204
+For example, the following simple view returns a
  205
+:class:`TemplateResponse()` with a simple template, and a context
  206
+containing a queryset::
  207
+
  208
+    from django.template.response import TemplateResponse
  209
+
  210
+    def blog_index(request):
  211
+        return TemplateResponse(request, 'entry_list.html', {'entries': Entry.objects.all()})
21  docs/releases/1.3.txt
@@ -133,6 +133,27 @@ can also add special translator comments in the source.
133 133
 For more information, see :ref:`contextual-markers` and
134 134
 :ref:`translator-comments`.
135 135
 
  136
+TemplateResponse
  137
+~~~~~~~~~~~~~~~~
  138
+
  139
+It can sometimes be beneficial to allow decorators or middleware to
  140
+modify a response *after* it has been constructed by the view. For
  141
+example, you may want to change the template that is used, or put
  142
+additional data into the context.
  143
+
  144
+However, you can't (easily) modify the content of a basic
  145
+:class:`~django.http.HttpResponse` after it has been constructed. To
  146
+overcome this limitation, Django 1.3 adds a new
  147
+:class:`~django.template.TemplateResponse` class. Unlike basic
  148
+:class:`~django.http.HttpResponse` objects,
  149
+:class:`~django.template.TemplateResponse` objects retain the details
  150
+of the template and context that was provided by the view to compute
  151
+the response. The final output of the response is not computed until
  152
+it is needed, later in the response process.
  153
+
  154
+For more details, see the :ref:`documentation </ref/template-response>`
  155
+on the :class:`~django.template.TemplateResponse` class.
  156
+
136 157
 Everything else
137 158
 ~~~~~~~~~~~~~~~
138 159
 
36  docs/topics/http/middleware.txt
@@ -97,6 +97,39 @@ calling ANY other request, view or exception middleware, or the appropriate
97 97
 view; it'll return that :class:`~django.http.HttpResponse`. Response
98 98
 middleware is always called on every response.
99 99
 
  100
+.. _template-response-middleware:
  101
+
  102
+``process_template_response``
  103
+-----------------------------
  104
+
  105
+.. versionadded:: 1.3
  106
+
  107
+.. method:: process_template_response(self, request, response)
  108
+
  109
+``request`` is an :class:`~django.http.HttpRequest` object. ``response`` is the
  110
+:class:`~django.template.response.SimpleTemplateResponse` subclass (e.g.
  111
+:class:`~django.template.response.TemplateResponse`) object returned by a
  112
+Django view.
  113
+
  114
+``process_template_response()`` must return an
  115
+:class:`~django.template.response.SimpleTemplateResponse` (or it's subclass)
  116
+object. It could alter the given ``response`` by changing
  117
+``response.template_name`` and ``response.template_context``, or it could
  118
+create and return a brand-new
  119
+:class:`~django.template.response.SimpleTemplateResponse` (or it's subclass)
  120
+instance.
  121
+
  122
+``process_template_response()`` will only be called if the response
  123
+instance has a ``render()`` method, indicating that it is a
  124
+:class:`~django.template.response.TemplateResponse`.
  125
+
  126
+You don't need to explicitly render responses -- responses will be
  127
+automatically rendered once all template response middleware has been
  128
+called.
  129
+
  130
+Middleware are run in reverse order during the response phase, which
  131
+includes process_template_response.
  132
+
100 133
 .. _response-middleware:
101 134
 
102 135
 ``process_response``
@@ -120,6 +153,7 @@ an earlier middleware method returned an :class:`~django.http.HttpResponse`
120 153
 classes are applied in reverse order, from the bottom up. This means classes
121 154
 defined at the end of :setting:`MIDDLEWARE_CLASSES` will be run first.
122 155
 
  156
+
123 157
 .. _exception-middleware:
124 158
 
125 159
 ``process_exception``
@@ -137,7 +171,7 @@ Django calls ``process_exception()`` when a view raises an exception.
137 171
 the browser. Otherwise, default exception handling kicks in.
138 172
 
139 173
 Again, middleware are run in reverse order during the response phase, which
140  
-includes ``process_exception``. If an exception middleware return a response,
  174
+includes ``process_exception``. If an exception middleware returns a response,
141 175
 the middleware classes above that middleware will not be called at all.
142 176
 
143 177
 ``__init__``
1  tests/regressiontests/generic_views/base.py
@@ -156,6 +156,7 @@ class TemplateViewTest(TestCase):
156 156
     rf = RequestFactory()
157 157
 
158 158
     def _assert_about(self, response):
  159
+        response.render()
159 160
         self.assertEqual(response.status_code, 200)
160 161
         self.assertEqual(response.content, '<h1>About</h1>')
161 162
 
313  tests/regressiontests/middleware_exceptions/tests.py
@@ -3,9 +3,10 @@
3 3
 from django.conf import settings
4 4
 from django.core.signals import got_request_exception
5 5
 from django.http import HttpResponse
  6
+from django.template.response import TemplateResponse
  7
+from django.template import Template
6 8
 from django.test import TestCase
7 9
 
8  
-
9 10
 class TestException(Exception):
10 11
     pass
11 12
 
@@ -16,6 +17,7 @@ def __init__(self):
16 17
         self.process_request_called = False
17 18
         self.process_view_called = False
18 19
         self.process_response_called = False
  20
+        self.process_template_response_called = False
19 21
         self.process_exception_called = False
20 22
 
21 23
     def process_request(self, request):
@@ -24,6 +26,10 @@ def process_request(self, request):
24 26
     def process_view(self, request, view_func, view_args, view_kwargs):
25 27
         self.process_view_called = True
26 28
 
  29
+    def process_template_response(self, request, response):
  30
+        self.process_template_response_called = True
  31
+        return response
  32
+
27 33
     def process_response(self, request, response):
28 34
         self.process_response_called = True
29 35
         return response
@@ -48,6 +54,11 @@ def process_response(self, request, response):
48 54
         super(ResponseMiddleware, self).process_response(request, response)
49 55
         return HttpResponse('Response Middleware')
50 56
 
  57
+class TemplateResponseMiddleware(TestMiddleware):
  58
+    def process_template_response(self, request, response):
  59
+        super(TemplateResponseMiddleware, self).process_template_response(request, response)
  60
+        return TemplateResponse(request, Template('Template Response Middleware'))
  61
+
51 62
 class ExceptionMiddleware(TestMiddleware):
52 63
     def process_exception(self, request, exception):
53 64
         super(ExceptionMiddleware, self).process_exception(request, exception)
@@ -66,6 +77,11 @@ def process_view(self, request, view_func, view_args, view_kwargs):
66 77
         super(BadViewMiddleware, self).process_view(request, view_func, view_args, view_kwargs)
67 78
         raise TestException('Test View Exception')
68 79
 
  80
+class BadTemplateResponseMiddleware(TestMiddleware):
  81
+    def process_template_response(self, request, response):
  82
+        super(BadTemplateResponseMiddleware, self).process_template_response(request, response)
  83
+        raise TestException('Test Template Response Exception')
  84
+
69 85
 class BadResponseMiddleware(TestMiddleware):
70 86
     def process_response(self, request, response):
71 87
         super(BadResponseMiddleware, self).process_response(request, response)
@@ -93,6 +109,7 @@ def _on_request_exception(self, sender, request, **kwargs):
93 109
     def _add_middleware(self, middleware):
94 110
         self.client.handler._request_middleware.insert(0, middleware.process_request)
95 111
         self.client.handler._view_middleware.insert(0, middleware.process_view)
  112
+        self.client.handler._template_response_middleware.append(middleware.process_template_response)
96 113
         self.client.handler._response_middleware.append(middleware.process_response)
97 114
         self.client.handler._exception_middleware.append(middleware.process_exception)
98 115
 
@@ -113,9 +130,10 @@ def assert_exceptions_handled(self, url, errors, extra_error=None):
113 130
             exception, value, tb = self.exceptions[i]
114 131
             self.assertEquals(value.args, (error, ))
115 132
 
116  
-    def assert_middleware_usage(self, middleware, request, view, response, exception):
  133
+    def assert_middleware_usage(self, middleware, request, view, template_response, response, exception):
117 134
         self.assertEqual(middleware.process_request_called, request)
118 135
         self.assertEqual(middleware.process_view_called, view)
  136
+        self.assertEqual(middleware.process_template_response_called, template_response)
119 137
         self.assertEqual(middleware.process_response_called, response)
120 138
         self.assertEqual(middleware.process_exception_called, exception)
121 139
 
@@ -132,9 +150,9 @@ def test_process_request_middleware(self):
132 150
         self.assert_exceptions_handled('/middleware_exceptions/view/', [])
133 151
 
134 152
         # Check that the right middleware methods have been invoked
135  
-        self.assert_middleware_usage(pre_middleware,  True,  False, True,  False)
136  
-        self.assert_middleware_usage(middleware,      True,  False, True,  False)
137  
-        self.assert_middleware_usage(post_middleware, False, False, True,  False)
  153
+        self.assert_middleware_usage(pre_middleware,  True,  False, False, True, False)
  154
+        self.assert_middleware_usage(middleware,      True,  False, False, True, False)
  155
+        self.assert_middleware_usage(post_middleware, False, False, False, True, False)
138 156
 
139 157
     def test_process_view_middleware(self):
140 158
         pre_middleware = TestMiddleware()
@@ -146,9 +164,9 @@ def test_process_view_middleware(self):
146 164
         self.assert_exceptions_handled('/middleware_exceptions/view/', [])
147 165
 
148 166
         # Check that the right middleware methods have been invoked
149  
-        self.assert_middleware_usage(pre_middleware,  True,  True,  True, False)
150  
-        self.assert_middleware_usage(middleware,      True,  True,  True, False)
151  
-        self.assert_middleware_usage(post_middleware, True,  False, True, False)
  167
+        self.assert_middleware_usage(pre_middleware,  True, True,  False, True, False)
  168
+        self.assert_middleware_usage(middleware,      True, True,  False, True, False)
  169
+        self.assert_middleware_usage(post_middleware, True, False, False, True, False)
152 170
 
153 171
     def test_process_response_middleware(self):
154 172
         pre_middleware = TestMiddleware()
@@ -160,9 +178,23 @@ def test_process_response_middleware(self):
160 178
         self.assert_exceptions_handled('/middleware_exceptions/view/', [])
161 179
 
162 180
         # Check that the right middleware methods have been invoked
163  
-        self.assert_middleware_usage(pre_middleware,  True,  True,  True,  False)
164  
-        self.assert_middleware_usage(middleware,      True,  True,  True,  False)
165  
-        self.assert_middleware_usage(post_middleware, True,  True,  True,  False)
  181
+        self.assert_middleware_usage(pre_middleware,  True, True, False, True, False)
  182
+        self.assert_middleware_usage(middleware,      True, True, False, True, False)
  183
+        self.assert_middleware_usage(post_middleware, True, True, False, True, False)
  184
+
  185
+    def test_process_template_response_middleware(self):
  186
+        pre_middleware = TestMiddleware()
  187
+        middleware = TemplateResponseMiddleware()
  188
+        post_middleware = TestMiddleware()
  189
+        self._add_middleware(post_middleware)
  190
+        self._add_middleware(middleware)
  191
+        self._add_middleware(pre_middleware)
  192
+        self.assert_exceptions_handled('/middleware_exceptions/template_response/', [])
  193
+
  194
+        # Check that the right middleware methods have been invoked
  195
+        self.assert_middleware_usage(pre_middleware,  True, True, True, True, False)
  196
+        self.assert_middleware_usage(middleware,      True, True, True, True, False)
  197
+        self.assert_middleware_usage(post_middleware, True, True, True, True, False)
166 198
 
167 199
     def test_process_exception_middleware(self):
168 200
         pre_middleware = TestMiddleware()
@@ -174,9 +206,9 @@ def test_process_exception_middleware(self):
174 206
         self.assert_exceptions_handled('/middleware_exceptions/view/', [])
175 207
 
176 208
         # Check that the right middleware methods have been invoked
177  
-        self.assert_middleware_usage(pre_middleware,  True,  True,  True, False)
178  
-        self.assert_middleware_usage(middleware,      True,  True,  True, False)
179  
-        self.assert_middleware_usage(post_middleware, True,  True,  True, False)
  209
+        self.assert_middleware_usage(pre_middleware,  True, True, False, True, False)
  210
+        self.assert_middleware_usage(middleware,      True, True, False, True, False)
  211
+        self.assert_middleware_usage(post_middleware, True, True, False, True, False)
180 212
 
181 213
     def test_process_request_middleware_not_found(self):
182 214
         pre_middleware = TestMiddleware()
@@ -188,9 +220,9 @@ def test_process_request_middleware_not_found(self):
188 220
         self.assert_exceptions_handled('/middleware_exceptions/not_found/', [])
189 221
 
190 222
         # Check that the right middleware methods have been invoked
191  
-        self.assert_middleware_usage(pre_middleware,  True,  False, True,  False)
192  
-        self.assert_middleware_usage(middleware,      True,  False, True,  False)
193  
-        self.assert_middleware_usage(post_middleware, False, False, True,  False)
  223
+        self.assert_middleware_usage(pre_middleware,  True,  False, False, True, False)
  224
+        self.assert_middleware_usage(middleware,      True,  False, False, True, False)
  225
+        self.assert_middleware_usage(post_middleware, False, False, False, True, False)
194 226
 
195 227
     def test_process_view_middleware_not_found(self):
196 228
         pre_middleware = TestMiddleware()
@@ -202,9 +234,23 @@ def test_process_view_middleware_not_found(self):
202 234
         self.assert_exceptions_handled('/middleware_exceptions/not_found/', [])
203 235
 
204 236
         # Check that the right middleware methods have been invoked
205  
-        self.assert_middleware_usage(pre_middleware,  True,  True,  True, False)
206  
-        self.assert_middleware_usage(middleware,      True,  True,  True, False)
207  
-        self.assert_middleware_usage(post_middleware, True,  False, True, False)
  237
+        self.assert_middleware_usage(pre_middleware,  True, True,  False, True, False)
  238
+        self.assert_middleware_usage(middleware,      True, True,  False, True, False)
  239
+        self.assert_middleware_usage(post_middleware, True, False, False, True, False)
  240
+
  241
+    def test_process_template_response_middleware_not_found(self):
  242
+        pre_middleware = TestMiddleware()
  243
+        middleware = TemplateResponseMiddleware()
  244
+        post_middleware = TestMiddleware()
  245
+        self._add_middleware(post_middleware)
  246
+        self._add_middleware(middleware)
  247
+        self._add_middleware(pre_middleware)
  248
+        self.assert_exceptions_handled('/middleware_exceptions/not_found/', [])
  249
+
  250
+        # Check that the right middleware methods have been invoked
  251
+        self.assert_middleware_usage(pre_middleware,  True, True, False, True, True)
  252
+        self.assert_middleware_usage(middleware,      True, True, False, True, True)
  253
+        self.assert_middleware_usage(post_middleware, True, True, False, True, True)
208 254
 
209 255
     def test_process_response_middleware_not_found(self):
210 256
         pre_middleware = TestMiddleware()
@@ -216,9 +262,9 @@ def test_process_response_middleware_not_found(self):
216 262
         self.assert_exceptions_handled('/middleware_exceptions/not_found/', [])
217 263
 
218 264
         # Check that the right middleware methods have been invoked
219  
-        self.assert_middleware_usage(pre_middleware,  True,  True,  True,  True)
220  
-        self.assert_middleware_usage(middleware,      True,  True,  True,  True)
221  
-        self.assert_middleware_usage(post_middleware, True,  True,  True,  True)
  265
+        self.assert_middleware_usage(pre_middleware,  True, True, False, True, True)
  266
+        self.assert_middleware_usage(middleware,      True, True, False, True, True)
  267
+        self.assert_middleware_usage(post_middleware, True, True, False, True, True)
222 268
 
223 269
     def test_process_exception_middleware_not_found(self):
224 270
         pre_middleware = TestMiddleware()
@@ -230,9 +276,9 @@ def test_process_exception_middleware_not_found(self):
230 276
         self.assert_exceptions_handled('/middleware_exceptions/not_found/', [])
231 277
 
232 278
         # Check that the right middleware methods have been invoked
233  
-        self.assert_middleware_usage(pre_middleware,  True,  True,  True, False)
234  
-        self.assert_middleware_usage(middleware,      True,  True,  True,  True)
235  
-        self.assert_middleware_usage(post_middleware, True,  True,  True,  True)
  279
+        self.assert_middleware_usage(pre_middleware,  True, True, False, True, False)
  280
+        self.assert_middleware_usage(middleware,      True, True, False, True, True)
  281
+        self.assert_middleware_usage(post_middleware, True, True, False, True, True)
236 282
 
237 283
     def test_process_request_middleware_exception(self):
238 284
         pre_middleware = TestMiddleware()
@@ -244,9 +290,9 @@ def test_process_request_middleware_exception(self):
244 290
         self.assert_exceptions_handled('/middleware_exceptions/error/', [])
245 291
 
246 292
         # Check that the right middleware methods have been invoked
247  
-        self.assert_middleware_usage(pre_middleware,  True,  False, True,  False)
248  
-        self.assert_middleware_usage(middleware,  True,  False, True,  False)
249  
-        self.assert_middleware_usage(post_middleware, False, False, True,  False)
  293
+        self.assert_middleware_usage(pre_middleware,  True,  False, False, True, False)
  294
+        self.assert_middleware_usage(middleware,      True,  False, False, True, False)
  295
+        self.assert_middleware_usage(post_middleware, False, False, False, True, False)
250 296
 
251 297
     def test_process_view_middleware_exception(self):
252 298
         pre_middleware = TestMiddleware()
@@ -258,9 +304,9 @@ def test_process_view_middleware_exception(self):
258 304
         self.assert_exceptions_handled('/middleware_exceptions/error/', [])
259 305
 
260 306
         # Check that the right middleware methods have been invoked
261  
-        self.assert_middleware_usage(pre_middleware,  True,  True,  True, False)
262  
-        self.assert_middleware_usage(middleware,      True,  True,  True, False)
263  
-        self.assert_middleware_usage(post_middleware, True,  False, True, False)
  307
+        self.assert_middleware_usage(pre_middleware,  True, True,  False, True, False)
  308
+        self.assert_middleware_usage(middleware,      True, True,  False, True, False)
  309
+        self.assert_middleware_usage(post_middleware, True, False, False, True, False)
264 310
 
265 311
     def test_process_response_middleware_exception(self):
266 312
         pre_middleware = TestMiddleware()
@@ -272,9 +318,9 @@ def test_process_response_middleware_exception(self):
272 318
         self.assert_exceptions_handled('/middleware_exceptions/error/', ['Error in view'], Exception())
273 319
 
274 320
         # Check that the right middleware methods have been invoked
275  
-        self.assert_middleware_usage(pre_middleware,  True,  True,  True,  True)
276  
-        self.assert_middleware_usage(middleware,      True,  True,  True,  True)
277  
-        self.assert_middleware_usage(post_middleware, True,  True,  True,  True)
  321
+        self.assert_middleware_usage(pre_middleware,  True, True, False, True, True)
  322
+        self.assert_middleware_usage(middleware,      True, True, False, True, True)
  323
+        self.assert_middleware_usage(post_middleware, True, True, False, True, True)
278 324
 
279 325
     def test_process_exception_middleware_exception(self):
280 326
         pre_middleware = TestMiddleware()
@@ -286,9 +332,9 @@ def test_process_exception_middleware_exception(self):
286 332
         self.assert_exceptions_handled('/middleware_exceptions/error/', [])
287 333
 
288 334
         # Check that the right middleware methods have been invoked
289  
-        self.assert_middleware_usage(pre_middleware,  True,  True,  True, False)
290  
-        self.assert_middleware_usage(middleware,      True,  True,  True,  True)
291  
-        self.assert_middleware_usage(post_middleware, True,  True,  True,  True)
  335
+        self.assert_middleware_usage(pre_middleware,  True, True, False, True, False)
  336
+        self.assert_middleware_usage(middleware,      True, True, False, True, True)
  337
+        self.assert_middleware_usage(post_middleware, True, True, False, True, True)
292 338
 
293 339
     def test_process_request_middleware_null_view(self):
294 340
         pre_middleware = TestMiddleware()
@@ -300,9 +346,9 @@ def test_process_request_middleware_null_view(self):
300 346
         self.assert_exceptions_handled('/middleware_exceptions/null_view/', [])
301 347
 
302 348
         # Check that the right middleware methods have been invoked
303  
-        self.assert_middleware_usage(pre_middleware,  True,  False, True,  False)
304  
-        self.assert_middleware_usage(middleware,      True,  False, True,  False)
305  
-        self.assert_middleware_usage(post_middleware, False, False, True,  False)
  349
+        self.assert_middleware_usage(pre_middleware,  True,  False, False, True, False)
  350
+        self.assert_middleware_usage(middleware,      True,  False, False, True, False)
  351
+        self.assert_middleware_usage(post_middleware, False, False, False, True, False)
306 352
 
307 353
     def test_process_view_middleware_null_view(self):
308 354
         pre_middleware = TestMiddleware()
@@ -314,9 +360,9 @@ def test_process_view_middleware_null_view(self):
314 360
         self.assert_exceptions_handled('/middleware_exceptions/null_view/', [])
315 361
 
316 362
         # Check that the right middleware methods have been invoked
317  
-        self.assert_middleware_usage(pre_middleware,  True,  True,  True, False)
318  
-        self.assert_middleware_usage(middleware,      True,  True,  True, False)
319  
-        self.assert_middleware_usage(post_middleware, True,  False, True, False)
  363
+        self.assert_middleware_usage(pre_middleware,  True, True,  False, True, False)
  364
+        self.assert_middleware_usage(middleware,      True, True,  False, True, False)
  365
+        self.assert_middleware_usage(post_middleware, True, False, False, True, False)
320 366
 
321 367
     def test_process_response_middleware_null_view(self):
322 368
         pre_middleware = TestMiddleware()
@@ -331,9 +377,9 @@ def test_process_response_middleware_null_view(self):
331 377
             ValueError())
332 378
 
333 379
         # Check that the right middleware methods have been invoked
334  
-        self.assert_middleware_usage(pre_middleware,  True,  True,  True,  False)
335  
-        self.assert_middleware_usage(middleware,      True,  True,  True,  False)
336  
-        self.assert_middleware_usage(post_middleware, True,  True,  True,  False)
  380
+        self.assert_middleware_usage(pre_middleware,  True, True, False, True, False)
  381
+        self.assert_middleware_usage(middleware,      True, True, False, True, False)
  382
+        self.assert_middleware_usage(post_middleware, True, True, False, True, False)
337 383
 
338 384
     def test_process_exception_middleware_null_view(self):
339 385
         pre_middleware = TestMiddleware()
@@ -348,9 +394,9 @@ def test_process_exception_middleware_null_view(self):
348 394
             ValueError())
349 395