From 733178830072caeca3c054a220808b4c557faec4 Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Sat, 22 Nov 2014 09:22:38 +0100 Subject: [PATCH] Avoided rewrapping Contexts in render_to_response. This change preserves backwards-compatibility for a very common misuse of render_to_response which even occurred in the official documentation. It fixes that misuse wherever it happened in the code base and docs. Context.__init__ is documented as accepting a dict and nothing else. Since Context is dict-like, Context(Context({})) could work to some extent. However, things get complicated with RequestContext and that gets in the way of refactoring the template engine. This is the real rationale for this change. --- django/contrib/auth/tests/urls.py | 12 ++++++------ django/template/loader.py | 7 ++++++- docs/topics/i18n/translation.txt | 5 ++--- tests/context_processors/views.py | 5 +++-- tests/shortcuts/tests.py | 9 +++++++++ tests/shortcuts/urls.py | 1 + tests/shortcuts/views.py | 9 +++++++++ 7 files changed, 36 insertions(+), 12 deletions(-) diff --git a/django/contrib/auth/tests/urls.py b/django/contrib/auth/tests/urls.py index 7e9382fb562..8145f2f69cc 100644 --- a/django/contrib/auth/tests/urls.py +++ b/django/contrib/auth/tests/urls.py @@ -28,7 +28,7 @@ def remote_user_auth_view(request): def auth_processor_no_attr_access(request): render_to_response('context_processors/auth_attrs_no_access.html', - RequestContext(request, {}, processors=[context_processors.auth])) + context_instance=RequestContext(request, {}, processors=[context_processors.auth])) # *After* rendering, we check whether the session was accessed return render_to_response('context_processors/auth_attrs_test_access.html', {'session_accessed': request.session.accessed}) @@ -36,30 +36,30 @@ def auth_processor_no_attr_access(request): def auth_processor_attr_access(request): render_to_response('context_processors/auth_attrs_access.html', - RequestContext(request, {}, processors=[context_processors.auth])) + context_instance=RequestContext(request, {}, processors=[context_processors.auth])) return render_to_response('context_processors/auth_attrs_test_access.html', {'session_accessed': request.session.accessed}) def auth_processor_user(request): return render_to_response('context_processors/auth_attrs_user.html', - RequestContext(request, {}, processors=[context_processors.auth])) + context_instance=RequestContext(request, {}, processors=[context_processors.auth])) def auth_processor_perms(request): return render_to_response('context_processors/auth_attrs_perms.html', - RequestContext(request, {}, processors=[context_processors.auth])) + context_instance=RequestContext(request, {}, processors=[context_processors.auth])) def auth_processor_perm_in_perms(request): return render_to_response('context_processors/auth_attrs_perm_in_perms.html', - RequestContext(request, {}, processors=[context_processors.auth])) + context_instance=RequestContext(request, {}, processors=[context_processors.auth])) def auth_processor_messages(request): info(request, "Message 1") return render_to_response('context_processors/auth_attrs_messages.html', - RequestContext(request, {}, processors=[context_processors.auth])) + context_instance=RequestContext(request, {}, processors=[context_processors.auth])) def userpage(request): diff --git a/django/template/loader.py b/django/template/loader.py index 093d7d172b5..be06d517d21 100644 --- a/django/template/loader.py +++ b/django/template/loader.py @@ -65,7 +65,12 @@ def render_to_string(template_name, dictionary=None, context_instance=None, else: t = get_template(template_name, dirs) if not context_instance: - return t.render(Context(dictionary)) + # Django < 1.8 accepted a Context in `dictionary` even though that's + # unintended. Preserve this ability but don't rewrap `dictionary`. + if isinstance(dictionary, Context): + return t.render(dictionary) + else: + return t.render(Context(dictionary)) if not dictionary: return t.render(context_instance) # Add the dictionary to the context stack, ensuring it gets removed again diff --git a/docs/topics/i18n/translation.txt b/docs/topics/i18n/translation.txt index 8ef9b89c6b6..daf66d332bc 100644 --- a/docs/topics/i18n/translation.txt +++ b/docs/topics/i18n/translation.txt @@ -860,9 +860,8 @@ If you do this in your view: .. code-block:: python - return render_to_response('mytemplate.html', { - 'available_languages': ['en', 'es', 'fr'], - }, RequestContext(request)) + context = {'available_languages': ['en', 'es', 'fr']} + return render(request, 'mytemplate.html', context) you can iterate over those languages in the template:: diff --git a/tests/context_processors/views.py b/tests/context_processors/views.py index 7a00edab0ad..077d7227504 100644 --- a/tests/context_processors/views.py +++ b/tests/context_processors/views.py @@ -8,12 +8,13 @@ from .models import DebugObject def request_processor(request): return render_to_response( 'context_processors/request_attrs.html', - RequestContext(request, {}, processors=[context_processors.request])) + context_instance=RequestContext(request, {}, processors=[context_processors.request])) def debug_processor(request): + return render_to_response( 'context_processors/debug.html', - RequestContext(request, { + context_instance=RequestContext(request, { 'debug_objects': DebugObject.objects, }, processors=[context_processors.debug])) diff --git a/tests/shortcuts/tests.py b/tests/shortcuts/tests.py index 4835b407974..24a73d4ffd0 100644 --- a/tests/shortcuts/tests.py +++ b/tests/shortcuts/tests.py @@ -32,6 +32,15 @@ class ShortcutTests(TestCase): self.assertEqual(response.content, b'spam eggs\n') self.assertEqual(response['Content-Type'], 'text/html; charset=utf-8') + def test_render_to_response_with_context_instance_misuse(self): + """ + For backwards-compatibility, ensure that it's possible to pass a + RequestContext instance in the dictionary argument instead of the + context_instance argument. + """ + response = self.client.get('/render_to_response/context_instance_misuse/') + self.assertContains(response, 'context processor output') + def test_render(self): response = self.client.get('/render/') self.assertEqual(response.status_code, 200) diff --git a/tests/shortcuts/urls.py b/tests/shortcuts/urls.py index cd18c8a01b0..f02e74a7453 100644 --- a/tests/shortcuts/urls.py +++ b/tests/shortcuts/urls.py @@ -7,6 +7,7 @@ urlpatterns = [ url(r'^render_to_response/request_context/$', views.render_to_response_view_with_request_context), url(r'^render_to_response/content_type/$', views.render_to_response_view_with_content_type), url(r'^render_to_response/dirs/$', views.render_to_response_view_with_dirs), + url(r'^render_to_response/context_instance_misuse/$', views.render_to_response_with_context_instance_misuse), url(r'^render/$', views.render_view), url(r'^render/base_context/$', views.render_view_with_base_context), url(r'^render/content_type/$', views.render_view_with_content_type), diff --git a/tests/shortcuts/views.py b/tests/shortcuts/views.py index afae78d4d8a..c75d324a342 100644 --- a/tests/shortcuts/views.py +++ b/tests/shortcuts/views.py @@ -33,6 +33,15 @@ def render_to_response_view_with_dirs(request): return render_to_response('render_dirs_test.html', dirs=dirs) +def context_processor(request): + return {'bar': 'context processor output'} + + +def render_to_response_with_context_instance_misuse(request): + context_instance = RequestContext(request, {}, processors=[context_processor]) + # Incorrect -- context_instance should be passed as a keyword argument. + return render_to_response('shortcuts/render_test.html', context_instance) + def render_view(request): return render(request, 'shortcuts/render_test.html', { 'foo': 'FOO',