Commit fc214715 authored by Preston Timmons's avatar Preston Timmons
Browse files

Fixed #15053 -- Enabled recursive template loading.

parent 1b1b58bc
Loading
Loading
Loading
Loading
+2 −2
Original line number Diff line number Diff line
@@ -59,8 +59,8 @@ from .base import (TemplateDoesNotExist, TemplateSyntaxError, # NOQA
from .context import ContextPopException                                # NOQA

# Template parts
from .base import (Context, Node, NodeList, RequestContext,             # NOQA
                   StringOrigin, Template, Variable)
from .base import (Context, Node, NodeList, Origin, RequestContext,     # NOQA
                   Template, Variable)

# Deprecated in Django 1.8, will be removed in Django 2.0.
from .base import resolve_variable                                      # NOQA
+26 −12
Original line number Diff line number Diff line
@@ -134,7 +134,15 @@ class TemplateSyntaxError(Exception):


class TemplateDoesNotExist(Exception):
    pass
    """
    This exception is used when template loaders are unable to find a
    template. The tried argument is an optional list of tuples containing
    (origin, status), where origin is an Origin object and status is a string
    with the reason the template wasn't found.
    """
    def __init__(self, msg, tried=None):
        self.tried = tried or []
        super(TemplateDoesNotExist, self).__init__(msg)


class TemplateEncodingError(Exception):
@@ -157,23 +165,29 @@ class InvalidTemplateLibrary(Exception):


class Origin(object):
    def __init__(self, name):
    def __init__(self, name, template_name=None, loader=None):
        self.name = name

    def reload(self):
        raise NotImplementedError('subclasses of Origin must provide a reload() method')
        self.template_name = template_name
        self.loader = loader

    def __str__(self):
        return self.name

    def __eq__(self, other):
        if not isinstance(other, Origin):
            return False

class StringOrigin(Origin):
    def __init__(self, source):
        super(StringOrigin, self).__init__(UNKNOWN_SOURCE)
        self.source = source
        return (
            self.name == other.name and
            self.loader == other.loader
        )

    def reload(self):
        return self.source
    @property
    def loader_name(self):
        if self.loader:
            return '%s.%s' % (
                self.loader.__module__, self.loader.__class__.__name__,
            )


class Template(object):
@@ -191,7 +205,7 @@ class Template(object):
            from .engine import Engine
            engine = Engine.get_default()
        if origin is None:
            origin = StringOrigin(template_string)
            origin = Origin(UNKNOWN_SOURCE)
        self.name = name
        self.origin = origin
        self.engine = engine
+18 −16
Original line number Diff line number Diff line
@@ -124,15 +124,25 @@ class Engine(object):
            raise ImproperlyConfigured(
                "Invalid value in template loaders configuration: %r" % loader)

    def find_template(self, name, dirs=None):
    def find_template(self, name, dirs=None, skip=None):
        tried = []
        for loader in self.template_loaders:
            if loader.supports_recursion:
                try:
                source, display_name = loader(name, dirs)
                origin = self.make_origin(display_name, loader, name, dirs)
                return source, origin
                    template = loader.get_template(
                        name, template_dirs=dirs, skip=skip,
                    )
                    return template, template.origin
                except TemplateDoesNotExist as e:
                    tried.extend(e.tried)
            else:
                # RemovedInDjango21Warning: Use old api for non-recursive
                # loaders.
                try:
                    return loader(name, dirs)
                except TemplateDoesNotExist:
                    pass
        raise TemplateDoesNotExist(name)
        raise TemplateDoesNotExist(name, tried=tried)

    def from_string(self, template_code):
        """
@@ -234,11 +244,3 @@ class Engine(object):
                continue
        # If we get here, none of the templates could be loaded
        raise TemplateDoesNotExist(', '.join(not_found))

    def make_origin(self, display_name, loader, name, dirs):
        if self.debug and display_name:
            # Inner import to avoid circular dependency
            from .loader import LoaderOrigin
            return LoaderOrigin(display_name, loader, name, dirs)
        else:
            return None
+13 −15
Original line number Diff line number Diff line
@@ -4,25 +4,20 @@ from django.utils.deprecation import RemovedInDjango20Warning

from . import engines
from .backends.django import DjangoTemplates
from .base import Origin, TemplateDoesNotExist
from .base import TemplateDoesNotExist
from .engine import (
    _context_instance_undefined, _dictionary_undefined, _dirs_undefined,
)
from .loaders import base


class LoaderOrigin(Origin):
    def __init__(self, display_name, loader, name, dirs):
        super(LoaderOrigin, self).__init__(display_name)
        self.loader, self.loadname, self.dirs = loader, name, dirs


def get_template(template_name, dirs=_dirs_undefined, using=None):
    """
    Loads and returns a template for the given name.

    Raises TemplateDoesNotExist if no such template exists.
    """
    tried = []
    engines = _engine_list(using)
    for engine in engines:
        try:
@@ -37,10 +32,10 @@ def get_template(template_name, dirs=_dirs_undefined, using=None):
                    stacklevel=2)
            else:
                return engine.get_template(template_name)
        except TemplateDoesNotExist:
            pass
        except TemplateDoesNotExist as e:
            tried.extend(e.tried)

    raise TemplateDoesNotExist(template_name)
    raise TemplateDoesNotExist(template_name, tried=tried)


def select_template(template_name_list, dirs=_dirs_undefined, using=None):
@@ -51,6 +46,7 @@ def select_template(template_name_list, dirs=_dirs_undefined, using=None):

    Raises TemplateDoesNotExist if no such template exists.
    """
    tried = []
    engines = _engine_list(using)
    for template_name in template_name_list:
        for engine in engines:
@@ -66,11 +62,11 @@ def select_template(template_name_list, dirs=_dirs_undefined, using=None):
                        stacklevel=2)
                else:
                    return engine.get_template(template_name)
            except TemplateDoesNotExist:
                pass
            except TemplateDoesNotExist as e:
                tried.extend(e.tried)

    if template_name_list:
        raise TemplateDoesNotExist(', '.join(template_name_list))
        raise TemplateDoesNotExist(', '.join(template_name_list), tried=tried)
    else:
        raise TemplateDoesNotExist("No template names provided")

@@ -96,6 +92,7 @@ def render_to_string(template_name, context=None,
        return template.render(context, request)

    else:
        tried = []
        # Some deprecated arguments were passed - use the legacy code path
        for engine in _engine_list(using):
            try:
@@ -126,13 +123,14 @@ def render_to_string(template_name, context=None,
                        "Skipping template backend %s because its render_to_string "
                        "method doesn't support the dictionary argument." %
                        engine.name, stacklevel=2)
            except TemplateDoesNotExist:
            except TemplateDoesNotExist as e:
                tried.extend(e.tried)
                continue

        if template_name:
            if isinstance(template_name, (list, tuple)):
                template_name = ', '.join(template_name)
            raise TemplateDoesNotExist(template_name)
            raise TemplateDoesNotExist(template_name, tried=tried)
        else:
            raise TemplateDoesNotExist("No template names provided")

+35 −1
Original line number Diff line number Diff line
@@ -82,6 +82,7 @@ class BlockNode(Node):

class ExtendsNode(Node):
    must_be_first = True
    context_key = 'extends_context'

    def __init__(self, nodelist, parent_name, template_dirs=None):
        self.nodelist = nodelist
@@ -92,6 +93,39 @@ class ExtendsNode(Node):
    def __repr__(self):
        return '<ExtendsNode: extends %s>' % self.parent_name.token

    def find_template(self, template_name, context):
        """
        This is a wrapper around engine.find_template(). A history is kept in
        the render_context attribute between successive extends calls and
        passed as the skip argument. This enables extends to work recursively
        without extending the same template twice.
        """
        # RemovedInDjango21Warning: If any non-recursive loaders are installed
        # do a direct template lookup. If the same template name appears twice,
        # raise an exception to avoid system recursion.
        for loader in context.template.engine.template_loaders:
            if not loader.supports_recursion:
                history = context.render_context.setdefault(
                    self.context_key, [context.template.origin.template_name],
                )
                if template_name in history:
                    raise ExtendsError(
                        "Cannot extend templates recursively when using "
                        "non-recursive template loaders",
                    )
                template = context.template.engine.get_template(template_name)
                history.append(template_name)
                return template

        history = context.render_context.setdefault(
            self.context_key, [context.template.origin],
        )
        template, origin = context.template.engine.find_template(
            template_name, skip=history,
        )
        history.append(origin)
        return template

    def get_parent(self, context):
        parent = self.parent_name.resolve(context)
        if not parent:
@@ -107,7 +141,7 @@ class ExtendsNode(Node):
        if isinstance(getattr(parent, 'template', None), Template):
            # parent is a django.template.backends.django.Template
            return parent.template
        return context.template.engine.get_template(parent)
        return self.find_template(parent, context)

    def render(self, context):
        compiled_parent = self.get_parent(context)
Loading