diff --git a/django/template/engine.py b/django/template/engine.py index 600c74ee24..257042d497 100644 --- a/django/template/engine.py +++ b/django/template/engine.py @@ -2,12 +2,13 @@ import warnings from django.conf import settings from django.core.exceptions import ImproperlyConfigured +from django.utils import lru_cache +from django.utils import six from django.utils.deprecation import RemovedInDjango20Warning from django.utils.functional import cached_property -from django.utils import lru_cache +from django.utils.module_loading import import_string from .base import Context, Template, TemplateDoesNotExist -from .loaders.utils import get_template_loaders _dirs_undefined = object() @@ -54,7 +55,49 @@ class Engine(object): @cached_property def template_loaders(self): - return get_template_loaders(self.loaders) + return self.get_template_loaders(self.loaders) + + def get_template_loaders(self, template_loaders): + loaders = [] + for template_loader in template_loaders: + loader = self.find_template_loader(template_loader) + if loader is not None: + loaders.append(loader) + return loaders + + def find_template_loader(self, loader): + if isinstance(loader, (tuple, list)): + args = list(loader[1:]) + loader = loader[0] + else: + args = [] + + if isinstance(loader, six.string_types): + loader_class = import_string(loader) + + if getattr(loader_class, '_accepts_engine_in_init', False): + args.insert(0, self) + else: + warnings.warn( + "%s inherits from django.template.loader.BaseLoader " + "instead of django.template.loaders.base.Loader. " % + loader, RemovedInDjango20Warning, stacklevel=2) + + loader_instance = loader_class(*args) + + if not loader_instance.is_usable: + warnings.warn( + "Your template loaders configuration includes %r, but " + "your Python installation doesn't support that type of " + "template loading. Consider removing that line from " + "your settings." % loader) + return None + else: + return loader_instance + + else: + raise ImproperlyConfigured( + "Invalid value in template loaders configuration: %r" % loader) def find_template(self, name, dirs=None): # Inner import to avoid circular dependency diff --git a/django/template/loader.py b/django/template/loader.py index 0c24e3d373..eb89ab5900 100644 --- a/django/template/loader.py +++ b/django/template/loader.py @@ -48,10 +48,11 @@ from .loaders import base class BaseLoader(base.Loader): + _accepts_engine_in_init = False def __init__(self, *args, **kwargs): warnings.warn( - "django.template.loader.BaseLoader was renamed to " + "django.template.loader.BaseLoader was superseded by " "django.template.loaders.base.Loader.", RemovedInDjango20Warning, stacklevel=2) super(BaseLoader, self).__init__(*args, **kwargs) diff --git a/django/template/loaders/base.py b/django/template/loaders/base.py index 03c58f7535..1a7df03f5e 100644 --- a/django/template/loaders/base.py +++ b/django/template/loaders/base.py @@ -4,10 +4,11 @@ from django.template.loader import get_template_from_string, make_origin class Loader(object): is_usable = False + # Only used to raise a deprecation warning. Remove in Django 2.0. + _accepts_engine_in_init = True - def __init__(self, *args, **kwargs): - # XXX dropping arguments silently may not be the best idea. - pass + def __init__(self, engine): + self.engine = engine def __call__(self, template_name, template_dirs=None): return self.load_template(template_name, template_dirs) diff --git a/django/template/loaders/cached.py b/django/template/loaders/cached.py index 925abbe31f..b8cf48d237 100644 --- a/django/template/loaders/cached.py +++ b/django/template/loaders/cached.py @@ -9,18 +9,16 @@ from django.template.loader import get_template_from_string, make_origin from django.utils.encoding import force_bytes from .base import Loader as BaseLoader -from .utils import get_template_loaders class Loader(BaseLoader): is_usable = True - def __init__(self, loaders): + def __init__(self, engine, loaders): self.template_cache = {} self.find_template_cache = {} - # Use the private, non-caching version of get_template_loaders - # in case loaders isn't hashable. - self.loaders = get_template_loaders(loaders) + self.loaders = engine.get_template_loaders(loaders) + super(Loader, self).__init__(engine) def cache_key(self, template_name, template_dirs): if template_dirs: diff --git a/django/template/loaders/locmem.py b/django/template/loaders/locmem.py index ba6b15ef51..3085d69258 100644 --- a/django/template/loaders/locmem.py +++ b/django/template/loaders/locmem.py @@ -10,8 +10,9 @@ from .base import Loader as BaseLoader class Loader(BaseLoader): is_usable = True - def __init__(self, templates_dict): + def __init__(self, engine, templates_dict): self.templates_dict = templates_dict + super(Loader, self).__init__(engine) def load_template_source(self, template_name, template_dirs=None): try: diff --git a/django/template/loaders/utils.py b/django/template/loaders/utils.py deleted file mode 100644 index 8b742f89fd..0000000000 --- a/django/template/loaders/utils.py +++ /dev/null @@ -1,38 +0,0 @@ -import warnings - -from django.core.exceptions import ImproperlyConfigured -from django.utils import six -from django.utils.module_loading import import_string - - -def get_template_loaders(template_loaders): - loaders = [] - for template_loader in template_loaders: - loader = find_template_loader(template_loader) - if loader is not None: - loaders.append(loader) - return loaders - - -def find_template_loader(loader): - if isinstance(loader, (tuple, list)): - loader, args = loader[0], loader[1:] - else: - args = [] - - if isinstance(loader, six.string_types): - loader_class = import_string(loader) - loader_instance = loader_class(*args) - - if not loader_instance.is_usable: - warnings.warn( - "Your TEMPLATE_LOADERS setting includes %r, but your Python " - "installation doesn't support that type of template loading. " - "Consider removing that line from TEMPLATE_LOADERS." % loader) - return None - else: - return loader_instance - - else: - raise ImproperlyConfigured( - "Invalid value in TEMPLATE_LOADERS: %r" % loader) diff --git a/django/test/signals.py b/django/test/signals.py index 013d293203..648162b8ae 100644 --- a/django/test/signals.py +++ b/django/test/signals.py @@ -97,13 +97,6 @@ def clear_context_processors_cache(**kwargs): get_standard_processors.cache_clear() -@receiver(setting_changed) -def clear_template_loaders_cache(**kwargs): - if kwargs['setting'] == 'TEMPLATE_LOADERS': - from django.template.loaders.utils import get_template_loaders - get_template_loaders.cache_clear() - - @receiver(setting_changed) def clear_serializers_cache(**kwargs): if kwargs['setting'] == 'SERIALIZATION_MODULES': diff --git a/django/views/debug.py b/django/views/debug.py index 06a27069f6..ab32d866a5 100644 --- a/django/views/debug.py +++ b/django/views/debug.py @@ -12,7 +12,7 @@ from django.http import (HttpResponse, HttpResponseNotFound, HttpRequest, build_request_repr) from django.template import Template, Context, TemplateDoesNotExist from django.template.defaultfilters import force_escape, pprint -from django.template.loaders.utils import get_template_loaders +from django.template.engine import Engine from django.utils.datastructures import MultiValueDict from django.utils.html import escape from django.utils.encoding import force_bytes, smart_text @@ -282,7 +282,8 @@ class ExceptionReporter(object): # If Django fails in get_template_loaders, provide an empty list # for the following loop to not fail. try: - template_loaders = get_template_loaders() + # TODO: handle multiple template engines. + template_loaders = Engine.get_default().template_loaders except Exception: template_loaders = [] for loader in template_loaders: diff --git a/tests/template_tests/test_loaders.py b/tests/template_tests/test_loaders.py index 220f766077..66957dc90e 100644 --- a/tests/template_tests/test_loaders.py +++ b/tests/template_tests/test_loaders.py @@ -22,7 +22,7 @@ except ImportError: from django.template import TemplateDoesNotExist, Context from django.template.loaders.eggs import Loader as EggLoader -from django.template.loaders.utils import find_template_loader +from django.template.engine import Engine from django.template import loader from django.test import TestCase, override_settings from django.test.utils import IgnorePendingDeprecationWarningsMixin @@ -86,26 +86,26 @@ class EggLoaderTest(TestCase): @override_settings(INSTALLED_APPS=['egg_empty']) def test_empty(self): "Loading any template on an empty egg should fail" - egg_loader = EggLoader() + egg_loader = EggLoader(Engine.get_default()) self.assertRaises(TemplateDoesNotExist, egg_loader.load_template_source, "not-existing.html") @override_settings(INSTALLED_APPS=['egg_1']) def test_non_existing(self): "Template loading fails if the template is not in the egg" - egg_loader = EggLoader() + egg_loader = EggLoader(Engine.get_default()) self.assertRaises(TemplateDoesNotExist, egg_loader.load_template_source, "not-existing.html") @override_settings(INSTALLED_APPS=['egg_1']) def test_existing(self): "A template can be loaded from an egg" - egg_loader = EggLoader() + egg_loader = EggLoader(Engine.get_default()) contents, template_name = egg_loader.load_template_source("y.html") self.assertEqual(contents, "y") self.assertEqual(template_name, "egg:egg_1:templates/y.html") def test_not_installed(self): "Loading an existent template from an egg not included in any app should fail" - egg_loader = EggLoader() + egg_loader = EggLoader(Engine.get_default()) self.assertRaises(TemplateDoesNotExist, egg_loader.load_template_source, "y.html") @@ -129,7 +129,7 @@ class CachedLoader(TestCase): def test_missing_template_is_cached(self): "#19949 -- Check that the missing template is cached." - template_loader = find_template_loader(settings.TEMPLATE_LOADERS[0]) + 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 diff --git a/tests/template_tests/tests.py b/tests/template_tests/tests.py index 80d7b727d6..397c6c4e11 100644 --- a/tests/template_tests/tests.py +++ b/tests/template_tests/tests.py @@ -14,8 +14,8 @@ from django.contrib.auth.models import Group from django.core import urlresolvers from django.template import (base as template_base, loader, Context, RequestContext, Template, TemplateSyntaxError) +from django.template.engine import Engine from django.template.loaders import app_directories, filesystem -from django.template.loaders.utils import get_template_loaders from django.test import RequestFactory, TestCase from django.test.utils import override_settings, extend_sys_path from django.utils.deprecation import RemovedInDjango19Warning, RemovedInDjango20Warning @@ -160,8 +160,8 @@ class UTF8Class: class TemplateLoaderTests(TestCase): def test_loaders_security(self): - ad_loader = app_directories.Loader() - fs_loader = filesystem.Loader() + ad_loader = app_directories.Loader(Engine.get_default()) + fs_loader = filesystem.Loader(Engine.get_default()) def test_template_sources(path, template_dirs, expected_sources): if isinstance(expected_sources, list): @@ -620,9 +620,7 @@ class TemplateTests(TestCase): if output != result: failures.append("Template test (Cached='%s', TEMPLATE_STRING_IF_INVALID='%s', TEMPLATE_DEBUG=%s): %s -- FAILED. Expected %r, got %r" % (is_cached, invalid_str, template_debug, name, result, output)) - # This relies on get_template_loaders() memoizing its - # result. All callers get the same iterable of loaders. - get_template_loaders()[0].reset() + Engine.get_default().template_loaders[0].reset() if template_base.invalid_var_format_string: expected_invalid_str = 'INVALID'