diff options
Diffstat (limited to 'django')
| -rw-r--r-- | django/contrib/messages/tests/base.py | 22 | ||||
| -rw-r--r-- | django/contrib/messages/tests/urls.py | 34 | ||||
| -rw-r--r-- | django/core/handlers/base.py | 15 | ||||
| -rw-r--r-- | django/template/response.py | 108 | ||||
| -rw-r--r-- | django/views/generic/base.py | 51 |
5 files changed, 176 insertions, 54 deletions
diff --git a/django/contrib/messages/tests/base.py b/django/contrib/messages/tests/base.py index 9a0f7fb36e..61938acc57 100644 --- a/django/contrib/messages/tests/base.py +++ b/django/contrib/messages/tests/base.py @@ -103,7 +103,7 @@ class BaseTest(TestCase): storage = self.get_storage() self.assertFalse(storage.added_new) storage.add(constants.INFO, 'Test message 1') - self.assert_(storage.added_new) + self.assertTrue(storage.added_new) storage.add(constants.INFO, 'Test message 2', extra_tags='tag') self.assertEqual(len(storage), 2) @@ -180,6 +180,26 @@ class BaseTest(TestCase): for msg in data['messages']: self.assertContains(response, msg) + def test_with_template_response(self): + settings.MESSAGE_LEVEL = constants.DEBUG + data = { + 'messages': ['Test message %d' % x for x in xrange(10)], + } + show_url = reverse('django.contrib.messages.tests.urls.show_template_response') + for level in self.levels.keys(): + add_url = reverse('django.contrib.messages.tests.urls.add_template_response', + args=(level,)) + response = self.client.post(add_url, data, follow=True) + self.assertRedirects(response, show_url) + self.assertTrue('messages' in response.context) + for msg in data['messages']: + self.assertContains(response, msg) + + # there shouldn't be any messages on second GET request + response = self.client.get(show_url) + for msg in data['messages']: + self.assertNotContains(response, msg) + def test_multiple_posts(self): """ Tests that messages persist properly when multiple POSTs are made diff --git a/django/contrib/messages/tests/urls.py b/django/contrib/messages/tests/urls.py index 6252adcfb1..7557f9af4c 100644 --- a/django/contrib/messages/tests/urls.py +++ b/django/contrib/messages/tests/urls.py @@ -2,9 +2,20 @@ from django.conf.urls.defaults import * from django.contrib import messages from django.core.urlresolvers import reverse from django.http import HttpResponseRedirect, HttpResponse -from django.shortcuts import render_to_response +from django.shortcuts import render_to_response, redirect from django.template import RequestContext, Template +from django.template.response import TemplateResponse +TEMPLATE = """{% if messages %} +<ul class="messages"> + {% for message in messages %} + <li{% if message.tags %} class="{{ message.tags }}"{% endif %}> + {{ message }} + </li> + {% endfor %} +</ul> +{% endif %} +""" def add(request, message_type): # don't default to False here, because we want to test that it defaults @@ -16,24 +27,27 @@ def add(request, message_type): fail_silently=fail_silently) else: getattr(messages, message_type)(request, msg) + show_url = reverse('django.contrib.messages.tests.urls.show') return HttpResponseRedirect(show_url) +def add_template_response(request, message_type): + for msg in request.POST.getlist('messages'): + getattr(messages, message_type)(request, msg) + + show_url = reverse('django.contrib.messages.tests.urls.show_template_response') + return HttpResponseRedirect(show_url) def show(request): - t = Template("""{% if messages %} -<ul class="messages"> - {% for message in messages %} - <li{% if message.tags %} class="{{ message.tags }}"{% endif %}> - {{ message }} - </li> - {% endfor %} -</ul> -{% endif %}""") + t = Template(TEMPLATE) return HttpResponse(t.render(RequestContext(request))) +def show_template_response(request): + return TemplateResponse(request, Template(TEMPLATE)) urlpatterns = patterns('', ('^add/(debug|info|success|warning|error)/$', add), ('^show/$', show), + ('^template_response/add/(debug|info|success|warning|error)/$', add_template_response), + ('^template_response/show/$', show_template_response), ) diff --git a/django/core/handlers/base.py b/django/core/handlers/base.py index 81f0a9ef3a..9468b1dab9 100644 --- a/django/core/handlers/base.py +++ b/django/core/handlers/base.py @@ -21,6 +21,7 @@ class BaseHandler(object): def __init__(self): self._request_middleware = self._view_middleware = self._response_middleware = self._exception_middleware = None + def load_middleware(self): """ Populate middleware lists from settings.MIDDLEWARE_CLASSES. @@ -30,16 +31,16 @@ class BaseHandler(object): from django.conf import settings from django.core import exceptions self._view_middleware = [] + self._template_response_middleware = [] self._response_middleware = [] self._exception_middleware = [] request_middleware = [] for middleware_path in settings.MIDDLEWARE_CLASSES: try: - dot = middleware_path.rindex('.') + mw_module, mw_classname = middleware_path.rsplit('.', 1) except ValueError: raise exceptions.ImproperlyConfigured('%s isn\'t a middleware module' % middleware_path) - mw_module, mw_classname = middleware_path[:dot], middleware_path[dot+1:] try: mod = import_module(mw_module) except ImportError, e: @@ -48,7 +49,6 @@ class BaseHandler(object): mw_class = getattr(mod, mw_classname) except AttributeError: raise exceptions.ImproperlyConfigured('Middleware module "%s" does not define a "%s" class' % (mw_module, mw_classname)) - try: mw_instance = mw_class() except exceptions.MiddlewareNotUsed: @@ -58,6 +58,8 @@ class BaseHandler(object): request_middleware.append(mw_instance.process_request) if hasattr(mw_instance, 'process_view'): self._view_middleware.append(mw_instance.process_view) + if hasattr(mw_instance, 'process_template_response'): + self._template_response_middleware.insert(0, mw_instance.process_template_response) if hasattr(mw_instance, 'process_response'): self._response_middleware.insert(0, mw_instance.process_response) if hasattr(mw_instance, 'process_exception'): @@ -164,6 +166,13 @@ class BaseHandler(object): urlresolvers.set_urlconf(None) try: + # If the response supports deferred rendering, apply template + # response middleware and the render the response + if hasattr(response, 'render') and callable(response.render): + for middleware_method in self._template_response_middleware: + response = middleware_method(request, response) + response.render() + # Apply response middleware, regardless of the response for middleware_method in self._response_middleware: response = middleware_method(request, response) diff --git a/django/template/response.py b/django/template/response.py new file mode 100644 index 0000000000..d89fee0aab --- /dev/null +++ b/django/template/response.py @@ -0,0 +1,108 @@ +from django.http import HttpResponse +from django.template import loader, Context, RequestContext + +class ContentNotRenderedError(Exception): + pass + +class SimpleTemplateResponse(HttpResponse): + + def __init__(self, template, context=None, mimetype=None, status=None, + content_type=None): + # It would seem obvious to call these next two members 'template' and + # 'context', but those names are reserved as part of the test Client API. + # To avoid the name collision, we use + # tricky-to-debug problems + self.template_name = template + self.context_data = context + + # _is_rendered tracks whether the template and context has been baked into + # a final response. + self._is_rendered = False + + # content argument doesn't make sense here because it will be replaced + # with rendered template so we always pass empty string in order to + # prevent errors and provide shorter signature. + super(SimpleTemplateResponse, self).__init__('', mimetype, status, + content_type) + + def resolve_template(self, template): + "Accepts a template object, path-to-template or list of paths" + if isinstance(template, (list, tuple)): + return loader.select_template(template) + elif isinstance(template, basestring): + return loader.get_template(template) + else: + return template + + def resolve_context(self, context): + """Convert context data into a full Context object + (assuming it isn't already a Context object). + """ + if isinstance(context, Context): + return context + else: + return Context(context) + + @property + def rendered_content(self): + """Returns the freshly rendered content for the template and context + described by the TemplateResponse. + + This *does not* set the final content of the response. To set the + response content, you must either call render(), or set the + content explicitly using the value of this property. + """ + template = self.resolve_template(self.template_name) + context = self.resolve_context(self.context_data) + content = template.render(context) + return content + + def render(self): + """Render (thereby finalizing) the content of the response. + + If the content has already been rendered, this is a no-op. + + Returns the baked response instance. + """ + if not self._is_rendered: + self._set_content(self.rendered_content) + return self + + is_rendered = property(lambda self: self._is_rendered) + + def __iter__(self): + if not self._is_rendered: + raise ContentNotRenderedError('The response content must be rendered before it can be iterated over.') + return super(SimpleTemplateResponse, self).__iter__() + + def _get_content(self): + if not self._is_rendered: + raise ContentNotRenderedError('The response content must be rendered before it can be accessed.') + return super(SimpleTemplateResponse, self)._get_content() + + def _set_content(self, value): + "Overrides rendered content, unless you later call render()" + super(SimpleTemplateResponse, self)._set_content(value) + self._is_rendered = True + + content = property(_get_content, _set_content) + + +class TemplateResponse(SimpleTemplateResponse): + def __init__(self, request, template, context=None, mimetype=None, + status=None, content_type=None): + # self.request gets over-written by django.test.client.Client - and + # unlike context_data and template_name the _request should not + # be considered part of the public API. + self._request = request + super(TemplateResponse, self).__init__( + template, context, mimetype, status, content_type) + + def resolve_context(self, context): + """Convert context data into a full RequestContext object + (assuming it isn't already a Context object). + """ + if isinstance(context, Context): + return context + else: + return RequestContext(self._request, context) diff --git a/django/views/generic/base.py b/django/views/generic/base.py index 0c9cd7971f..5e69a8dd30 100644 --- a/django/views/generic/base.py +++ b/django/views/generic/base.py @@ -1,6 +1,7 @@ from django import http from django.core.exceptions import ImproperlyConfigured from django.template import RequestContext, loader +from django.template.response import TemplateResponse from django.utils.functional import update_wrapper from django.utils.log import getLogger from django.utils.decorators import classonlymethod @@ -81,59 +82,29 @@ class TemplateResponseMixin(object): A mixin that can be used to render a template. """ template_name = None + response_class = TemplateResponse - def render_to_response(self, context): + def render_to_response(self, context, **response_kwargs): """ Returns a response with a template rendered with the given context. """ - return self.get_response(self.render_template(context)) - - def get_response(self, content, **httpresponse_kwargs): - """ - Construct an `HttpResponse` object. - """ - return http.HttpResponse(content, **httpresponse_kwargs) - - def render_template(self, context): - """ - Render the template with a given context. - """ - context_instance = self.get_context_instance(context) - return self.get_template().render(context_instance) - - def get_context_instance(self, context): - """ - Get the template context instance. Must return a Context (or subclass) - instance. - """ - return RequestContext(self.request, context) - - def get_template(self): - """ - Get a ``Template`` object for the given request. - """ - names = self.get_template_names() - if not names: - raise ImproperlyConfigured(u"'%s' must provide template_name." - % self.__class__.__name__) - return self.load_template(names) + return self.response_class( + request = self.request, + template = self.get_template_names(), + context = context, + **response_kwargs + ) def get_template_names(self): """ - Return a list of template names to be used for the request. Must return - a list. May not be called if get_template is overridden. + Returns a list of template names to be used for the request. Must return + a list. May not be called if render_to_response is overridden. """ if self.template_name is None: return [] else: return [self.template_name] - def load_template(self, names): - """ - Load a list of templates using the default template loader. - """ - return loader.select_template(names) - class TemplateView(TemplateResponseMixin, View): """ |
