From 572cdb43917ad860acaaffecd60b4e92599ae3eb Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Fri, 14 Nov 2014 15:48:27 +0100 Subject: [PATCH] Introduced a template engine class. Moved Django templates loading infrastructure there. --- django/template/base.py | 6 +- django/template/engine.py | 154 ++++++++++++++++++++++++++++++++++++++ django/template/loader.py | 104 +++---------------------- django/test/signals.py | 13 ++++ 4 files changed, 184 insertions(+), 93 deletions(-) create mode 100644 django/template/engine.py diff --git a/django/template/base.py b/django/template/base.py index 7c69e26088..c8c9844a6a 100644 --- a/django/template/base.py +++ b/django/template/base.py @@ -124,7 +124,7 @@ class StringOrigin(Origin): class Template(object): - def __init__(self, template_string, origin=None, name=None): + def __init__(self, template_string, origin=None, name=None, engine=None): try: template_string = force_text(template_string) except UnicodeDecodeError: @@ -132,9 +132,13 @@ class Template(object): "from unicode or UTF-8 strings.") if settings.TEMPLATE_DEBUG and origin is None: origin = StringOrigin(template_string) + if engine is None: + from .engine import Engine + engine = Engine.get_default() self.nodelist = compile_string(template_string, origin) self.name = name self.origin = origin + self.engine = engine def __iter__(self): for node in self.nodelist: diff --git a/django/template/engine.py b/django/template/engine.py new file mode 100644 index 0000000000..c377dbbc83 --- /dev/null +++ b/django/template/engine.py @@ -0,0 +1,154 @@ +import warnings + +from django.conf import settings +from django.core.exceptions import ImproperlyConfigured +from django.utils.deprecation import RemovedInDjango20Warning +from django.utils.functional import cached_property +from django.utils import lru_cache + +from .base import Context, Template, TemplateDoesNotExist +from .loaders.utils import _get_template_loaders + + +_dirs_undefined = object() + + +class Engine(object): + + def __init__(self, dirs=None, app_dirs=False, + allowed_include_roots=None, context_processors=None, + loaders=None, string_if_invalid=''): + if dirs is None: + dirs = [] + if allowed_include_roots is None: + allowed_include_roots = [] + if context_processors is None: + context_processors = [] + if loaders is None: + loaders = ['django.template.loaders.filesystem.Loader'] + if app_dirs: + loaders += ['django.template.loaders.app_directories.Loader'] + else: + if app_dirs: + raise ImproperlyConfigured( + "APP_DIRS must not be set when LOADERS is defined.") + + self.dirs = dirs + self.app_dirs = app_dirs + self.allowed_include_roots = allowed_include_roots + self.context_processors = context_processors + self.loaders = loaders + self.string_if_invalid = string_if_invalid + + @classmethod + @lru_cache.lru_cache() + def get_default(cls): + """Transitional method for refactoring.""" + return cls( + dirs=settings.TEMPLATE_DIRS, + allowed_include_roots=settings.ALLOWED_INCLUDE_ROOTS, + context_processors=settings.TEMPLATE_CONTEXT_PROCESSORS, + loaders=settings.TEMPLATE_LOADERS, + string_if_invalid=settings.TEMPLATE_STRING_IF_INVALID, + ) + + @cached_property + def template_loaders(self): + return _get_template_loaders(self.loaders) + + def find_template(self, name, dirs=None): + # Inner import to avoid circular dependency + from .loader import make_origin + for loader in self.template_loaders: + try: + source, display_name = loader(name, dirs) + return (source, make_origin(display_name, loader, name, dirs)) + except TemplateDoesNotExist: + pass + raise TemplateDoesNotExist(name) + + def get_template(self, template_name, dirs=_dirs_undefined): + """ + Returns a compiled Template object for the given template name, + handling template inheritance recursively. + """ + if dirs is _dirs_undefined: + dirs = None + else: + warnings.warn( + "The dirs argument of get_template is deprecated.", + RemovedInDjango20Warning, stacklevel=2) + + template, origin = self.find_template(template_name, dirs) + if not hasattr(template, 'render'): + # template needs to be compiled + template = self.get_template_from_string(template, origin, template_name) + return template + + def get_template_from_string(self, source, origin=None, name=None): + """ + Returns a compiled Template object for the given template code, + handling template inheritance recursively. + """ + return Template(source, origin, name) + + def render_to_string(self, template_name, dictionary=None, context_instance=None, + dirs=_dirs_undefined): + """ + Loads the given template_name and renders it with the given dictionary as + context. The template_name may be a string to load a single template using + get_template, or it may be a tuple to use select_template to find one of + the templates in the list. Returns a string. + """ + if dirs is _dirs_undefined: + # Do not set dirs to None here to avoid triggering the deprecation + # warning in select_template or get_template. + pass + else: + warnings.warn( + "The dirs argument of render_to_string is deprecated.", + RemovedInDjango20Warning, stacklevel=2) + + if isinstance(template_name, (list, tuple)): + t = self.select_template(template_name, dirs) + else: + t = self.get_template(template_name, dirs) + if not context_instance: + # 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 + # to keep the context_instance in the same state it started in. + with context_instance.push(dictionary): + return t.render(context_instance) + + def select_template(self, template_name_list, dirs=_dirs_undefined): + """ + Given a list of template names, returns the first that can be loaded. + """ + if dirs is _dirs_undefined: + # Do not set dirs to None here to avoid triggering the deprecation + # warning in get_template. + pass + else: + warnings.warn( + "The dirs argument of select_template is deprecated.", + RemovedInDjango20Warning, stacklevel=2) + + if not template_name_list: + raise TemplateDoesNotExist("No template names provided") + not_found = [] + for template_name in template_name_list: + try: + return self.get_template(template_name, dirs) + except TemplateDoesNotExist as exc: + if exc.args[0] not in not_found: + not_found.append(exc.args[0]) + continue + # If we get here, none of the templates could be loaded + raise TemplateDoesNotExist(', '.join(not_found)) diff --git a/django/template/loader.py b/django/template/loader.py index 9641f32e78..0c24e3d373 100644 --- a/django/template/loader.py +++ b/django/template/loader.py @@ -1,12 +1,10 @@ import warnings from django.conf import settings -from django.template.base import Origin, Template, Context, TemplateDoesNotExist -from django.template.loaders.utils import get_template_loaders from django.utils.deprecation import RemovedInDjango20Warning - -_dirs_undefined = object() +from .base import Origin +from .engine import Engine class LoaderOrigin(Origin): @@ -25,102 +23,24 @@ def make_origin(display_name, loader, name, dirs): return None -def find_template(name, dirs=None): - for loader in get_template_loaders(): - try: - source, display_name = loader(name, dirs) - return (source, make_origin(display_name, loader, name, dirs)) - except TemplateDoesNotExist: - pass - raise TemplateDoesNotExist(name) +def find_template(*args, **kwargs): + return Engine.get_default().find_template(*args, **kwargs) -def get_template(template_name, dirs=_dirs_undefined): - """ - Returns a compiled Template object for the given template name, - handling template inheritance recursively. - """ - if dirs is _dirs_undefined: - dirs = None - else: - warnings.warn( - "The dirs argument of get_template is deprecated.", - RemovedInDjango20Warning, stacklevel=2) - - template, origin = find_template(template_name, dirs) - if not hasattr(template, 'render'): - # template needs to be compiled - template = get_template_from_string(template, origin, template_name) - return template +def get_template(*args, **kwargs): + return Engine.get_default().get_template(*args, **kwargs) -def get_template_from_string(source, origin=None, name=None): - """ - Returns a compiled Template object for the given template code, - handling template inheritance recursively. - """ - return Template(source, origin, name) +def get_template_from_string(*args, **kwargs): + return Engine.get_default().get_template_from_string(*args, **kwargs) -def render_to_string(template_name, dictionary=None, context_instance=None, - dirs=_dirs_undefined): - """ - Loads the given template_name and renders it with the given dictionary as - context. The template_name may be a string to load a single template using - get_template, or it may be a tuple to use select_template to find one of - the templates in the list. Returns a string. - """ - if dirs is _dirs_undefined: - # Do not set dirs to None here to avoid triggering the deprecation - # warning in select_template or get_template. - pass - else: - warnings.warn( - "The dirs argument of render_to_string is deprecated.", - RemovedInDjango20Warning, stacklevel=2) - - if isinstance(template_name, (list, tuple)): - t = select_template(template_name, dirs) - else: - t = get_template(template_name, dirs) - if not context_instance: - # 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 - # to keep the context_instance in the same state it started in. - with context_instance.push(dictionary): - return t.render(context_instance) +def render_to_string(*args, **kwargs): + return Engine.get_default().render_to_string(*args, **kwargs) -def select_template(template_name_list, dirs=_dirs_undefined): - "Given a list of template names, returns the first that can be loaded." - if dirs is _dirs_undefined: - # Do not set dirs to None here to avoid triggering the deprecation - # warning in get_template. - pass - else: - warnings.warn( - "The dirs argument of select_template is deprecated.", - RemovedInDjango20Warning, stacklevel=2) - - if not template_name_list: - raise TemplateDoesNotExist("No template names provided") - not_found = [] - for template_name in template_name_list: - try: - return get_template(template_name, dirs) - except TemplateDoesNotExist as e: - if e.args[0] not in not_found: - not_found.append(e.args[0]) - continue - # If we get here, none of the templates could be loaded - raise TemplateDoesNotExist(', '.join(not_found)) +def select_template(*args, **kwargs): + return Engine.get_default().select_template(*args, **kwargs) # This line must remain at the bottom to avoid import loops. diff --git a/django/test/signals.py b/django/test/signals.py index b8e7193087..013d293203 100644 --- a/django/test/signals.py +++ b/django/test/signals.py @@ -77,6 +77,19 @@ def update_connections_time_zone(**kwargs): conn.cursor().execute(tz_sql, [tz]) +@receiver(setting_changed) +def reset_default_template_engine(**kwargs): + if kwargs['setting'] in { + 'TEMPLATE_DIRS', + 'ALLOWED_INCLUDE_ROOTS', + 'TEMPLATE_CONTEXT_PROCESSORS', + 'TEMPLATE_LOADERS', + 'TEMPLATE_STRING_IF_INVALID', + }: + from django.template.engine import Engine + Engine.get_default.cache_clear() + + @receiver(setting_changed) def clear_context_processors_cache(**kwargs): if kwargs['setting'] == 'TEMPLATE_CONTEXT_PROCESSORS':