Ticket #12816: render_shortcut.5.diff

File render_shortcut.5.diff, 22.0 KB (added by kmike, 5 years ago)

generic views integration, fixed test client and messages middleware

  • django/contrib/messages/middleware.py

    diff -r c67d03486d14 django/contrib/messages/middleware.py
    a b  
    2020        # A higher middleware layer may return a request which does not contain
    2121        # messages storage, so make no assumption that it will be there.
    2222        if hasattr(request, '_messages'):
     23
     24            # we have to bake the response here because there is no way to
     25            # determine if messages where iterated in template or not
     26            if hasattr(response, 'bake') and callable(response.bake):
     27                response.bake()
     28
    2329            unstored_messages = request._messages.update(response)
    2430            if unstored_messages and settings.DEBUG:
    2531                raise ValueError('Not all temporary messages could be stored.')
  • django/contrib/messages/tests/base.py

    diff -r c67d03486d14 django/contrib/messages/tests/base.py
    a b  
    9696        storage = self.get_storage()
    9797        self.assertFalse(storage.added_new)
    9898        storage.add(constants.INFO, 'Test message 1')
    99         self.assert_(storage.added_new)
     99        self.assertTrue(storage.added_new)
    100100        storage.add(constants.INFO, 'Test message 2', extra_tags='tag')
    101101        self.assertEqual(len(storage), 2)
    102102
     
    173173            for msg in data['messages']:
    174174                self.assertContains(response, msg)
    175175
     176    def test_with_template_response(self):
     177        settings.MESSAGE_LEVEL = constants.DEBUG
     178        data = {
     179            'messages': ['Test message %d' % x for x in xrange(10)],
     180        }
     181        show_url = reverse(self.urls + '.show_template_response')
     182
     183        for level in self.levels.keys():
     184            add_url = reverse(self.urls + '.add_template_response',
     185                              args=(level,))
     186            response = self.client.post(add_url, data, follow=True)
     187            self.assertRedirects(response, show_url)
     188            self.assertTrue('messages' in response.context)
     189            for msg in data['messages']:
     190                self.assertContains(response, msg)
     191
     192            # there shouldn't be any messages on second GET request
     193            response = self.client.get(show_url)
     194            for msg in data['messages']:
     195                self.assertNotContains(response, msg)
     196
    176197    def test_multiple_posts(self):
    177198        """
    178199        Tests that messages persist properly when multiple POSTs are made
  • django/contrib/messages/tests/urls.py

    diff -r c67d03486d14 django/contrib/messages/tests/urls.py
    a b  
    22from django.contrib import messages
    33from django.core.urlresolvers import reverse
    44from django.http import HttpResponseRedirect, HttpResponse
    5 from django.shortcuts import render_to_response
     5from django.shortcuts import render_to_response, redirect
    66from django.template import RequestContext, Template
     7from django.template.response import TemplateResponse
    78
     9TEMPLATE = """{% 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"""
    819
    920def add(request, message_type):
    1021    # don't default to False here, because we want to test that it defaults
     
    1627                                            fail_silently=fail_silently)
    1728        else:
    1829            getattr(messages, message_type)(request, msg)
     30
    1931    show_url = reverse('django.contrib.messages.tests.urls.show')
    2032    return HttpResponseRedirect(show_url)
    2133
     34def add_template_response(request, message_type):
     35    for msg in request.POST.getlist('messages'):
     36        getattr(messages, message_type)(request, msg)
     37    return redirect('django.contrib.messages.tests.urls.show_template_response')
    2238
    2339def 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 %}""")
     40    t = Template(TEMPLATE)
    3341    return HttpResponse(t.render(RequestContext(request)))
    3442
     43def show_template_response(request):
     44    return TemplateResponse(request, Template(TEMPLATE))
    3545
    3646urlpatterns = patterns('',
    3747    ('^add/(debug|info|success|warning|error)/$', add),
    3848    ('^show/$', show),
     49    ('^add-template-response/(debug|info|success|warning|error)/$', add_template_response),
     50    ('^show-template-response/$', show_template_response),
    3951)
  • django/shortcuts/__init__.py

    diff -r c67d03486d14 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 -r c67d03486d14 django/template/response.py
    - +  
     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
     58    def __iter__(self):
     59        self.bake()
     60        return super(SimpleTemplateResponse, self).__iter__()
     61
     62    def _get_content(self):
     63        self.bake()
     64        return super(SimpleTemplateResponse, self)._get_content()
     65
     66    def _set_content(self, value):
     67        "Overrides rendered content, unless you later call force_bake()"
     68        super(SimpleTemplateResponse, self)._set_content(value)
     69        self.baked = True
     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
  • django/test/client.py

    diff -r c67d03486d14 django/test/client.py
    a b  
    379379
    380380            try:
    381381                response = self.handler(environ)
     382                if hasattr(response, 'bake') and callable(response.bake):
     383                    response.bake()
     384
    382385            except TemplateDoesNotExist, e:
    383386                # If the view raises an exception, Django will attempt to show
    384387                # the 500.html template. If that template is not available,
  • django/test/testcases.py

    diff -r c67d03486d14 django/test/testcases.py
    a b  
    473473        Asserts that the template with the provided name was used in rendering
    474474        the response.
    475475        """
     476        if hasattr(response, 'bake') and callable(response.bake):
     477            response.bake()
     478
    476479        if msg_prefix:
    477480            msg_prefix += ": "
    478481
  • django/views/generic/base.py

    diff -r c67d03486d14 django/views/generic/base.py
    a b  
    22from django import http
    33from django.core.exceptions import ImproperlyConfigured
    44from django.template import RequestContext, loader
     5from django.template.response import SimpleTemplateResponse
    56from django.utils.translation import ugettext_lazy as _
    67from django.utils.functional import update_wrapper
    78from django.utils.log import getLogger
     
    8990    """
    9091    template_name = None
    9192
    92     def render_to_response(self, context):
     93    def render_to_response(self, context, **httpresponse_kwargs):
    9394        """
    9495        Returns a response with a template rendered with the given context.
    9596        """
    96         return self.get_response(self.render_template(context))
    97 
    98     def get_response(self, content, **httpresponse_kwargs):
    99         """
    100         Construct an `HttpResponse` object.
    101         """
    102         return http.HttpResponse(content, **httpresponse_kwargs)
    103 
    104     def render_template(self, context):
    105         """
    106         Render the template with a given context.
    107         """
    108         context_instance = self.get_context_instance(context)
    109         return self.get_template().render(context_instance)
     97        return SimpleTemplateResponse(
     98            self.get_template(),
     99            self.get_context_instance(context),
     100            **httpresponse_kwargs
     101        )
    110102
    111103    def get_context_instance(self, context):
    112104        """
  • new file tests/regressiontests/templates/response.py

    diff -r c67d03486d14 tests/regressiontests/templates/response.py
    - +  
     1import os
     2from django.utils import unittest
     3from django.test import RequestFactory
     4from django.conf import settings
     5from django.template import Template, Context, RequestContext
     6from django.template.response import TemplateResponse, SimpleTemplateResponse
     7import django.template.context
     8
     9def test_processor(request):
     10    return {'processors': 'yes'}
     11test_processor_name = 'regressiontests.templates.response.test_processor'
     12
     13class BaseTemplateResponseTest(unittest.TestCase):
     14    # tests rely on fact that global context
     15    # processors should only work when RequestContext is used.
     16
     17    def setUp(self):
     18        self.factory = RequestFactory()
     19
     20        cp = list(settings.TEMPLATE_CONTEXT_PROCESSORS)
     21        self._old_processors = cp[:]
     22        self._old_TEMPLATE_DIRS = settings.TEMPLATE_DIRS
     23        settings.TEMPLATE_CONTEXT_PROCESSORS = cp + [test_processor_name]
     24        settings.TEMPLATE_DIRS = (
     25            os.path.join(
     26                os.path.dirname(__file__),
     27                'templates'
     28            ),
     29        )
     30
     31    def tearDown(self):
     32        settings.TEMPLATE_DIRS = self._old_TEMPLATE_DIRS
     33        settings.TEMPLATE_CONTEXT_PROCESSORS = tuple(self._old_processors)
     34        # Force re-evaluation of the contex processor list
     35        django.template.context._standard_context_processors = None
     36
     37
     38class SimpleTemplateResponseTest(BaseTemplateResponseTest):
     39
     40    def _response(self, template='foo', *args, **kwargs):
     41        return SimpleTemplateResponse(Template(template), *args, **kwargs)
     42
     43    def test_template_resolving(self):
     44        response = SimpleTemplateResponse('first/test.html')
     45        self.assertEqual('First template\n', response.content)
     46
     47        templates = ['foo.html', 'second/test.html', 'first/test.html']
     48        response = SimpleTemplateResponse(templates)
     49        self.assertEqual('Second template\n', response.content)
     50
     51        response = self._response()
     52        self.assertEqual(response.content, 'foo')
     53
     54    def test_explicit_baking(self):
     55        # explicit baking
     56        response = self._response()
     57        self.assertFalse(response.baked)
     58        response.bake()
     59        self.assertTrue(response.baked)
     60
     61    def test_force_bake(self):
     62        # response is not re-baked without the force_bake call
     63        response = self._response()
     64        self.assertFalse(response.baked)
     65        self.assertEqual(response.content, 'foo')
     66
     67        response.template_name = Template('bar{{ baz }}')
     68        self.assertEqual(response.content, 'foo')
     69        response.bake()
     70        self.assertEqual(response.content, 'foo')
     71        response.force_bake()
     72        self.assertEqual(response.content, 'bar')
     73
     74        response.template_context = {'baz': 'baz'}
     75        self.assertEqual(response.content, 'bar')
     76        response.bake()
     77        self.assertEqual(response.content, 'bar')
     78        response.force_bake()
     79        self.assertEqual(response.content, 'barbaz')
     80
     81        response.template_context = {'baz': 'spam'}
     82        for x in response:
     83            pass
     84        self.assertEqual(response.content, 'barbaz')
     85
     86    def test_baking_on_iteration(self):
     87        # response becomes baked after first iteration
     88        response = self._response()
     89        self.assertFalse(response.baked)
     90        for x in response:
     91            pass
     92        self.assertTrue(response.baked)
     93
     94    def test_baking_on_access(self):
     95        # the response is baked when content is accessed
     96        response = self._response()
     97        self.assertFalse(response.baked)
     98        content = response.content
     99        self.assertTrue(response.baked)
     100        self.assertEqual(content, 'foo')
     101
     102    def test_set_content(self):
     103        # content can be overriden
     104        response = self._response()
     105        self.assertFalse(response.baked)
     106        response.content = 'spam'
     107        self.assertTrue(response.baked)
     108        self.assertEqual(response.content, 'spam')
     109        response.content = 'baz'
     110        self.assertEqual(response.content, 'baz')
     111
     112    def test_dict_context(self):
     113        response = self._response('{{ foo }}{{ processors }}',
     114                                  {'foo': 'bar'})
     115        self.assertEqual(response.template_context, {'foo': 'bar'})
     116        self.assertEqual(response.content, 'bar')
     117
     118    def test_context_instance(self):
     119        response = self._response('{{ foo }}{{ processors }}',
     120                                  Context({'foo': 'bar'}))
     121        self.assertEqual(response.template_context.__class__, Context)
     122        self.assertEqual(response.content, 'bar')
     123
     124    def test_kwargs(self):
     125        response = self._response(content_type = 'application/json', status=504)
     126        self.assertEqual(response['content-type'], 'application/json')
     127        self.assertEqual(response.status_code, 504)
     128
     129    def test_args(self):
     130        response = SimpleTemplateResponse('', {}, 'application/json', 504)
     131        self.assertEqual(response['content-type'], 'application/json')
     132        self.assertEqual(response.status_code, 504)
     133
     134
     135class TemplateResponseTest(BaseTemplateResponseTest):
     136
     137    def _response(self, template='foo', *args, **kwargs):
     138        return TemplateResponse(self.factory.get('/'), Template(template),
     139                                *args, **kwargs)
     140
     141    def test_render(self):
     142        response = self._response('{{ foo }}{{ processors }}')
     143        self.assertEqual(response.content, 'yes')
     144
     145    def test_render_with_requestcontext(self):
     146        response = self._response('{{ foo }}{{ processors }}',
     147                                  {'foo': 'bar'})
     148        self.assertEqual(response.content, 'baryes')
     149
     150    def test_render_with_context(self):
     151        response = self._response('{{ foo }}{{ processors }}',
     152                                  Context({'foo': 'bar'}))
     153        self.assertEqual(response.content, 'bar')
     154
     155    def test_kwargs(self):
     156        response = self._response(content_type = 'application/json', status=504)
     157        self.assertEqual(response['content-type'], 'application/json')
     158        self.assertEqual(response.status_code, 504)
     159
     160    def test_args(self):
     161        response = TemplateResponse(self.factory.get('/'), '', {},
     162                                    'application/json', 504)
     163        self.assertEqual(response['content-type'], 'application/json')
     164        self.assertEqual(response.status_code, 504)
  • tests/regressiontests/templates/tests.py

    diff -r c67d03486d14 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 -r c67d03486d14 tests/regressiontests/views/templates/debug/render_test.html
    - +  
     1{{ foo }}.{{ bar }}.{{ baz }}.{{ processors }}
  • tests/regressiontests/views/tests/__init__.py

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