Ticket #12816: render_shortcut.diff

File render_shortcut.diff, 12.4 KB (added by Mikhail Korobov, 14 years ago)

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

  • django/shortcuts/__init__.py

    diff --git a/django/shortcuts/__init__.py b/django/shortcuts/__init__.py
    a b  
    55"""
    66
    77from django.template import loader
     8from django.template.response import TemplateResponse
    89from django.http import HttpResponse, Http404
    910from django.http import HttpResponseRedirect, HttpResponsePermanentRedirect
    1011from django.db.models.manager import Manager
     
    2324    """
    2425    Returns an HttpResponseRedirect to the apropriate URL for the arguments
    2526    passed.
    26    
     27
    2728    The arguments could be:
    28    
     29
    2930        * A model: the model's `get_absolute_url()` function will be called.
    30    
     31
    3132        * A view name, possibly with arguments: `urlresolvers.reverse()` will
    3233          be used to reverse-resolve the name.
    33          
     34
    3435        * A URL, which will be used as-is for the redirect location.
    35        
     36
    3637    By default issues a temporary redirect; pass permanent=True to issue a
    3738    permanent redirect
    3839    """
     
    4041        redirect_class = HttpResponsePermanentRedirect
    4142    else:
    4243        redirect_class = HttpResponseRedirect
    43    
     44
    4445    # If it's a model, use get_absolute_url()
    4546    if hasattr(to, 'get_absolute_url'):
    4647        return redirect_class(to.get_absolute_url())
    47    
     48
    4849    # Next try a reverse URL resolution.
    4950    try:
    5051        return redirect_class(urlresolvers.reverse(to, args=args, kwargs=kwargs))
     
    5556        # If this doesn't "feel" like a URL, re-raise.
    5657        if '/' not in to and '.' not in to:
    5758            raise
    58        
     59
    5960    # Finally, fall back and assume it's a URL
    6061    return redirect_class(to)
    6162
     
    101102    obj_list = list(queryset.filter(*args, **kwargs))
    102103    if not obj_list:
    103104        raise Http404('No %s matches the given query.' % queryset.model._meta.object_name)
    104     return obj_list
    105  No newline at end of file
     105    return obj_list
     106
     107render = TemplateResponse
  • new file django/template/response.py

    diff --git a/django/template/response.py b/django/template/response.py
    new file mode 100644
    - +  
     1from django.http import HttpResponse
     2from django.template import loader, Context, RequestContext
     3
     4class SimpleTemplateResponse(HttpResponse):
     5
     6    def __init__(self, template, context=None, mimetype=None, status=None,
     7            content_type=None):
     8        # These two properties were originally called 'template' and 'context'
     9        # but django.test.client.Client was clobbering those leading to really
     10        # tricky-to-debug problems
     11        self.template_name = template
     12        self.template_context = context
     13        self.baked = False
     14
     15        # content argument doesn't make sense here because it will be replaced
     16        # with rendered template so we always pass empty string in order to
     17        # prevent errors and provide shorter signature.
     18        super(SimpleTemplateResponse, self).__init__('', mimetype, status,
     19                                                     content_type)
     20
     21    def resolve_template(self, template):
     22        "Accepts a template object, path-to-template or list of paths"
     23        if isinstance(template, (list, tuple)):
     24            return loader.select_template(template)
     25        elif isinstance(template, basestring):
     26            return loader.get_template(template)
     27        else:
     28            return template
     29
     30    def resolve_context(self, context):
     31        "context can be a dictionary or a context object"
     32        if isinstance(context, Context):
     33            return context
     34        else:
     35            return Context(context)
     36
     37    def render(self):
     38        template = self.resolve_template(self.template_name)
     39        context = self.resolve_context(self.template_context)
     40        content = template.render(context)
     41        return content
     42
     43    def bake(self):
     44        """
     45        The template is baked the first time you try to access
     46        response.content or iterate over it. This is a bit ugly, but is
     47        necessary because Django middleware sometimes expects to be able to
     48        over-write the content of a response.
     49        """
     50        if not self.baked:
     51            self.force_bake()
     52
     53    def force_bake(self):
     54        "Call this if you have modified the template or context but are "
     55        "unsure if the template has already been baked."
     56        self._set_content(self.render())
     57        self.baked = True
     58
     59    def __iter__(self):
     60        self.bake()
     61        return super(SimpleTemplateResponse, self).__iter__()
     62
     63    def _get_content(self):
     64        self.bake()
     65        return super(SimpleTemplateResponse, self)._get_content()
     66
     67    def _set_content(self, value):
     68        "Overrides rendered content, unless you later call force_bake()"
     69        super(SimpleTemplateResponse, self)._set_content(value)
     70
     71    content = property(_get_content, _set_content)
     72
     73class TemplateResponse(SimpleTemplateResponse):
     74
     75    def __init__(self, request, template, context=None, mimetype=None,
     76            status=None, content_type=None):
     77        # self.request gets over-written by django.test.client.Client - and
     78        # unlike template_context and template_name the _request should not
     79        # be considered part of the public API.
     80        self._request = request
     81        super(TemplateResponse, self).__init__(
     82            template, context, mimetype, status, content_type)
     83
     84    def resolve_context(self, context):
     85        if isinstance(context, Context):
     86            return context
     87        else:
     88            return RequestContext(self._request, context)
     89
  • new file tests/regressiontests/templates/response.py

    diff --git a/tests/regressiontests/templates/response.py b/tests/regressiontests/templates/response.py
    new file mode 100644
    - +  
     1from django.utils import unittest
     2from django.test import RequestFactory
     3from django.conf import settings
     4from django.template import Template, Context, RequestContext
     5from django.template.response import TemplateResponse, SimpleTemplateResponse
     6
     7# tests rely on fact that django.contrib.auth.context_processors.auth
     8# is in settings.TEMPLATE_CONTEXT_PROCESSORS and global context
     9# processors should only work when RequestContext is used.
     10
     11class SimpleTemplateResponseTest(unittest.TestCase):
     12
     13    def _response(self, template='foo', *args, **kwargs):
     14        return SimpleTemplateResponse(Template(template), *args, **kwargs)
     15
     16    def test_template_resolving(self):
     17
     18        # templates from 'templates/templates/..' dir can't be loaded so
     19        # the base.html template is reused. It is assumed that base.html
     20        # contains 'Django Internal Tests' string.
     21
     22        response = SimpleTemplateResponse('base.html')
     23        self.assertIn('Django Internal Tests', response.content)
     24
     25        response = SimpleTemplateResponse(['foo.html', 'base.html'])
     26        self.assertIn('Django Internal Tests', response.content)
     27
     28        response = self._response()
     29        self.assertEqual(response.content, 'foo')
     30
     31    def test_explicit_baking(self):
     32        # explicit baking
     33        response = self._response()
     34        self.assertFalse(response.baked)
     35        response.bake()
     36        self.assertTrue(response.baked)
     37
     38    def test_baking_on_access(self):
     39        # the response is baked when content is accessed
     40        response = self._response()
     41        self.assertFalse(response.baked)
     42        content = response.content
     43        self.assertTrue(response.baked)
     44        self.assertEqual(content, 'foo')
     45
     46    def test_dict_context(self):
     47        response = self._response('{{ foo }}{{ user.is_anonymous }}',
     48                                  {'foo': 'bar'})
     49        self.assertEqual(response.template_context, {'foo': 'bar'})
     50        self.assertEqual(response.content, 'bar')
     51
     52    def test_context_instance(self):
     53        response = self._response('{{ foo }}{{ user.is_anonymous }}',
     54                                  Context({'foo': 'bar'}))
     55        self.assertEqual(response.template_context.__class__, Context)
     56        self.assertEqual(response.content, 'bar')
     57
     58    def test_kwargs(self):
     59        response = self._response(content_type = 'application/json', status=504)
     60        self.assertEqual(response['content-type'], 'application/json')
     61        self.assertEqual(response.status_code, 504)
     62
     63    def test_args(self):
     64        response = SimpleTemplateResponse('', {}, 'application/json', 504)
     65        self.assertEqual(response['content-type'], 'application/json')
     66        self.assertEqual(response.status_code, 504)
     67
     68
     69class TemplateResponseTest(unittest.TestCase):
     70    def setUp(self):
     71        self.factory = RequestFactory()
     72
     73    def _response(self, template='foo', *args, **kwargs):
     74        return TemplateResponse(self.factory.get('/'), Template(template),
     75                                *args, **kwargs)
     76
     77    def test_render(self):
     78        response = self._response('{{ foo }}{{ user.is_anonymous }}')
     79        self.assertEqual(response.content, 'True')
     80
     81    def test_render_with_requestcontext(self):
     82        response = self._response('{{ foo }}{{ user.is_anonymous }}',
     83                                  {'foo': 'bar'})
     84        self.assertEqual(response.content, 'barTrue')
     85
     86    def test_render_with_context(self):
     87        response = self._response('{{ foo }}{{ user.is_anonymous }}',
     88                                  Context({'foo': 'bar'}))
     89        self.assertEqual(response.content, 'bar')
     90
     91    def test_kwargs(self):
     92        response = self._response(content_type = 'application/json', status=504)
     93        self.assertEqual(response['content-type'], 'application/json')
     94        self.assertEqual(response.status_code, 504)
     95
     96    def test_args(self):
     97        response = TemplateResponse(self.factory.get('/'), '', {},
     98                                    'application/json', 504)
     99        self.assertEqual(response['content-type'], 'application/json')
     100        self.assertEqual(response.status_code, 504)
  • tests/regressiontests/templates/tests.py

    diff --git a/tests/regressiontests/templates/tests.py b/tests/regressiontests/templates/tests.py
    a b  
    2727from unicode import unicode_tests
    2828from nodelist import NodelistTest
    2929from smartif import *
     30from response import *
    3031
    3132try:
    3233    from loaders import *
  • new file tests/regressiontests/views/templates/debug/render_test.html

    diff --git a/tests/regressiontests/views/templates/debug/render_test.html b/tests/regressiontests/views/templates/debug/render_test.html
    new file mode 100644
    - +  
     1{{ foo }}.{{ bar }}.{{ baz }}.{{ user.is_anonymous }}
  • tests/regressiontests/views/tests/__init__.py

    diff --git a/tests/regressiontests/views/tests/__init__.py b/tests/regressiontests/views/tests/__init__.py
    a b  
    77from i18n import *
    88from specials import *
    99from static import *
     10from shortcuts import *
  • new file tests/regressiontests/views/tests/shortcuts.py

    diff --git a/tests/regressiontests/views/tests/shortcuts.py b/tests/regressiontests/views/tests/shortcuts.py
    new file mode 100644
    - +  
     1from django.utils import unittest
     2from django.test import RequestFactory
     3from django.template import Template
     4from django.shortcuts import render
     5
     6def library_view(request):
     7    return render(request, 'debug/render_test.html',
     8                  {'foo': 'foo', 'baz': 'baz'})
     9
     10def customized_context_view(request):
     11    response = library_view(request)
     12    response.template_context.update({'bar': 'bar', 'baz': 'spam'})
     13    return response
     14
     15def customized_template_view(request):
     16    response = library_view(request)
     17    response.template_name = Template('context does not matter')
     18    return response
     19
     20
     21class RenderTest(unittest.TestCase):
     22
     23    def setUp(self):
     24        self.factory = RequestFactory()
     25        self.request = self.factory.get('/')
     26
     27    def test_library_view(self):
     28        response = library_view(self.request)
     29        self.assertEqual(response.content, 'foo..baz.True\n')
     30
     31    def test_customized_context(self):
     32        response = customized_context_view(self.request)
     33        self.assertEqual(response.content, 'foo.bar.spam.True\n')
     34
     35    def test_customized_template(self):
     36        response = customized_template_view(self.request)
     37        self.assertEqual(response.content, 'context does not matter')
     38
     39    def custom_status_and_content_type(self):
     40        response = render(self.request, 'base.html', 'application/json', 504)
     41        self.assertEqual(response.status_code, 504)
     42        self.assertEqual(response['content-type', 'application/json'])
Back to Top