diff --git a/django/test/client.py b/django/test/client.py index 08e3ff6b71..05880884f0 100644 --- a/django/test/client.py +++ b/django/test/client.py @@ -4,6 +4,7 @@ import sys import os import re import mimetypes +import warnings try: from cStringIO import StringIO except ImportError: @@ -93,7 +94,7 @@ def store_rendered_templates(store, signal, sender, template, context, **kwargs) """ Stores templates and contexts that are rendered. """ - store.setdefault('template', []).append(template) + store.setdefault('templates', []).append(template) store.setdefault('context', ContextList()).append(context) def encode_multipart(boundary, data): @@ -260,16 +261,25 @@ class Client(object): response.request = request # Add any rendered template detail to the response. - # If there was only one template rendered (the most likely case), - # flatten the list to a single element. - for detail in ('template', 'context'): - if data.get(detail): - if len(data[detail]) == 1: - setattr(response, detail, data[detail][0]); - else: - setattr(response, detail, data[detail]) - else: - setattr(response, detail, None) + response.templates = data.get("templates", []) + response.context = data.get("context") + + # Flatten a single context. Not really necessary anymore thanks to + # the __getattr__ flattening in ContextList, but has some edge-case + # backwards-compatibility implications. + if response.context and len(response.context) == 1: + response.context = response.context[0] + + # Provide a backwards-compatible (but pending deprecation) response.template + def _get_template(self): + warnings.warn("response.template is deprecated; use response.templates instead (which is always a list)", + PendingDeprecationWarning) + if not self.templates: + return None + elif len(self.templates) == 1: + return self.templates[0] + return self.templates + response.__class__.template = property(_get_template) # Update persistent cookie data. if response.cookies: diff --git a/django/test/testcases.py b/django/test/testcases.py index 90319a72e9..40b3c82500 100644 --- a/django/test/testcases.py +++ b/django/test/testcases.py @@ -443,7 +443,7 @@ class TransactionTestCase(unittest.TestCase): if msg_prefix: msg_prefix += ": " - template_names = [t.name for t in to_list(response.template)] + template_names = [t.name for t in response.templates] if not template_names: self.fail(msg_prefix + "No templates used to render the response") self.failUnless(template_name in template_names, @@ -459,7 +459,7 @@ class TransactionTestCase(unittest.TestCase): if msg_prefix: msg_prefix += ": " - template_names = [t.name for t in to_list(response.template)] + template_names = [t.name for t in response.templates] self.failIf(template_name in template_names, msg_prefix + "Template '%s' was used unexpectedly in rendering" " the response" % template_name) diff --git a/docs/internals/deprecation.txt b/docs/internals/deprecation.txt index b313871128..edd3a1d2ec 100644 --- a/docs/internals/deprecation.txt +++ b/docs/internals/deprecation.txt @@ -102,6 +102,12 @@ their deprecation, as per the :ref:`Django deprecation policy * The ``mod_python`` request handler has been deprecated since the 1.3 release. The ``mod_wsgi`` handler should be used instead. + * The ``template`` attribute on :class:`~django.test.client.Response` + objects returned by the :ref:`test client ` has been + deprecated since the 1.3 release. The + :attr:`~django.test.client.Response.templates` attribute should be + used instead. + * 2.0 * ``django.views.defaults.shortcut()``. This function has been moved to ``django.contrib.contenttypes.views.shortcut()`` as part of the diff --git a/docs/releases/1.3.txt b/docs/releases/1.3.txt index 483fec6122..a494ec94df 100644 --- a/docs/releases/1.3.txt +++ b/docs/releases/1.3.txt @@ -106,6 +106,23 @@ If you are currently using the ``mod_python`` request handler, it is strongly encouraged you redeploy your Django instances using :doc:`mod_wsgi `. +Test client response ``template`` attribute +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Django's :ref:`test client ` returns +:class:`~django.test.client.Response` objects annotated with extra testing +information. In Django versions prior to 1.3, this included a +:attr:`~django.test.client.Response.template` attribute containing information +about templates rendered in generating the response: either None, a single +:class:`~django.template.Template` object, or a list of +:class:`~django.template.Template` objects. This inconsistency in return values +(sometimes a list, sometimes not) made the attribute difficult to work with. + +In Django 1.3 the :attr:`~django.test.client.Response.template` attribute is +deprecated in favor of a new :attr:`~django.test.client.Response.templates` +attribute, which is always a list, even if it has only a single element or no +elements. + What's new in Django 1.3 ======================== diff --git a/docs/topics/testing.txt b/docs/topics/testing.txt index 28411ab05f..8a19d38f22 100644 --- a/docs/topics/testing.txt +++ b/docs/topics/testing.txt @@ -494,6 +494,8 @@ Testing tools Django provides a small set of tools that come in handy when writing tests. +.. _test-client: + The test client --------------- @@ -894,15 +896,15 @@ Specifically, a ``Response`` object has the following attributes: The HTTP status of the response, as an integer. See RFC2616_ for a full list of HTTP status codes. - .. attribute:: template + .. versionadded:: 1.3 - The ``Template`` instance that was used to render the final content. Use + .. attribute:: templates + + A list of ``Template`` instances used to render the final content, in + the order they were rendered. For each template in the list, use ``template.name`` to get the template's file name, if the template was - loaded from a file. (The name is a string such as ``'admin/index.html'``.) - - If the rendered page used multiple templates -- e.g., using :ref:`template - inheritance` -- then ``template`` will be a list of - ``Template`` instances, in the order in which they were rendered. + loaded from a file. (The name is a string such as + ``'admin/index.html'``.) You can also use dictionary syntax on the response object to query the value of any settings in the HTTP headers. For example, you could determine the diff --git a/tests/modeltests/test_client/models.py b/tests/modeltests/test_client/models.py index cd3269ee26..4a0c9d2ae0 100644 --- a/tests/modeltests/test_client/models.py +++ b/tests/modeltests/test_client/models.py @@ -37,7 +37,7 @@ class ClientTest(TestCase): # Check some response details self.assertContains(response, 'This is a test') self.assertEqual(response.context['var'], u'\xf2') - self.assertEqual(response.template.name, 'GET Template') + self.assertEqual(response.templates[0].name, 'GET Template') def test_get_post_view(self): "GET a view that normally expects POSTs" @@ -45,7 +45,7 @@ class ClientTest(TestCase): # Check some response details self.assertEqual(response.status_code, 200) - self.assertEqual(response.template.name, 'Empty GET Template') + self.assertEqual(response.templates[0].name, 'Empty GET Template') self.assertTemplateUsed(response, 'Empty GET Template') self.assertTemplateNotUsed(response, 'Empty POST Template') @@ -55,7 +55,7 @@ class ClientTest(TestCase): # Check some response details self.assertEqual(response.status_code, 200) - self.assertEqual(response.template.name, 'Empty POST Template') + self.assertEqual(response.templates[0].name, 'Empty POST Template') self.assertTemplateNotUsed(response, 'Empty GET Template') self.assertTemplateUsed(response, 'Empty POST Template') @@ -69,7 +69,7 @@ class ClientTest(TestCase): # Check some response details self.assertEqual(response.status_code, 200) self.assertEqual(response.context['data'], '37') - self.assertEqual(response.template.name, 'POST Template') + self.assertEqual(response.templates[0].name, 'POST Template') self.failUnless('Data received' in response.content) def test_response_headers(self): @@ -84,7 +84,7 @@ class ClientTest(TestCase): response = self.client.post("/test_client/raw_post_view/", test_doc, content_type="text/xml") self.assertEqual(response.status_code, 200) - self.assertEqual(response.template.name, "Book template") + self.assertEqual(response.templates[0].name, "Book template") self.assertEqual(response.content, "Blink - Malcolm Gladwell") def test_redirect(self): diff --git a/tests/regressiontests/test_client_regress/models.py b/tests/regressiontests/test_client_regress/models.py index 22b59e54a9..20ed3af82f 100644 --- a/tests/regressiontests/test_client_regress/models.py +++ b/tests/regressiontests/test_client_regress/models.py @@ -9,7 +9,7 @@ from django.test import Client, TestCase from django.test.utils import ContextList from django.core.urlresolvers import reverse from django.core.exceptions import SuspiciousOperation -from django.template import TemplateDoesNotExist, TemplateSyntaxError, Context +from django.template import TemplateDoesNotExist, TemplateSyntaxError, Context, Template from django.template import loader from django.test.client import encode_file @@ -861,3 +861,18 @@ class RequestHeadersTest(TestCase): self.assertEquals(response.content, "HTTP_X_ARG_CHECK: Testing 123") self.assertRedirects(response, '/test_client_regress/check_headers/', status_code=301, target_status_code=200) + +class ResponseTemplateDeprecationTests(TestCase): + """ + Response.template still works backwards-compatibly, but with pending deprecation warning. Refs #12226. + + """ + def test_response_template_data(self): + response = self.client.get("/test_client_regress/request_data/", data={'foo':'whiz'}) + self.assertEqual(response.template.__class__, Template) + self.assertEqual(response.template.name, 'base.html') + + def test_response_no_template(self): + response = self.client.get("/test_client_regress/request_methods/") + self.assertEqual(response.template, None) +