Browse Source

Deprecated TEMPLATE_LOADERS.

Aymeric Augustin 10 years ago
parent
commit
cf0fd65ed4

+ 26 - 0
django/contrib/auth/tests/settings.py

@@ -0,0 +1,26 @@
+import os
+
+from django.utils._os import upath
+
+
+AUTH_MIDDLEWARE_CLASSES = (
+    'django.contrib.sessions.middleware.SessionMiddleware',
+    'django.contrib.auth.middleware.AuthenticationMiddleware',
+)
+
+AUTH_TEMPLATES = [{
+    'BACKEND': 'django.template.backends.django.DjangoTemplates',
+    'DIRS': [os.path.join(os.path.dirname(upath(__file__)), 'templates')],
+    'APP_DIRS': True,
+    'OPTIONS': {
+        'context_processors': (
+            'django.contrib.auth.context_processors.auth',
+            'django.template.context_processors.debug',
+            'django.template.context_processors.i18n',
+            'django.template.context_processors.media',
+            'django.template.context_processors.static',
+            'django.template.context_processors.tz',
+            'django.contrib.messages.context_processors.messages',
+        ),
+    },
+}]

+ 7 - 25
django/contrib/auth/tests/test_context_processors.py

@@ -1,13 +1,12 @@
-import os
-
 from django.contrib.auth import authenticate
 from django.contrib.auth import authenticate
-from django.contrib.auth.tests.utils import skipIfCustomUser
 from django.contrib.auth.models import User, Permission
 from django.contrib.auth.models import User, Permission
 from django.contrib.contenttypes.models import ContentType
 from django.contrib.contenttypes.models import ContentType
 from django.contrib.auth.context_processors import PermWrapper, PermLookupDict
 from django.contrib.auth.context_processors import PermWrapper, PermLookupDict
 from django.db.models import Q
 from django.db.models import Q
 from django.test import TestCase, override_settings
 from django.test import TestCase, override_settings
-from django.utils._os import upath
+
+from .settings import AUTH_MIDDLEWARE_CLASSES, AUTH_TEMPLATES
+from .utils import skipIfCustomUser
 
 
 
 
 class MockUser(object):
 class MockUser(object):
@@ -61,17 +60,10 @@ class PermWrapperTests(TestCase):
 
 
 @skipIfCustomUser
 @skipIfCustomUser
 @override_settings(
 @override_settings(
-    TEMPLATE_LOADERS=('django.template.loaders.filesystem.Loader',),
-    TEMPLATE_DIRS=(
-        os.path.join(os.path.dirname(upath(__file__)), 'templates'),
-    ),
-    TEMPLATE_CONTEXT_PROCESSORS=(
-        'django.contrib.auth.context_processors.auth',
-        'django.contrib.messages.context_processors.messages'
-    ),
+    PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',),
     ROOT_URLCONF='django.contrib.auth.tests.urls',
     ROOT_URLCONF='django.contrib.auth.tests.urls',
+    TEMPLATES=AUTH_TEMPLATES,
     USE_TZ=False,                           # required for loading the fixture
     USE_TZ=False,                           # required for loading the fixture
-    PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',),
 )
 )
 class AuthContextProcessorTests(TestCase):
 class AuthContextProcessorTests(TestCase):
     """
     """
@@ -79,12 +71,7 @@ class AuthContextProcessorTests(TestCase):
     """
     """
     fixtures = ['context-processors-users.xml']
     fixtures = ['context-processors-users.xml']
 
 
-    @override_settings(
-        MIDDLEWARE_CLASSES=(
-            'django.contrib.sessions.middleware.SessionMiddleware',
-            'django.contrib.auth.middleware.AuthenticationMiddleware',
-        ),
-    )
+    @override_settings(MIDDLEWARE_CLASSES=AUTH_MIDDLEWARE_CLASSES)
     def test_session_not_accessed(self):
     def test_session_not_accessed(self):
         """
         """
         Tests that the session is not accessed simply by including
         Tests that the session is not accessed simply by including
@@ -93,12 +80,7 @@ class AuthContextProcessorTests(TestCase):
         response = self.client.get('/auth_processor_no_attr_access/')
         response = self.client.get('/auth_processor_no_attr_access/')
         self.assertContains(response, "Session not accessed")
         self.assertContains(response, "Session not accessed")
 
 
-    @override_settings(
-        MIDDLEWARE_CLASSES=(
-            'django.contrib.sessions.middleware.SessionMiddleware',
-            'django.contrib.auth.middleware.AuthenticationMiddleware',
-        ),
-    )
+    @override_settings(MIDDLEWARE_CLASSES=AUTH_MIDDLEWARE_CLASSES)
     def test_session_is_accessed(self):
     def test_session_is_accessed(self):
         """
         """
         Tests that the session is accessed if the auth context processor
         Tests that the session is accessed if the auth context processor

+ 29 - 34
django/contrib/auth/tests/test_forms.py

@@ -1,6 +1,5 @@
 from __future__ import unicode_literals
 from __future__ import unicode_literals
 
 
-import os
 import re
 import re
 
 
 from django import forms
 from django import forms
@@ -8,17 +7,18 @@ from django.contrib.auth.models import User
 from django.contrib.auth.forms import (UserCreationForm, AuthenticationForm,
 from django.contrib.auth.forms import (UserCreationForm, AuthenticationForm,
     PasswordChangeForm, SetPasswordForm, UserChangeForm, PasswordResetForm,
     PasswordChangeForm, SetPasswordForm, UserChangeForm, PasswordResetForm,
     ReadOnlyPasswordHashField, ReadOnlyPasswordHashWidget)
     ReadOnlyPasswordHashField, ReadOnlyPasswordHashWidget)
-from django.contrib.auth.tests.utils import skipIfCustomUser
 from django.core import mail
 from django.core import mail
 from django.core.mail import EmailMultiAlternatives
 from django.core.mail import EmailMultiAlternatives
 from django.forms.fields import Field, CharField
 from django.forms.fields import Field, CharField
 from django.test import TestCase, override_settings
 from django.test import TestCase, override_settings
 from django.utils.encoding import force_text
 from django.utils.encoding import force_text
-from django.utils._os import upath
 from django.utils import translation
 from django.utils import translation
 from django.utils.text import capfirst
 from django.utils.text import capfirst
 from django.utils.translation import ugettext as _
 from django.utils.translation import ugettext as _
 
 
+from .settings import AUTH_TEMPLATES
+from .utils import skipIfCustomUser
+
 
 
 @skipIfCustomUser
 @skipIfCustomUser
 @override_settings(USE_TZ=False, PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',))
 @override_settings(USE_TZ=False, PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',))
@@ -360,10 +360,7 @@ class UserChangeFormTest(TestCase):
 @skipIfCustomUser
 @skipIfCustomUser
 @override_settings(
 @override_settings(
     PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',),
     PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',),
-    TEMPLATE_LOADERS=('django.template.loaders.filesystem.Loader',),
-    TEMPLATE_DIRS=(
-        os.path.join(os.path.dirname(upath(__file__)), 'templates'),
-    ),
+    TEMPLATES=AUTH_TEMPLATES,
     USE_TZ=False,
     USE_TZ=False,
 )
 )
 class PasswordResetFormTest(TestCase):
 class PasswordResetFormTest(TestCase):
@@ -416,33 +413,31 @@ class PasswordResetFormTest(TestCase):
         self.assertEqual(mail.outbox[0].subject, 'Custom password reset on example.com')
         self.assertEqual(mail.outbox[0].subject, 'Custom password reset on example.com')
 
 
     def test_custom_email_constructor(self):
     def test_custom_email_constructor(self):
-        template_path = os.path.join(os.path.dirname(__file__), 'templates')
-        with self.settings(TEMPLATE_DIRS=(template_path,)):
-            data = {'email': 'testclient@example.com'}
-
-            class CustomEmailPasswordResetForm(PasswordResetForm):
-                def send_mail(self, subject_template_name, email_template_name,
-                              context, from_email, to_email,
-                              html_email_template_name=None):
-                    EmailMultiAlternatives(
-                        "Forgot your password?",
-                        "Sorry to hear you forgot your password.",
-                        None, [to_email],
-                        ['site_monitor@example.com'],
-                        headers={'Reply-To': 'webmaster@example.com'},
-                        alternatives=[("Really sorry to hear you forgot your password.",
-                                       "text/html")]).send()
-
-            form = CustomEmailPasswordResetForm(data)
-            self.assertTrue(form.is_valid())
-            # Since we're not providing a request object, we must provide a
-            # domain_override to prevent the save operation from failing in the
-            # potential case where contrib.sites is not installed. Refs #16412.
-            form.save(domain_override='example.com')
-            self.assertEqual(len(mail.outbox), 1)
-            self.assertEqual(mail.outbox[0].subject, 'Forgot your password?')
-            self.assertEqual(mail.outbox[0].bcc, ['site_monitor@example.com'])
-            self.assertEqual(mail.outbox[0].content_subtype, "plain")
+        data = {'email': 'testclient@example.com'}
+
+        class CustomEmailPasswordResetForm(PasswordResetForm):
+            def send_mail(self, subject_template_name, email_template_name,
+                          context, from_email, to_email,
+                          html_email_template_name=None):
+                EmailMultiAlternatives(
+                    "Forgot your password?",
+                    "Sorry to hear you forgot your password.",
+                    None, [to_email],
+                    ['site_monitor@example.com'],
+                    headers={'Reply-To': 'webmaster@example.com'},
+                    alternatives=[("Really sorry to hear you forgot your password.",
+                                   "text/html")]).send()
+
+        form = CustomEmailPasswordResetForm(data)
+        self.assertTrue(form.is_valid())
+        # Since we're not providing a request object, we must provide a
+        # domain_override to prevent the save operation from failing in the
+        # potential case where contrib.sites is not installed. Refs #16412.
+        form.save(domain_override='example.com')
+        self.assertEqual(len(mail.outbox), 1)
+        self.assertEqual(mail.outbox[0].subject, 'Forgot your password?')
+        self.assertEqual(mail.outbox[0].bcc, ['site_monitor@example.com'])
+        self.assertEqual(mail.outbox[0].content_subtype, "plain")
 
 
     def test_preserve_username_case(self):
     def test_preserve_username_case(self):
         """
         """

+ 9 - 13
django/contrib/auth/tests/test_views.py

@@ -1,14 +1,17 @@
 from importlib import import_module
 from importlib import import_module
 import itertools
 import itertools
-import os
 import re
 import re
 import warnings
 import warnings
 
 
 from django.apps import apps
 from django.apps import apps
-from django.conf import global_settings, settings
+from django.conf import settings
 from django.contrib.sites.requests import RequestSite
 from django.contrib.sites.requests import RequestSite
 from django.contrib.admin.models import LogEntry
 from django.contrib.admin.models import LogEntry
+from django.contrib.auth import SESSION_KEY, REDIRECT_FIELD_NAME
+from django.contrib.auth.forms import (AuthenticationForm, PasswordChangeForm,
+    SetPasswordForm)
 from django.contrib.auth.models import User
 from django.contrib.auth.models import User
+from django.contrib.auth.views import login as login_view
 from django.core import mail
 from django.core import mail
 from django.core.urlresolvers import reverse, NoReverseMatch
 from django.core.urlresolvers import reverse, NoReverseMatch
 from django.http import QueryDict, HttpRequest
 from django.http import QueryDict, HttpRequest
@@ -16,19 +19,15 @@ from django.utils.encoding import force_text
 from django.utils.http import urlquote
 from django.utils.http import urlquote
 from django.utils.six.moves.urllib.parse import urlparse, ParseResult
 from django.utils.six.moves.urllib.parse import urlparse, ParseResult
 from django.utils.translation import LANGUAGE_SESSION_KEY
 from django.utils.translation import LANGUAGE_SESSION_KEY
-from django.utils._os import upath
 from django.test import TestCase, override_settings
 from django.test import TestCase, override_settings
 from django.test.utils import patch_logger
 from django.test.utils import patch_logger
 from django.middleware.csrf import CsrfViewMiddleware
 from django.middleware.csrf import CsrfViewMiddleware
 from django.contrib.sessions.middleware import SessionMiddleware
 from django.contrib.sessions.middleware import SessionMiddleware
 
 
-from django.contrib.auth import SESSION_KEY, REDIRECT_FIELD_NAME
-from django.contrib.auth.forms import (AuthenticationForm, PasswordChangeForm,
-                SetPasswordForm)
 # Needed so model is installed when tests are run independently:
 # Needed so model is installed when tests are run independently:
-from django.contrib.auth.tests.custom_user import CustomUser  # NOQA
-from django.contrib.auth.tests.utils import skipIfCustomUser
-from django.contrib.auth.views import login as login_view
+from .custom_user import CustomUser  # NOQA
+from .settings import AUTH_TEMPLATES
+from .utils import skipIfCustomUser
 
 
 
 
 @override_settings(
 @override_settings(
@@ -36,10 +35,7 @@ from django.contrib.auth.views import login as login_view
         ('en', 'English'),
         ('en', 'English'),
     ),
     ),
     LANGUAGE_CODE='en',
     LANGUAGE_CODE='en',
-    TEMPLATE_LOADERS=global_settings.TEMPLATE_LOADERS,
-    TEMPLATE_DIRS=(
-        os.path.join(os.path.dirname(upath(__file__)), 'templates'),
-    ),
+    TEMPLATES=AUTH_TEMPLATES,
     USE_TZ=False,
     USE_TZ=False,
     PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',),
     PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',),
     ROOT_URLCONF='django.contrib.auth.tests.urls',
     ROOT_URLCONF='django.contrib.auth.tests.urls',

+ 3 - 3
django/views/debug.py

@@ -781,7 +781,7 @@ TECHNICAL_500_TEMPLATE = ("""
         {% endfor %}
         {% endfor %}
         </ul>
         </ul>
     {% else %}
     {% else %}
-        <p>Django couldn't find any templates because your <code>TEMPLATE_LOADERS</code> setting is empty!</p>
+        <p>Django couldn't find any templates because your <code>'loaders'</code> option is empty!</p>
     {% endif %}
     {% endif %}
 </div>
 </div>
 {% endif %}
 {% endif %}
@@ -900,7 +900,7 @@ Installed Middleware:
 {% for loader in loader_debug_info %}Using loader {{ loader.loader }}:
 {% for loader in loader_debug_info %}Using loader {{ loader.loader }}:
 {% for t in loader.templates %}{{ t.name }} ({{ t.status }})
 {% for t in loader.templates %}{{ t.name }} ({{ t.status }})
 {% endfor %}{% endfor %}
 {% endfor %}{% endfor %}
-{% else %}Django couldn't find any templates because your TEMPLATE_LOADERS setting is empty!
+{% else %}Django couldn't find any templates because your 'loaders' option is empty!
 {% endif %}
 {% endif %}
 {% endif %}{% if template_info %}
 {% endif %}{% if template_info %}
 Template error:
 Template error:
@@ -1091,7 +1091,7 @@ Installed Middleware:
 {% for loader in loader_debug_info %}Using loader {{ loader.loader }}:
 {% for loader in loader_debug_info %}Using loader {{ loader.loader }}:
 {% for t in loader.templates %}{{ t.name }} ({{ t.status }})
 {% for t in loader.templates %}{{ t.name }} ({{ t.status }})
 {% endfor %}{% endfor %}
 {% endfor %}{% endfor %}
-{% else %}Django couldn't find any templates because your TEMPLATE_LOADERS setting is empty!
+{% else %}Django couldn't find any templates because your 'loaders' option is empty!
 {% endif %}
 {% endif %}
 {% endif %}{% if template_info %}
 {% endif %}{% if template_info %}
 Template error:
 Template error:

+ 2 - 2
docs/howto/deployment/checklist.txt

@@ -178,8 +178,8 @@ processing time.
 
 
 This helps a lot on virtualized hosts with limited network performance.
 This helps a lot on virtualized hosts with limited network performance.
 
 
-:setting:`TEMPLATE_LOADERS`
----------------------------
+:setting:`TEMPLATES`
+--------------------
 
 
 Enabling the cached template loader often improves performance drastically, as
 Enabling the cached template loader often improves performance drastically, as
 it avoids compiling each template every time it needs to be rendered. See the
 it avoids compiling each template every time it needs to be rendered. See the

+ 1 - 0
docs/internals/deprecation.txt

@@ -90,6 +90,7 @@ details on these changes.
 * The following settings will be removed:
 * The following settings will be removed:
 
 
   * ``ALLOWED_INCLUDE_ROOTS``
   * ``ALLOWED_INCLUDE_ROOTS``
+  * ``TEMPLATE_LOADERS``
   * ``TEMPLATE_STRING_IF_INVALID``
   * ``TEMPLATE_STRING_IF_INVALID``
 
 
 * The backwards compatibility alias ``django.template.loader.BaseLoader`` will
 * The backwards compatibility alias ``django.template.loader.BaseLoader`` will

+ 7 - 6
docs/intro/tutorial03.txt

@@ -314,12 +314,13 @@ creating a template that the view can use.
 First, create a directory called ``templates`` in your ``polls`` directory.
 First, create a directory called ``templates`` in your ``polls`` directory.
 Django will look for templates in there.
 Django will look for templates in there.
 
 
-Django's :setting:`TEMPLATE_LOADERS` setting contains a list of callables that
-know how to import templates from various sources. One of the defaults is
-:class:`django.template.loaders.app_directories.Loader` which looks for a
-"templates" subdirectory in each of the :setting:`INSTALLED_APPS` - this is how
-Django knows to find the polls templates even though we didn't modify
-:setting:`TEMPLATE_DIRS`, as we did in :ref:`Tutorial 2
+Your project's :setting:`TEMPLATES` setting describes how Django will load and
+render templates. The default settings file configures a ``DjangoTemplates``
+backend whose :setting:`APP_DIRS <TEMPLATES-APP_DIRS>` option is set to
+``True``. By convention ``DjangoTemplates`` looks for a "templates"
+subdirectory in each of the :setting:`INSTALLED_APPS`. This is how Django
+knows to find the polls templates even though we didn't modify the
+:setting:`DIRS <TEMPLATES-DIRS>` option, as we did in :ref:`Tutorial 2
 <ref-customizing-your-projects-templates>`.
 <ref-customizing-your-projects-templates>`.
 
 
 .. admonition:: Organizing templates
 .. admonition:: Organizing templates

+ 3 - 2
docs/ref/contrib/admin/index.txt

@@ -2337,8 +2337,9 @@ directory.
 
 
 In order to override one or more of them, first create an ``admin`` directory
 In order to override one or more of them, first create an ``admin`` directory
 in your project's ``templates`` directory. This can be any of the directories
 in your project's ``templates`` directory. This can be any of the directories
-you specified in :setting:`TEMPLATE_DIRS`. If you have customized the
-:setting:`TEMPLATE_LOADERS` setting, be sure
+you specified in the :setting:`DIRS <TEMPLATES-DIRS>` option of the
+``DjangoTemplates`` backend in the :setting:`TEMPLATES` setting. If you have
+customized the ``'loaders'`` option, be sure
 ``'django.template.loaders.filesystem.Loader'`` appears before
 ``'django.template.loaders.filesystem.Loader'`` appears before
 ``'django.template.loaders.app_directories.Loader'`` so that your custom
 ``'django.template.loaders.app_directories.Loader'`` so that your custom
 templates will be found by the template loading system before those that are
 templates will be found by the template loading system before those that are

+ 3 - 3
docs/ref/contrib/sitemaps.txt

@@ -34,9 +34,9 @@ To install the sitemap app, follow these steps:
 1. Add ``'django.contrib.sitemaps'`` to your :setting:`INSTALLED_APPS`
 1. Add ``'django.contrib.sitemaps'`` to your :setting:`INSTALLED_APPS`
    setting.
    setting.
 
 
-2. Make sure ``'django.template.loaders.app_directories.Loader'``
-   is in your :setting:`TEMPLATE_LOADERS` setting. It's in there by default,
-   so you'll only need to change this if you've changed that setting.
+2. Make sure your :setting:`TEMPLATES` setting contains a ``DjangoTemplates``
+   backend whose ``APP_DIRS`` options is set to ``True``. It's in there by
+   default, so you'll only need to change this if you've changed that setting.
 
 
 3. Make sure you've installed the
 3. Make sure you've installed the
    :mod:`sites framework <django.contrib.sites>`.
    :mod:`sites framework <django.contrib.sites>`.

+ 5 - 0
docs/ref/settings.txt

@@ -2439,6 +2439,11 @@ Default::
      ('django.template.loaders.filesystem.Loader',
      ('django.template.loaders.filesystem.Loader',
       'django.template.loaders.app_directories.Loader')
       'django.template.loaders.app_directories.Loader')
 
 
+.. deprecated:: 1.8
+
+    Set the ``'loaders'`` option in the :setting:`OPTIONS <TEMPLATES-OPTIONS>`
+    of a ``DjangoTemplates`` backend instead.
+
 A tuple of template loader classes, specified as strings. Each ``Loader`` class
 A tuple of template loader classes, specified as strings. Each ``Loader`` class
 knows how to import templates from a particular source. Optionally, a tuple can be
 knows how to import templates from a particular source. Optionally, a tuple can be
 used instead of a string. The first item in the tuple should be the ``Loader``’s
 used instead of a string. The first item in the tuple should be the ``Loader``’s

+ 46 - 20
docs/ref/templates/api.txt

@@ -734,9 +734,10 @@ with a few other template loaders, which know how to load templates from other
 sources.
 sources.
 
 
 Some of these other loaders are disabled by default, but you can activate them
 Some of these other loaders are disabled by default, but you can activate them
-by editing your :setting:`TEMPLATE_LOADERS` setting. :setting:`TEMPLATE_LOADERS`
-should be a tuple of strings or tuples, where each represents a template loader
-class. Here are the template loaders that come with Django:
+by adding a ``'loaders'`` option to your ``DjangoTemplates`` backend in the
+:setting:`TEMPLATES` setting. ``'loaders'`` should be a list of strings or
+tuples, where each represents a template loader class. Here are the template
+loaders that come with Django:
 
 
 .. currentmodule:: django.template.loaders
 .. currentmodule:: django.template.loaders
 
 
@@ -744,8 +745,16 @@ class. Here are the template loaders that come with Django:
 
 
 .. class:: filesystem.Loader
 .. class:: filesystem.Loader
 
 
-    Loads templates from the filesystem, according to :setting:`TEMPLATE_DIRS`.
-    This loader is enabled by default.
+    Loads templates from the filesystem, according to
+    :setting:`DIRS <TEMPLATES-DIRS>`.
+
+    This loader is enabled by default. However it won't find any templates
+    until you set :setting:`DIRS <TEMPLATES-DIRS>` to a non-empty list::
+
+        TEMPLATES = [{
+            'BACKEND': 'django.template.backends.django.DjangoTemplates',
+            'DIRS': [os.path.join(BASE_DIR, 'templates')],
+        }]
 
 
 ``django.template.loaders.app_directories.Loader``
 ``django.template.loaders.app_directories.Loader``
 
 
@@ -782,7 +791,14 @@ class. Here are the template loaders that come with Django:
     it caches a list of which :setting:`INSTALLED_APPS` packages have a
     it caches a list of which :setting:`INSTALLED_APPS` packages have a
     ``templates`` subdirectory.
     ``templates`` subdirectory.
 
 
-    This loader is enabled by default.
+    This loader is enabled if and only if :setting:`APP_DIRS
+    <TEMPLATES-APP_DIRS>` is set::
+
+        TEMPLATES = [{
+            'BACKEND': 'django.template.backends.django.DjangoTemplates',
+            'APP_DIRS': True,
+        }]
+
 
 
 ``django.template.loaders.eggs.Loader``
 ``django.template.loaders.eggs.Loader``
 
 
@@ -810,12 +826,18 @@ class. Here are the template loaders that come with Django:
     For example, to enable template caching with the ``filesystem`` and
     For example, to enable template caching with the ``filesystem`` and
     ``app_directories`` template loaders you might use the following settings::
     ``app_directories`` template loaders you might use the following settings::
 
 
-        TEMPLATE_LOADERS = (
-            ('django.template.loaders.cached.Loader', (
-                'django.template.loaders.filesystem.Loader',
-                'django.template.loaders.app_directories.Loader',
-            )),
-        )
+        TEMPLATES = [{
+            'BACKEND': 'django.template.backends.django.DjangoTemplates',
+            'DIRS': [os.path.join(BASE_DIR, 'templates')],
+            'OPTIONS': {
+                'loaders': [
+                    ('django.template.loaders.cached.Loader', (
+                        'django.template.loaders.filesystem.Loader',
+                        'django.template.loaders.app_directories.Loader',
+                    )),
+                ],
+            },
+        }]
 
 
     .. note::
     .. note::
 
 
@@ -838,17 +860,21 @@ class. Here are the template loaders that come with Django:
 
 
     This loader takes a dictionary of templates as its first argument::
     This loader takes a dictionary of templates as its first argument::
 
 
-        TEMPLATE_LOADERS = (
-            ('django.template.loaders.locmem.Loader', {
-                'index.html': 'content here',
-            }),
-        )
+        TEMPLATES = [{
+            'BACKEND': 'django.template.backends.django.DjangoTemplates',
+            'OPTIONS': {
+                'loaders': [
+                    ('django.template.loaders.locmem.Loader', {
+                        'index.html': 'content here',
+                    }),
+                ],
+            },
+        }]
 
 
     This loader is disabled by default.
     This loader is disabled by default.
 
 
-Django uses the template loaders in order according to the
-:setting:`TEMPLATE_LOADERS` setting. It uses each loader until a loader finds a
-match.
+Django uses the template loaders in order according to the ``'loaders'``
+option. It uses each loader until a loader finds a match.
 
 
 .. currentmodule:: django.template
 .. currentmodule:: django.template
 
 

+ 3 - 3
docs/releases/1.2.txt

@@ -267,9 +267,9 @@ opposed to functions, the only method available until Django 1.1.
 All the template loaders :ref:`shipped with Django <template-loaders>` have
 All the template loaders :ref:`shipped with Django <template-loaders>` have
 been ported to the new API but they still implement the function-based API and
 been ported to the new API but they still implement the function-based API and
 the template core machinery still accepts function-based loaders (builtin or
 the template core machinery still accepts function-based loaders (builtin or
-third party) so there is no immediate need to modify your
-:setting:`TEMPLATE_LOADERS` setting in existing projects, things will keep
-working if you leave it untouched up to and including the Django 1.3 release.
+third party) so there is no immediate need to modify your ``TEMPLATE_LOADERS``
+setting in existing projects, things will keep working if you leave it
+untouched up to and including the Django 1.3 release.
 
 
 If you have developed your own custom template loaders we suggest to consider
 If you have developed your own custom template loaders we suggest to consider
 porting them to a class-based implementation because the code for backwards
 porting them to a class-based implementation because the code for backwards

+ 2 - 1
docs/releases/1.8.txt

@@ -917,7 +917,7 @@ Miscellaneous
   session store always fetches the most current session data.
   session store always fetches the most current session data.
 
 
 * Private APIs ``override_template_loaders`` and ``override_with_test_loader``
 * Private APIs ``override_template_loaders`` and ``override_with_test_loader``
-  in ``django.test.utils`` were removed. Override ``TEMPLATE_LOADERS`` with
+  in ``django.test.utils`` were removed. Override ``TEMPLATES`` with
   ``override_settings`` instead.
   ``override_settings`` instead.
 
 
 * Warnings from the MySQL database backend are no longer converted to
 * Warnings from the MySQL database backend are no longer converted to
@@ -1021,6 +1021,7 @@ As a consequence of the multiple template engines refactor, several settings
 are deprecated in favor of :setting:`TEMPLATES`:
 are deprecated in favor of :setting:`TEMPLATES`:
 
 
 * ``ALLOWED_INCLUDE_ROOTS``
 * ``ALLOWED_INCLUDE_ROOTS``
+* ``TEMPLATE_LOADERS``
 * ``TEMPLATE_STRING_IF_INVALID``
 * ``TEMPLATE_STRING_IF_INVALID``
 
 
 ``django.core.context_processors``
 ``django.core.context_processors``

+ 3 - 4
docs/topics/class-based-views/generic-display.txt

@@ -136,10 +136,9 @@ bit is just the lowercased version of the model's name.
 
 
 .. note::
 .. note::
 
 
-    Thus, when (for example) the
-    :class:`django.template.loaders.app_directories.Loader` template loader is
-    enabled in :setting:`TEMPLATE_LOADERS`, a template location could be:
-    /path/to/project/books/templates/books/publisher_list.html
+    Thus, when (for example) the ``APP_DIRS`` option of a ``DjangoTemplates``
+    backend is set to True in :setting:`TEMPLATES`, a template location could
+    be: /path/to/project/books/templates/books/publisher_list.html
 
 
 This template will be rendered against a context containing a variable called
 This template will be rendered against a context containing a variable called
 ``object_list`` that contains all the publisher objects. A very simple template
 ``object_list`` that contains all the publisher objects. A very simple template

+ 1 - 2
docs/topics/testing/tools.txt

@@ -1303,8 +1303,7 @@ Django itself uses this signal to reset various data:
 Overridden settings              Data reset
 Overridden settings              Data reset
 ================================ ========================
 ================================ ========================
 USE_TZ, TIME_ZONE                Databases timezone
 USE_TZ, TIME_ZONE                Databases timezone
-TEMPLATE_CONTEXT_PROCESSORS      Context processors cache
-TEMPLATE_LOADERS                 Template loaders cache
+TEMPLATES                        Template engines
 SERIALIZATION_MODULES            Serializers cache
 SERIALIZATION_MODULES            Serializers cache
 LOCALE_PATHS, LANGUAGE_CODE      Default translation and loaded translations
 LOCALE_PATHS, LANGUAGE_CODE      Default translation and loaded translations
 MEDIA_ROOT, DEFAULT_FILE_STORAGE Default file storage
 MEDIA_ROOT, DEFAULT_FILE_STORAGE Default file storage

+ 4 - 4
tests/settings_tests/tests.py

@@ -431,14 +431,14 @@ class IsOverriddenTest(TestCase):
             s = Settings('fake_settings_module')
             s = Settings('fake_settings_module')
 
 
             self.assertTrue(s.is_overridden('SECRET_KEY'))
             self.assertTrue(s.is_overridden('SECRET_KEY'))
-            self.assertFalse(s.is_overridden('TEMPLATE_LOADERS'))
+            self.assertFalse(s.is_overridden('ALLOWED_HOSTS'))
         finally:
         finally:
             del sys.modules['fake_settings_module']
             del sys.modules['fake_settings_module']
 
 
     def test_override(self):
     def test_override(self):
-        self.assertFalse(settings.is_overridden('TEMPLATE_LOADERS'))
-        with override_settings(TEMPLATE_LOADERS=[]):
-            self.assertTrue(settings.is_overridden('TEMPLATE_LOADERS'))
+        self.assertFalse(settings.is_overridden('ALLOWED_HOSTS'))
+        with override_settings(ALLOWED_HOSTS=[]):
+            self.assertTrue(settings.is_overridden('ALLOWED_HOSTS'))
 
 
 
 
 class TestTupleSettings(unittest.TestCase):
 class TestTupleSettings(unittest.TestCase):

+ 64 - 50
tests/template_tests/test_loaders.py

@@ -16,7 +16,7 @@ except ImportError:
 
 
 
 
 from django.template import TemplateDoesNotExist, Context
 from django.template import TemplateDoesNotExist, Context
-from django.template.loaders.eggs import Loader as EggLoader
+from django.template.loaders import cached, eggs
 from django.template.engine import Engine
 from django.template.engine import Engine
 from django.template import loader
 from django.template import loader
 from django.test import SimpleTestCase, override_settings
 from django.test import SimpleTestCase, override_settings
@@ -26,6 +26,11 @@ from django.utils._os import upath
 from django.utils.six import StringIO
 from django.utils.six import StringIO
 
 
 
 
+TEMPLATES_DIR = os.path.join(os.path.dirname(upath(__file__)), 'templates')
+
+GLOBAL_TEMPLATES_DIR = os.path.join(os.path.dirname(os.path.dirname(upath(__file__))), 'templates')
+
+
 # Mock classes and objects for pkg_resources functions.
 # Mock classes and objects for pkg_resources functions.
 class MockLoader(object):
 class MockLoader(object):
     pass
     pass
@@ -48,7 +53,10 @@ def create_egg(name, resources):
 
 
 @unittest.skipUnless(pkg_resources, 'setuptools is not installed')
 @unittest.skipUnless(pkg_resources, 'setuptools is not installed')
 class EggLoaderTest(SimpleTestCase):
 class EggLoaderTest(SimpleTestCase):
+
     def setUp(self):
     def setUp(self):
+        self.loader = eggs.Loader(Engine.get_default())
+
         # Defined here b/c at module scope we may not have pkg_resources
         # Defined here b/c at module scope we may not have pkg_resources
         class MockProvider(pkg_resources.NullProvider):
         class MockProvider(pkg_resources.NullProvider):
             def __init__(self, module):
             def __init__(self, module):
@@ -81,70 +89,64 @@ class EggLoaderTest(SimpleTestCase):
     @override_settings(INSTALLED_APPS=['egg_empty'])
     @override_settings(INSTALLED_APPS=['egg_empty'])
     def test_empty(self):
     def test_empty(self):
         "Loading any template on an empty egg should fail"
         "Loading any template on an empty egg should fail"
-        egg_loader = EggLoader(Engine.get_default())
-        self.assertRaises(TemplateDoesNotExist, egg_loader.load_template_source, "not-existing.html")
+        with self.assertRaises(TemplateDoesNotExist):
+            self.loader.load_template_source("not-existing.html")
 
 
     @override_settings(INSTALLED_APPS=['egg_1'])
     @override_settings(INSTALLED_APPS=['egg_1'])
     def test_non_existing(self):
     def test_non_existing(self):
         "Template loading fails if the template is not in the egg"
         "Template loading fails if the template is not in the egg"
-        egg_loader = EggLoader(Engine.get_default())
-        self.assertRaises(TemplateDoesNotExist, egg_loader.load_template_source, "not-existing.html")
+        with self.assertRaises(TemplateDoesNotExist):
+            self.loader.load_template_source("not-existing.html")
 
 
     @override_settings(INSTALLED_APPS=['egg_1'])
     @override_settings(INSTALLED_APPS=['egg_1'])
     def test_existing(self):
     def test_existing(self):
         "A template can be loaded from an egg"
         "A template can be loaded from an egg"
-        egg_loader = EggLoader(Engine.get_default())
-        contents, template_name = egg_loader.load_template_source("y.html")
+        contents, template_name = self.loader.load_template_source("y.html")
         self.assertEqual(contents, "y")
         self.assertEqual(contents, "y")
         self.assertEqual(template_name, "egg:egg_1:templates/y.html")
         self.assertEqual(template_name, "egg:egg_1:templates/y.html")
 
 
     def test_not_installed(self):
     def test_not_installed(self):
         "Loading an existent template from an egg not included in any app should fail"
         "Loading an existent template from an egg not included in any app should fail"
-        egg_loader = EggLoader(Engine.get_default())
-        self.assertRaises(TemplateDoesNotExist, egg_loader.load_template_source, "y.html")
+        with self.assertRaises(TemplateDoesNotExist):
+            self.loader.load_template_source("y.html")
 
 
 
 
-@override_settings(
-    TEMPLATE_LOADERS=(
-        ('django.template.loaders.cached.Loader', (
-            'django.template.loaders.filesystem.Loader',
-        )),
-    )
-)
 class CachedLoader(SimpleTestCase):
 class CachedLoader(SimpleTestCase):
+
+    def setUp(self):
+        self.loader = cached.Loader(Engine.get_default(), [
+            'django.template.loaders.filesystem.Loader',
+        ])
+
     def test_templatedir_caching(self):
     def test_templatedir_caching(self):
         "Check that the template directories form part of the template cache key. Refs #13573"
         "Check that the template directories form part of the template cache key. Refs #13573"
-        template_loader = Engine.get_default().template_loaders[0]
-
         # Retrieve a template specifying a template directory to check
         # Retrieve a template specifying a template directory to check
-        t1, name = template_loader.find_template('test.html', (os.path.join(os.path.dirname(upath(__file__)), 'templates', 'first'),))
+        t1, name = self.loader.find_template('test.html', (os.path.join(TEMPLATES_DIR, 'first'),))
         # Now retrieve the same template name, but from a different directory
         # Now retrieve the same template name, but from a different directory
-        t2, name = template_loader.find_template('test.html', (os.path.join(os.path.dirname(upath(__file__)), 'templates', 'second'),))
+        t2, name = self.loader.find_template('test.html', (os.path.join(TEMPLATES_DIR, 'second'),))
 
 
         # The two templates should not have the same content
         # The two templates should not have the same content
         self.assertNotEqual(t1.render(Context({})), t2.render(Context({})))
         self.assertNotEqual(t1.render(Context({})), t2.render(Context({})))
 
 
     def test_missing_template_is_cached(self):
     def test_missing_template_is_cached(self):
         "#19949 -- Check that the missing template is cached."
         "#19949 -- Check that the missing template is cached."
-        template_loader = Engine.get_default().template_loaders[0]
-        # Empty cache, which may be filled from previous tests.
-        template_loader.reset()
         # Check that 'missing.html' isn't already in cache before 'missing.html' is loaded
         # Check that 'missing.html' isn't already in cache before 'missing.html' is loaded
-        self.assertRaises(KeyError, lambda: template_loader.template_cache["missing.html"])
+        with self.assertRaises(KeyError):
+            self.loader.template_cache["missing.html"]
         # Try to load it, it should fail
         # Try to load it, it should fail
-        self.assertRaises(TemplateDoesNotExist, template_loader.load_template, "missing.html")
+        with self.assertRaises(TemplateDoesNotExist):
+            self.loader.load_template("missing.html")
         # Verify that the fact that the missing template, which hasn't been found, has actually
         # Verify that the fact that the missing template, which hasn't been found, has actually
         # been cached:
         # been cached:
-        self.assertEqual(template_loader.template_cache.get("missing.html"),
-                         TemplateDoesNotExist,
+        cached_miss = self.loader.template_cache["missing.html"]
+        self.assertEqual(cached_miss, TemplateDoesNotExist,
                          "Cached template loader doesn't cache file lookup misses. It should.")
                          "Cached template loader doesn't cache file lookup misses. It should.")
 
 
 
 
-@override_settings(
-    TEMPLATE_DIRS=(
-        os.path.join(os.path.dirname(upath(__file__)), 'templates'),
-    )
-)
+@override_settings(TEMPLATES=[{
+    'BACKEND': 'django.template.backends.django.DjangoTemplates',
+    'DIRS': [TEMPLATES_DIR],
+}])
 class RenderToStringTest(SimpleTestCase):
 class RenderToStringTest(SimpleTestCase):
     def test_basic(self):
     def test_basic(self):
         self.assertEqual(loader.render_to_string('test_context.html'), 'obj:\n')
         self.assertEqual(loader.render_to_string('test_context.html'), 'obj:\n')
@@ -164,11 +166,10 @@ class RenderToStringTest(SimpleTestCase):
             loader.select_template, [])
             loader.select_template, [])
 
 
 
 
-@override_settings(
-    TEMPLATE_DIRS=(
-        os.path.join(os.path.dirname(upath(__file__)), 'templates'),
-    )
-)
+@override_settings(TEMPLATES=[{
+    'BACKEND': 'django.template.backends.django.DjangoTemplates',
+    'DIRS': [TEMPLATES_DIR],
+}])
 class DeprecatedRenderToStringTest(IgnorePendingDeprecationWarningsMixin, SimpleTestCase):
 class DeprecatedRenderToStringTest(IgnorePendingDeprecationWarningsMixin, SimpleTestCase):
 
 
     def test_existing_context_kept_clean(self):
     def test_existing_context_kept_clean(self):
@@ -191,7 +192,10 @@ class DeprecatedRenderToStringTest(IgnorePendingDeprecationWarningsMixin, Simple
             loader.render_to_string('test_context_stack.html', context_instance=Context()).strip())
             loader.render_to_string('test_context_stack.html', context_instance=Context()).strip())
 
 
 
 
-class TemplateDirsOverrideTest(IgnorePendingDeprecationWarningsMixin, unittest.TestCase):
+@override_settings(TEMPLATES=[{
+    'BACKEND': 'django.template.backends.django.DjangoTemplates',
+}])
+class TemplateDirsOverrideTest(IgnorePendingDeprecationWarningsMixin, SimpleTestCase):
 
 
     dirs_tuple = (os.path.join(os.path.dirname(upath(__file__)), 'other_templates'),)
     dirs_tuple = (os.path.join(os.path.dirname(upath(__file__)), 'other_templates'),)
     dirs_list = list(dirs_tuple)
     dirs_list = list(dirs_tuple)
@@ -212,14 +216,18 @@ class TemplateDirsOverrideTest(IgnorePendingDeprecationWarningsMixin, unittest.T
             self.assertEqual(template.render(Context({})), 'spam eggs\n')
             self.assertEqual(template.render(Context({})), 'spam eggs\n')
 
 
 
 
-@override_settings(
-    TEMPLATE_LOADERS=(
-        ('django.template.loaders.cached.Loader', (
-            'django.template.loaders.filesystem.Loader',
-            'django.template.loaders.app_directories.Loader',
-        )),
-    )
-)
+@override_settings(TEMPLATES=[{
+    'BACKEND': 'django.template.backends.django.DjangoTemplates',
+    'DIRS': [GLOBAL_TEMPLATES_DIR],
+    'OPTIONS': {
+        'loaders': [
+            ('django.template.loaders.cached.Loader', [
+                'django.template.loaders.filesystem.Loader',
+                'django.template.loaders.app_directories.Loader',
+            ]),
+        ],
+    },
+}])
 class PriorityCacheLoader(SimpleTestCase):
 class PriorityCacheLoader(SimpleTestCase):
     def test_basic(self):
     def test_basic(self):
         """
         """
@@ -229,10 +237,16 @@ class PriorityCacheLoader(SimpleTestCase):
         self.assertEqual(t1.render(Context({})), 'priority\n')
         self.assertEqual(t1.render(Context({})), 'priority\n')
 
 
 
 
-@override_settings(
-    TEMPLATE_LOADERS=('django.template.loaders.filesystem.Loader',
-                      'django.template.loaders.app_directories.Loader',),
-)
+@override_settings(TEMPLATES=[{
+    'BACKEND': 'django.template.backends.django.DjangoTemplates',
+    'DIRS': [GLOBAL_TEMPLATES_DIR],
+    'OPTIONS': {
+        'loaders': [
+            'django.template.loaders.filesystem.Loader',
+            'django.template.loaders.app_directories.Loader',
+        ],
+    },
+}])
 class PriorityLoader(SimpleTestCase):
 class PriorityLoader(SimpleTestCase):
     def test_basic(self):
     def test_basic(self):
         """
         """

+ 65 - 34
tests/template_tests/tests.py

@@ -17,6 +17,11 @@ from django.test.utils import override_settings, extend_sys_path
 from django.utils._os import upath
 from django.utils._os import upath
 
 
 
 
+TESTS_DIR = os.path.dirname(os.path.dirname(os.path.abspath(upath(__file__))))
+
+TEMPLATES_DIR = os.path.join(TESTS_DIR, 'templates')
+
+
 class TemplateLoaderTests(SimpleTestCase):
 class TemplateLoaderTests(SimpleTestCase):
 
 
     def test_loaders_security(self):
     def test_loaders_security(self):
@@ -73,7 +78,10 @@ class TemplateLoaderTests(SimpleTestCase):
             test_template_sources('/DIR1/index.HTML', template_dirs,
             test_template_sources('/DIR1/index.HTML', template_dirs,
                                   ['/DIR1/index.HTML'])
                                   ['/DIR1/index.HTML'])
 
 
-    @override_settings(TEMPLATE_LOADERS=['django.template.loaders.filesystem.Loader'])
+    @override_settings(TEMPLATES=[{
+        'BACKEND': 'django.template.backends.django.DjangoTemplates',
+        'DIRS': [TEMPLATES_DIR],
+    }])
     # Turn TEMPLATE_DEBUG on, so that the origin file name will be kept with
     # Turn TEMPLATE_DEBUG on, so that the origin file name will be kept with
     # the compiled templates.
     # the compiled templates.
     @override_settings(TEMPLATE_DEBUG=True)
     @override_settings(TEMPLATE_DEBUG=True)
@@ -90,10 +98,17 @@ class TemplateLoaderTests(SimpleTestCase):
         self.assertTrue(template_name.endswith(load_name),
         self.assertTrue(template_name.endswith(load_name),
             'Template loaded by filesystem loader has incorrect name for debug page: %s' % template_name)
             'Template loaded by filesystem loader has incorrect name for debug page: %s' % template_name)
 
 
-    @override_settings(TEMPLATE_LOADERS=[
-        ('django.template.loaders.cached.Loader',
-            ['django.template.loaders.filesystem.Loader']),
-    ])
+    @override_settings(TEMPLATES=[{
+        'BACKEND': 'django.template.backends.django.DjangoTemplates',
+        'DIRS': [TEMPLATES_DIR],
+        'OPTIONS': {
+            'loaders': [
+                ('django.template.loaders.cached.Loader', [
+                    'django.template.loaders.filesystem.Loader',
+                ]),
+            ],
+        },
+    }])
     @override_settings(TEMPLATE_DEBUG=True)
     @override_settings(TEMPLATE_DEBUG=True)
     def test_cached_loader_debug_origin(self):
     def test_cached_loader_debug_origin(self):
         # Same comment as in test_loader_debug_origin.
         # Same comment as in test_loader_debug_origin.
@@ -130,7 +145,10 @@ class TemplateLoaderTests(SimpleTestCase):
     # Test the base loader class via the app loader. load_template
     # Test the base loader class via the app loader. load_template
     # from base is used by all shipped loaders excepting cached,
     # from base is used by all shipped loaders excepting cached,
     # which has its own test.
     # which has its own test.
-    @override_settings(TEMPLATE_LOADERS=['django.template.loaders.app_directories.Loader'])
+    @override_settings(TEMPLATES=[{
+        'BACKEND': 'django.template.backends.django.DjangoTemplates',
+        'APP_DIRS': True,
+    }])
     def test_include_missing_template(self):
     def test_include_missing_template(self):
         """
         """
         Tests that the correct template is identified as not existing
         Tests that the correct template is identified as not existing
@@ -151,7 +169,10 @@ class TemplateLoaderTests(SimpleTestCase):
     # Test the base loader class via the app loader. load_template
     # Test the base loader class via the app loader. load_template
     # from base is used by all shipped loaders excepting cached,
     # from base is used by all shipped loaders excepting cached,
     # which has its own test.
     # which has its own test.
-    @override_settings(TEMPLATE_LOADERS=['django.template.loaders.app_directories.Loader'])
+    @override_settings(TEMPLATES=[{
+        'BACKEND': 'django.template.backends.django.DjangoTemplates',
+        'APP_DIRS': True,
+    }])
     def test_extends_include_missing_baseloader(self):
     def test_extends_include_missing_baseloader(self):
         """
         """
         Tests that the correct template is identified as not existing
         Tests that the correct template is identified as not existing
@@ -168,34 +189,39 @@ class TemplateLoaderTests(SimpleTestCase):
             self.assertEqual(e.args[0], 'missing.html')
             self.assertEqual(e.args[0], 'missing.html')
         self.assertEqual(r, None, 'Template rendering unexpectedly succeeded, produced: ->%r<-' % r)
         self.assertEqual(r, None, 'Template rendering unexpectedly succeeded, produced: ->%r<-' % r)
 
 
+    @override_settings(TEMPLATES=[{
+        'BACKEND': 'django.template.backends.django.DjangoTemplates',
+        'OPTIONS': {
+            'loaders': [
+                ('django.template.loaders.cached.Loader', [
+                    'django.template.loaders.app_directories.Loader',
+                ]),
+            ],
+        },
+    }])
     @override_settings(TEMPLATE_DEBUG=True)
     @override_settings(TEMPLATE_DEBUG=True)
     def test_extends_include_missing_cachedloader(self):
     def test_extends_include_missing_cachedloader(self):
         """
         """
         Same as test_extends_include_missing_baseloader, only tests
         Same as test_extends_include_missing_baseloader, only tests
         behavior of the cached loader instead of base loader.
         behavior of the cached loader instead of base loader.
         """
         """
-        with override_settings(TEMPLATE_LOADERS=[
-            ('django.template.loaders.cached.Loader', [
-                'django.template.loaders.app_directories.Loader',
-            ]),
-        ]):
-            load_name = 'test_extends_error.html'
-            tmpl = loader.get_template(load_name)
-            r = None
-            try:
-                r = tmpl.render(template.Context({}))
-            except template.TemplateDoesNotExist as e:
-                self.assertEqual(e.args[0], 'missing.html')
-            self.assertEqual(r, None, 'Template rendering unexpectedly succeeded, produced: ->%r<-' % r)
-
-            # For the cached loader, repeat the test, to ensure the first attempt did not cache a
-            # result that behaves incorrectly on subsequent attempts.
-            tmpl = loader.get_template(load_name)
-            try:
-                tmpl.render(template.Context({}))
-            except template.TemplateDoesNotExist as e:
-                self.assertEqual(e.args[0], 'missing.html')
-            self.assertEqual(r, None, 'Template rendering unexpectedly succeeded, produced: ->%r<-' % r)
+        load_name = 'test_extends_error.html'
+        tmpl = loader.get_template(load_name)
+        r = None
+        try:
+            r = tmpl.render(template.Context({}))
+        except template.TemplateDoesNotExist as e:
+            self.assertEqual(e.args[0], 'missing.html')
+        self.assertEqual(r, None, 'Template rendering unexpectedly succeeded, produced: ->%r<-' % r)
+
+        # For the cached loader, repeat the test, to ensure the first attempt did not cache a
+        # result that behaves incorrectly on subsequent attempts.
+        tmpl = loader.get_template(load_name)
+        try:
+            tmpl.render(template.Context({}))
+        except template.TemplateDoesNotExist as e:
+            self.assertEqual(e.args[0], 'missing.html')
+        self.assertEqual(r, None, 'Template rendering unexpectedly succeeded, produced: ->%r<-' % r)
 
 
     def test_include_template_argument(self):
     def test_include_template_argument(self):
         """
         """
@@ -429,11 +455,16 @@ class RequestContextTests(unittest.TestCase):
     def setUp(self):
     def setUp(self):
         self.fake_request = RequestFactory().get('/')
         self.fake_request = RequestFactory().get('/')
 
 
-    @override_settings(TEMPLATE_LOADERS=[
-        ('django.template.loaders.locmem.Loader', {
-            'child': '{{ var|default:"none" }}',
-        }),
-    ])
+    @override_settings(TEMPLATES=[{
+        'BACKEND': 'django.template.backends.django.DjangoTemplates',
+        'OPTIONS': {
+            'loaders': [
+                ('django.template.loaders.locmem.Loader', {
+                    'child': '{{ var|default:"none" }}',
+                }),
+            ],
+        },
+    }])
     def test_include_only(self):
     def test_include_only(self):
         """
         """
         Regression test for #15721, ``{% include %}`` and ``RequestContext``
         Regression test for #15721, ``{% include %}`` and ``RequestContext``

+ 20 - 13
tests/view_tests/tests/test_debug.py

@@ -63,21 +63,28 @@ class DebugViewTests(TestCase):
         response = self.client.get('/raises400/')
         response = self.client.get('/raises400/')
         self.assertContains(response, '<div class="context" id="', status_code=400)
         self.assertContains(response, '<div class="context" id="', status_code=400)
 
 
+    # Ensure no 403.html template exists to test the default case.
+    @override_settings(TEMPLATES=[{
+        'BACKEND': 'django.template.backends.django.DjangoTemplates',
+    }])
     def test_403(self):
     def test_403(self):
-        # Ensure no 403.html template exists to test the default case.
-        with override_settings(TEMPLATE_LOADERS=[]):
-            response = self.client.get('/raises403/')
-            self.assertContains(response, '<h1>403 Forbidden</h1>', status_code=403)
-
+        response = self.client.get('/raises403/')
+        self.assertContains(response, '<h1>403 Forbidden</h1>', status_code=403)
+
+    # Set up a test 403.html template.
+    @override_settings(TEMPLATES=[{
+        'BACKEND': 'django.template.backends.django.DjangoTemplates',
+        'OPTIONS': {
+            'loaders': [
+                ('django.template.loaders.locmem.Loader', {
+                    '403.html': 'This is a test template for a 403 error.',
+                }),
+            ],
+        },
+    }])
     def test_403_template(self):
     def test_403_template(self):
-        # Set up a test 403.html template.
-        with override_settings(TEMPLATE_LOADERS=[
-            ('django.template.loaders.locmem.Loader', {
-                '403.html': 'This is a test template for a 403 Forbidden error.',
-            })
-        ]):
-            response = self.client.get('/raises403/')
-            self.assertContains(response, 'test template', status_code=403)
+        response = self.client.get('/raises403/')
+        self.assertContains(response, 'test template', status_code=403)
 
 
     def test_404(self):
     def test_404(self):
         response = self.client.get('/raises404/')
         response = self.client.get('/raises404/')

+ 15 - 10
tests/view_tests/tests/test_defaults.py

@@ -35,21 +35,26 @@ class DefaultsTests(TestCase):
         response = self.client.get('/server_error/')
         response = self.client.get('/server_error/')
         self.assertEqual(response.status_code, 500)
         self.assertEqual(response.status_code, 500)
 
 
+    @override_settings(TEMPLATES=[{
+        'BACKEND': 'django.template.backends.django.DjangoTemplates',
+        'OPTIONS': {
+            'loaders': [
+                ('django.template.loaders.locmem.Loader', {
+                    '404.html': 'This is a test template for a 404 error.',
+                    '500.html': 'This is a test template for a 500 error.',
+                }),
+            ],
+        },
+    }])
     def test_custom_templates(self):
     def test_custom_templates(self):
         """
         """
         Test that 404.html and 500.html templates are picked by their respective
         Test that 404.html and 500.html templates are picked by their respective
         handler.
         handler.
         """
         """
-        with override_settings(TEMPLATE_LOADERS=[
-            ('django.template.loaders.locmem.Loader', {
-                '404.html': 'This is a test template for a 404 error.',
-                '500.html': 'This is a test template for a 500 error.',
-            }),
-        ]):
-            for code, url in ((404, '/non_existing_url/'), (500, '/server_error/')):
-                response = self.client.get(url)
-                self.assertContains(response, "test template for a %d error" % code,
-                    status_code=code)
+        for code, url in ((404, '/non_existing_url/'), (500, '/server_error/')):
+            response = self.client.get(url)
+            self.assertContains(response, "test template for a %d error" % code,
+                status_code=code)
 
 
     def test_get_absolute_url_attributes(self):
     def test_get_absolute_url_attributes(self):
         "A model can set attributes on the get_absolute_url method"
         "A model can set attributes on the get_absolute_url method"