Code

Ticket #12816: render_shortcut.2.diff

File render_shortcut.2.diff, 12.6 KB (added by kmike, 3 years ago)

django.shortcuts.render implemented as TemplateResponse, with tests but without docs

Line 
1diff -r c67d03486d14 django/shortcuts/__init__.py
2--- a/django/shortcuts/__init__.py      Wed Oct 20 20:16:36 2010 +0000
3+++ b/django/shortcuts/__init__.py      Fri Oct 22 06:06:02 2010 +0600
4@@ -5,6 +5,7 @@
5 """
6 
7 from django.template import loader
8+from django.template.response import TemplateResponse
9 from django.http import HttpResponse, Http404
10 from django.http import HttpResponseRedirect, HttpResponsePermanentRedirect
11 from django.db.models.manager import Manager
12@@ -23,16 +24,16 @@
13     """
14     Returns an HttpResponseRedirect to the apropriate URL for the arguments
15     passed.
16-   
17+
18     The arguments could be:
19-   
20+
21         * A model: the model's `get_absolute_url()` function will be called.
22-   
23+
24         * A view name, possibly with arguments: `urlresolvers.reverse()` will
25           be used to reverse-resolve the name.
26-         
27+
28         * A URL, which will be used as-is for the redirect location.
29-       
30+
31     By default issues a temporary redirect; pass permanent=True to issue a
32     permanent redirect
33     """
34@@ -40,11 +41,11 @@
35         redirect_class = HttpResponsePermanentRedirect
36     else:
37         redirect_class = HttpResponseRedirect
38-   
39+
40     # If it's a model, use get_absolute_url()
41     if hasattr(to, 'get_absolute_url'):
42         return redirect_class(to.get_absolute_url())
43-   
44+
45     # Next try a reverse URL resolution.
46     try:
47         return redirect_class(urlresolvers.reverse(to, args=args, kwargs=kwargs))
48@@ -55,7 +56,7 @@
49         # If this doesn't "feel" like a URL, re-raise.
50         if '/' not in to and '.' not in to:
51             raise
52-       
53+
54     # Finally, fall back and assume it's a URL
55     return redirect_class(to)
56 
57@@ -101,4 +102,6 @@
58     obj_list = list(queryset.filter(*args, **kwargs))
59     if not obj_list:
60         raise Http404('No %s matches the given query.' % queryset.model._meta.object_name)
61-    return obj_list
62\ No newline at end of file
63+    return obj_list
64+
65+render = TemplateResponse
66diff -r c67d03486d14 django/template/response.py
67--- /dev/null   Thu Jan 01 00:00:00 1970 +0000
68+++ b/django/template/response.py       Fri Oct 22 06:06:02 2010 +0600
69@@ -0,0 +1,89 @@
70+from django.http import HttpResponse
71+from django.template import loader, Context, RequestContext
72+
73+class SimpleTemplateResponse(HttpResponse):
74+
75+    def __init__(self, template, context=None, mimetype=None, status=None,
76+            content_type=None):
77+        # These two properties were originally called 'template' and 'context'
78+        # but django.test.client.Client was clobbering those leading to really
79+        # tricky-to-debug problems
80+        self.template_name = template
81+        self.template_context = context
82+        self.baked = False
83+
84+        # content argument doesn't make sense here because it will be replaced
85+        # with rendered template so we always pass empty string in order to
86+        # prevent errors and provide shorter signature.
87+        super(SimpleTemplateResponse, self).__init__('', mimetype, status,
88+                                                     content_type)
89+
90+    def resolve_template(self, template):
91+        "Accepts a template object, path-to-template or list of paths"
92+        if isinstance(template, (list, tuple)):
93+            return loader.select_template(template)
94+        elif isinstance(template, basestring):
95+            return loader.get_template(template)
96+        else:
97+            return template
98+
99+    def resolve_context(self, context):
100+        "context can be a dictionary or a context object"
101+        if isinstance(context, Context):
102+            return context
103+        else:
104+            return Context(context)
105+
106+    def render(self):
107+        template = self.resolve_template(self.template_name)
108+        context = self.resolve_context(self.template_context)
109+        content = template.render(context)
110+        return content
111+
112+    def bake(self):
113+        """
114+        The template is baked the first time you try to access
115+        response.content or iterate over it. This is a bit ugly, but is
116+        necessary because Django middleware sometimes expects to be able to
117+        over-write the content of a response.
118+        """
119+        if not self.baked:
120+            self.force_bake()
121+
122+    def force_bake(self):
123+        "Call this if you have modified the template or context but are "
124+        "unsure if the template has already been baked."
125+        self._set_content(self.render())
126+        self.baked = True
127+
128+    def __iter__(self):
129+        self.bake()
130+        return super(SimpleTemplateResponse, self).__iter__()
131+
132+    def _get_content(self):
133+        self.bake()
134+        return super(SimpleTemplateResponse, self)._get_content()
135+
136+    def _set_content(self, value):
137+        "Overrides rendered content, unless you later call force_bake()"
138+        super(SimpleTemplateResponse, self)._set_content(value)
139+
140+    content = property(_get_content, _set_content)
141+
142+class TemplateResponse(SimpleTemplateResponse):
143+
144+    def __init__(self, request, template, context=None, mimetype=None,
145+            status=None, content_type=None):
146+        # self.request gets over-written by django.test.client.Client - and
147+        # unlike template_context and template_name the _request should not
148+        # be considered part of the public API.
149+        self._request = request
150+        super(TemplateResponse, self).__init__(
151+            template, context, mimetype, status, content_type)
152+
153+    def resolve_context(self, context):
154+        if isinstance(context, Context):
155+            return context
156+        else:
157+            return RequestContext(self._request, context)
158+
159diff -r c67d03486d14 tests/regressiontests/templates/response.py
160--- /dev/null   Thu Jan 01 00:00:00 1970 +0000
161+++ b/tests/regressiontests/templates/response.py       Fri Oct 22 06:06:02 2010 +0600
162@@ -0,0 +1,100 @@
163+from django.utils import unittest
164+from django.test import RequestFactory
165+from django.conf import settings
166+from django.template import Template, Context, RequestContext
167+from django.template.response import TemplateResponse, SimpleTemplateResponse
168+
169+# tests rely on fact that django.contrib.auth.context_processors.auth
170+# is in settings.TEMPLATE_CONTEXT_PROCESSORS and global context
171+# processors should only work when RequestContext is used.
172+
173+class SimpleTemplateResponseTest(unittest.TestCase):
174+
175+    def _response(self, template='foo', *args, **kwargs):
176+        return SimpleTemplateResponse(Template(template), *args, **kwargs)
177+
178+    def test_template_resolving(self):
179+
180+        # templates from 'templates/templates/..' dir can't be loaded so
181+        # the base.html template is reused. It is assumed that base.html
182+        # contains 'Django Internal Tests' string.
183+
184+        response = SimpleTemplateResponse('base.html')
185+        self.assertIn('Django Internal Tests', response.content)
186+
187+        response = SimpleTemplateResponse(['foo.html', 'base.html'])
188+        self.assertIn('Django Internal Tests', response.content)
189+
190+        response = self._response()
191+        self.assertEqual(response.content, 'foo')
192+
193+    def test_explicit_baking(self):
194+        # explicit baking
195+        response = self._response()
196+        self.assertFalse(response.baked)
197+        response.bake()
198+        self.assertTrue(response.baked)
199+
200+    def test_baking_on_access(self):
201+        # the response is baked when content is accessed
202+        response = self._response()
203+        self.assertFalse(response.baked)
204+        content = response.content
205+        self.assertTrue(response.baked)
206+        self.assertEqual(content, 'foo')
207+
208+    def test_dict_context(self):
209+        response = self._response('{{ foo }}{{ user.is_anonymous }}',
210+                                  {'foo': 'bar'})
211+        self.assertEqual(response.template_context, {'foo': 'bar'})
212+        self.assertEqual(response.content, 'bar')
213+
214+    def test_context_instance(self):
215+        response = self._response('{{ foo }}{{ user.is_anonymous }}',
216+                                  Context({'foo': 'bar'}))
217+        self.assertEqual(response.template_context.__class__, Context)
218+        self.assertEqual(response.content, 'bar')
219+
220+    def test_kwargs(self):
221+        response = self._response(content_type = 'application/json', status=504)
222+        self.assertEqual(response['content-type'], 'application/json')
223+        self.assertEqual(response.status_code, 504)
224+
225+    def test_args(self):
226+        response = SimpleTemplateResponse('', {}, 'application/json', 504)
227+        self.assertEqual(response['content-type'], 'application/json')
228+        self.assertEqual(response.status_code, 504)
229+
230+
231+class TemplateResponseTest(unittest.TestCase):
232+    def setUp(self):
233+        self.factory = RequestFactory()
234+
235+    def _response(self, template='foo', *args, **kwargs):
236+        return TemplateResponse(self.factory.get('/'), Template(template),
237+                                *args, **kwargs)
238+
239+    def test_render(self):
240+        response = self._response('{{ foo }}{{ user.is_anonymous }}')
241+        self.assertEqual(response.content, 'True')
242+
243+    def test_render_with_requestcontext(self):
244+        response = self._response('{{ foo }}{{ user.is_anonymous }}',
245+                                  {'foo': 'bar'})
246+        self.assertEqual(response.content, 'barTrue')
247+
248+    def test_render_with_context(self):
249+        response = self._response('{{ foo }}{{ user.is_anonymous }}',
250+                                  Context({'foo': 'bar'}))
251+        self.assertEqual(response.content, 'bar')
252+
253+    def test_kwargs(self):
254+        response = self._response(content_type = 'application/json', status=504)
255+        self.assertEqual(response['content-type'], 'application/json')
256+        self.assertEqual(response.status_code, 504)
257+
258+    def test_args(self):
259+        response = TemplateResponse(self.factory.get('/'), '', {},
260+                                    'application/json', 504)
261+        self.assertEqual(response['content-type'], 'application/json')
262+        self.assertEqual(response.status_code, 504)
263diff -r c67d03486d14 tests/regressiontests/templates/tests.py
264--- a/tests/regressiontests/templates/tests.py  Wed Oct 20 20:16:36 2010 +0000
265+++ b/tests/regressiontests/templates/tests.py  Fri Oct 22 06:06:02 2010 +0600
266@@ -27,6 +27,7 @@
267 from unicode import unicode_tests
268 from nodelist import NodelistTest
269 from smartif import *
270+from response import *
271 
272 try:
273     from loaders import *
274diff -r c67d03486d14 tests/regressiontests/views/templates/debug/render_test.html
275--- /dev/null   Thu Jan 01 00:00:00 1970 +0000
276+++ b/tests/regressiontests/views/templates/debug/render_test.html      Fri Oct 22 06:06:02 2010 +0600
277@@ -0,0 +1,1 @@
278+{{ foo }}.{{ bar }}.{{ baz }}.{{ user.is_anonymous }}
279diff -r c67d03486d14 tests/regressiontests/views/tests/__init__.py
280--- a/tests/regressiontests/views/tests/__init__.py     Wed Oct 20 20:16:36 2010 +0000
281+++ b/tests/regressiontests/views/tests/__init__.py     Fri Oct 22 06:06:02 2010 +0600
282@@ -7,3 +7,4 @@
283 from i18n import *
284 from specials import *
285 from static import *
286+from shortcuts import *
287diff -r c67d03486d14 tests/regressiontests/views/tests/shortcuts.py
288--- /dev/null   Thu Jan 01 00:00:00 1970 +0000
289+++ b/tests/regressiontests/views/tests/shortcuts.py    Fri Oct 22 06:06:02 2010 +0600
290@@ -0,0 +1,43 @@
291+from django.utils import unittest
292+from django.test import RequestFactory
293+from django.template import Template
294+from django.shortcuts import render
295+
296+def library_view(request):
297+    return render(request, 'debug/render_test.html',
298+                  {'foo': 'foo', 'baz': 'baz'})
299+
300+def customized_context_view(request):
301+    response = library_view(request)
302+    response.template_context.update({'bar': 'bar', 'baz': 'spam'})
303+    return response
304+
305+def customized_template_view(request):
306+    response = library_view(request)
307+    response.template_name = Template('context does not matter')
308+    return response
309+
310+
311+class RenderTest(unittest.TestCase):
312+
313+    def setUp(self):
314+        self.factory = RequestFactory()
315+        self.request = self.factory.get('/')
316+
317+    def test_library_view(self):
318+        response = library_view(self.request)
319+        self.assertEqual(response.content, 'foo..baz.True\n')
320+
321+    def test_customized_context(self):
322+        response = customized_context_view(self.request)
323+        self.assertEqual(response.content, 'foo.bar.spam.True\n')
324+
325+    def test_customized_template(self):
326+        response = customized_template_view(self.request)
327+        self.assertEqual(response.content, 'context does not matter')
328+
329+    def test_custom_status_and_content_type(self):
330+        response = render(self.request, 'base.html', {'foo': 'bar'},
331+                          'application/json', 504)
332+        self.assertEqual(response.status_code, 504)
333+        self.assertEqual(response['content-type'], 'application/json')