Fixed #15053 -- Enabled recursive template loading.
This commit is contained in:
parent
1b1b58bc7b
commit
fc21471526
|
@ -59,8 +59,8 @@ from .base import (TemplateDoesNotExist, TemplateSyntaxError, # NOQA
|
||||||
from .context import ContextPopException # NOQA
|
from .context import ContextPopException # NOQA
|
||||||
|
|
||||||
# Template parts
|
# Template parts
|
||||||
from .base import (Context, Node, NodeList, RequestContext, # NOQA
|
from .base import (Context, Node, NodeList, Origin, RequestContext, # NOQA
|
||||||
StringOrigin, Template, Variable)
|
Template, Variable)
|
||||||
|
|
||||||
# Deprecated in Django 1.8, will be removed in Django 2.0.
|
# Deprecated in Django 1.8, will be removed in Django 2.0.
|
||||||
from .base import resolve_variable # NOQA
|
from .base import resolve_variable # NOQA
|
||||||
|
|
|
@ -134,7 +134,15 @@ class TemplateSyntaxError(Exception):
|
||||||
|
|
||||||
|
|
||||||
class TemplateDoesNotExist(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):
|
class TemplateEncodingError(Exception):
|
||||||
|
@ -157,23 +165,29 @@ class InvalidTemplateLibrary(Exception):
|
||||||
|
|
||||||
|
|
||||||
class Origin(object):
|
class Origin(object):
|
||||||
def __init__(self, name):
|
def __init__(self, name, template_name=None, loader=None):
|
||||||
self.name = name
|
self.name = name
|
||||||
|
self.template_name = template_name
|
||||||
def reload(self):
|
self.loader = loader
|
||||||
raise NotImplementedError('subclasses of Origin must provide a reload() method')
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
if not isinstance(other, Origin):
|
||||||
|
return False
|
||||||
|
|
||||||
class StringOrigin(Origin):
|
return (
|
||||||
def __init__(self, source):
|
self.name == other.name and
|
||||||
super(StringOrigin, self).__init__(UNKNOWN_SOURCE)
|
self.loader == other.loader
|
||||||
self.source = source
|
)
|
||||||
|
|
||||||
def reload(self):
|
@property
|
||||||
return self.source
|
def loader_name(self):
|
||||||
|
if self.loader:
|
||||||
|
return '%s.%s' % (
|
||||||
|
self.loader.__module__, self.loader.__class__.__name__,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class Template(object):
|
class Template(object):
|
||||||
|
@ -191,7 +205,7 @@ class Template(object):
|
||||||
from .engine import Engine
|
from .engine import Engine
|
||||||
engine = Engine.get_default()
|
engine = Engine.get_default()
|
||||||
if origin is None:
|
if origin is None:
|
||||||
origin = StringOrigin(template_string)
|
origin = Origin(UNKNOWN_SOURCE)
|
||||||
self.name = name
|
self.name = name
|
||||||
self.origin = origin
|
self.origin = origin
|
||||||
self.engine = engine
|
self.engine = engine
|
||||||
|
|
|
@ -124,15 +124,25 @@ class Engine(object):
|
||||||
raise ImproperlyConfigured(
|
raise ImproperlyConfigured(
|
||||||
"Invalid value in template loaders configuration: %r" % loader)
|
"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:
|
for loader in self.template_loaders:
|
||||||
try:
|
if loader.supports_recursion:
|
||||||
source, display_name = loader(name, dirs)
|
try:
|
||||||
origin = self.make_origin(display_name, loader, name, dirs)
|
template = loader.get_template(
|
||||||
return source, origin
|
name, template_dirs=dirs, skip=skip,
|
||||||
except TemplateDoesNotExist:
|
)
|
||||||
pass
|
return template, template.origin
|
||||||
raise TemplateDoesNotExist(name)
|
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, tried=tried)
|
||||||
|
|
||||||
def from_string(self, template_code):
|
def from_string(self, template_code):
|
||||||
"""
|
"""
|
||||||
|
@ -234,11 +244,3 @@ class Engine(object):
|
||||||
continue
|
continue
|
||||||
# If we get here, none of the templates could be loaded
|
# If we get here, none of the templates could be loaded
|
||||||
raise TemplateDoesNotExist(', '.join(not_found))
|
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
|
|
||||||
|
|
|
@ -4,25 +4,20 @@ from django.utils.deprecation import RemovedInDjango20Warning
|
||||||
|
|
||||||
from . import engines
|
from . import engines
|
||||||
from .backends.django import DjangoTemplates
|
from .backends.django import DjangoTemplates
|
||||||
from .base import Origin, TemplateDoesNotExist
|
from .base import TemplateDoesNotExist
|
||||||
from .engine import (
|
from .engine import (
|
||||||
_context_instance_undefined, _dictionary_undefined, _dirs_undefined,
|
_context_instance_undefined, _dictionary_undefined, _dirs_undefined,
|
||||||
)
|
)
|
||||||
from .loaders import base
|
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):
|
def get_template(template_name, dirs=_dirs_undefined, using=None):
|
||||||
"""
|
"""
|
||||||
Loads and returns a template for the given name.
|
Loads and returns a template for the given name.
|
||||||
|
|
||||||
Raises TemplateDoesNotExist if no such template exists.
|
Raises TemplateDoesNotExist if no such template exists.
|
||||||
"""
|
"""
|
||||||
|
tried = []
|
||||||
engines = _engine_list(using)
|
engines = _engine_list(using)
|
||||||
for engine in engines:
|
for engine in engines:
|
||||||
try:
|
try:
|
||||||
|
@ -37,10 +32,10 @@ def get_template(template_name, dirs=_dirs_undefined, using=None):
|
||||||
stacklevel=2)
|
stacklevel=2)
|
||||||
else:
|
else:
|
||||||
return engine.get_template(template_name)
|
return engine.get_template(template_name)
|
||||||
except TemplateDoesNotExist:
|
except TemplateDoesNotExist as e:
|
||||||
pass
|
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):
|
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.
|
Raises TemplateDoesNotExist if no such template exists.
|
||||||
"""
|
"""
|
||||||
|
tried = []
|
||||||
engines = _engine_list(using)
|
engines = _engine_list(using)
|
||||||
for template_name in template_name_list:
|
for template_name in template_name_list:
|
||||||
for engine in engines:
|
for engine in engines:
|
||||||
|
@ -66,11 +62,11 @@ def select_template(template_name_list, dirs=_dirs_undefined, using=None):
|
||||||
stacklevel=2)
|
stacklevel=2)
|
||||||
else:
|
else:
|
||||||
return engine.get_template(template_name)
|
return engine.get_template(template_name)
|
||||||
except TemplateDoesNotExist:
|
except TemplateDoesNotExist as e:
|
||||||
pass
|
tried.extend(e.tried)
|
||||||
|
|
||||||
if template_name_list:
|
if template_name_list:
|
||||||
raise TemplateDoesNotExist(', '.join(template_name_list))
|
raise TemplateDoesNotExist(', '.join(template_name_list), tried=tried)
|
||||||
else:
|
else:
|
||||||
raise TemplateDoesNotExist("No template names provided")
|
raise TemplateDoesNotExist("No template names provided")
|
||||||
|
|
||||||
|
@ -96,6 +92,7 @@ def render_to_string(template_name, context=None,
|
||||||
return template.render(context, request)
|
return template.render(context, request)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
tried = []
|
||||||
# Some deprecated arguments were passed - use the legacy code path
|
# Some deprecated arguments were passed - use the legacy code path
|
||||||
for engine in _engine_list(using):
|
for engine in _engine_list(using):
|
||||||
try:
|
try:
|
||||||
|
@ -126,13 +123,14 @@ def render_to_string(template_name, context=None,
|
||||||
"Skipping template backend %s because its render_to_string "
|
"Skipping template backend %s because its render_to_string "
|
||||||
"method doesn't support the dictionary argument." %
|
"method doesn't support the dictionary argument." %
|
||||||
engine.name, stacklevel=2)
|
engine.name, stacklevel=2)
|
||||||
except TemplateDoesNotExist:
|
except TemplateDoesNotExist as e:
|
||||||
|
tried.extend(e.tried)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if template_name:
|
if template_name:
|
||||||
if isinstance(template_name, (list, tuple)):
|
if isinstance(template_name, (list, tuple)):
|
||||||
template_name = ', '.join(template_name)
|
template_name = ', '.join(template_name)
|
||||||
raise TemplateDoesNotExist(template_name)
|
raise TemplateDoesNotExist(template_name, tried=tried)
|
||||||
else:
|
else:
|
||||||
raise TemplateDoesNotExist("No template names provided")
|
raise TemplateDoesNotExist("No template names provided")
|
||||||
|
|
||||||
|
|
|
@ -82,6 +82,7 @@ class BlockNode(Node):
|
||||||
|
|
||||||
class ExtendsNode(Node):
|
class ExtendsNode(Node):
|
||||||
must_be_first = True
|
must_be_first = True
|
||||||
|
context_key = 'extends_context'
|
||||||
|
|
||||||
def __init__(self, nodelist, parent_name, template_dirs=None):
|
def __init__(self, nodelist, parent_name, template_dirs=None):
|
||||||
self.nodelist = nodelist
|
self.nodelist = nodelist
|
||||||
|
@ -92,6 +93,39 @@ class ExtendsNode(Node):
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return '<ExtendsNode: extends %s>' % self.parent_name.token
|
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):
|
def get_parent(self, context):
|
||||||
parent = self.parent_name.resolve(context)
|
parent = self.parent_name.resolve(context)
|
||||||
if not parent:
|
if not parent:
|
||||||
|
@ -107,7 +141,7 @@ class ExtendsNode(Node):
|
||||||
if isinstance(getattr(parent, 'template', None), Template):
|
if isinstance(getattr(parent, 'template', None), Template):
|
||||||
# parent is a django.template.backends.django.Template
|
# parent is a django.template.backends.django.Template
|
||||||
return parent.template
|
return parent.template
|
||||||
return context.template.engine.get_template(parent)
|
return self.find_template(parent, context)
|
||||||
|
|
||||||
def render(self, context):
|
def render(self, context):
|
||||||
compiled_parent = self.get_parent(context)
|
compiled_parent = self.get_parent(context)
|
||||||
|
|
|
@ -1,4 +1,8 @@
|
||||||
from django.template.base import Template, TemplateDoesNotExist
|
import warnings
|
||||||
|
from inspect import getargspec
|
||||||
|
|
||||||
|
from django.template.base import Origin, Template, TemplateDoesNotExist
|
||||||
|
from django.utils.deprecation import RemovedInDjango21Warning
|
||||||
|
|
||||||
|
|
||||||
class Loader(object):
|
class Loader(object):
|
||||||
|
@ -9,15 +13,54 @@ class Loader(object):
|
||||||
self.engine = engine
|
self.engine = engine
|
||||||
|
|
||||||
def __call__(self, template_name, template_dirs=None):
|
def __call__(self, template_name, template_dirs=None):
|
||||||
|
# RemovedInDjango21Warning: Allow loaders to be called like functions.
|
||||||
return self.load_template(template_name, template_dirs)
|
return self.load_template(template_name, template_dirs)
|
||||||
|
|
||||||
def load_template(self, template_name, template_dirs=None):
|
def get_template(self, template_name, template_dirs=None, skip=None):
|
||||||
source, display_name = self.load_template_source(
|
"""
|
||||||
template_name, template_dirs)
|
Calls self.get_template_sources() and returns a Template object for
|
||||||
origin = self.engine.make_origin(
|
the first template matching template_name. If skip is provided,
|
||||||
display_name, self.load_template_source,
|
template origins in skip are ignored. This is used to avoid recursion
|
||||||
template_name, template_dirs)
|
during template extending.
|
||||||
|
"""
|
||||||
|
tried = []
|
||||||
|
|
||||||
|
args = [template_name]
|
||||||
|
# RemovedInDjango21Warning: Add template_dirs for compatibility with
|
||||||
|
# old loaders
|
||||||
|
if 'template_dirs' in getargspec(self.get_template_sources)[0]:
|
||||||
|
args.append(template_dirs)
|
||||||
|
|
||||||
|
for origin in self.get_template_sources(*args):
|
||||||
|
if skip is not None and origin in skip:
|
||||||
|
tried.append((origin, 'Skipped'))
|
||||||
|
continue
|
||||||
|
|
||||||
|
try:
|
||||||
|
contents = self.get_contents(origin)
|
||||||
|
except TemplateDoesNotExist:
|
||||||
|
tried.append((origin, 'Source does not exist'))
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
return Template(
|
||||||
|
contents, origin, origin.template_name, self.engine,
|
||||||
|
)
|
||||||
|
|
||||||
|
raise TemplateDoesNotExist(template_name, tried=tried)
|
||||||
|
|
||||||
|
def load_template(self, template_name, template_dirs=None):
|
||||||
|
warnings.warn(
|
||||||
|
'The load_template() method is deprecated. Use get_template() '
|
||||||
|
'instead.', RemovedInDjango21Warning,
|
||||||
|
)
|
||||||
|
source, display_name = self.load_template_source(
|
||||||
|
template_name, template_dirs,
|
||||||
|
)
|
||||||
|
origin = Origin(
|
||||||
|
name=display_name,
|
||||||
|
template_name=template_name,
|
||||||
|
loader=self,
|
||||||
|
)
|
||||||
try:
|
try:
|
||||||
template = Template(source, origin, template_name, self.engine)
|
template = Template(source, origin, template_name, self.engine)
|
||||||
except TemplateDoesNotExist:
|
except TemplateDoesNotExist:
|
||||||
|
@ -29,14 +72,23 @@ class Loader(object):
|
||||||
else:
|
else:
|
||||||
return template, None
|
return template, None
|
||||||
|
|
||||||
def load_template_source(self, template_name, template_dirs=None):
|
def get_template_sources(self, template_name):
|
||||||
"""
|
"""
|
||||||
Returns a tuple containing the source and origin for the given
|
An iterator that yields possible matching template paths for a
|
||||||
template name.
|
template name.
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError(
|
raise NotImplementedError(
|
||||||
"subclasses of Loader must provide "
|
'subclasses of Loader must provide a get_template_sources() method'
|
||||||
"a load_template_source() method")
|
)
|
||||||
|
|
||||||
|
def load_template_source(self, template_name, template_dirs=None):
|
||||||
|
"""
|
||||||
|
RemovedInDjango21Warning: Returns a tuple containing the source and
|
||||||
|
origin for the given template name.
|
||||||
|
"""
|
||||||
|
raise NotImplementedError(
|
||||||
|
'subclasses of Loader must provide a load_template_source() method'
|
||||||
|
)
|
||||||
|
|
||||||
def reset(self):
|
def reset(self):
|
||||||
"""
|
"""
|
||||||
|
@ -44,3 +96,11 @@ class Loader(object):
|
||||||
templates or cached loader modules).
|
templates or cached loader modules).
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@property
|
||||||
|
def supports_recursion(self):
|
||||||
|
"""
|
||||||
|
RemovedInDjango21Warning: This is an internal property used by the
|
||||||
|
ExtendsNode during the deprecation of non-recursive loaders.
|
||||||
|
"""
|
||||||
|
return hasattr(self, 'get_contents')
|
||||||
|
|
|
@ -4,8 +4,11 @@ to load templates from them in order, caching the result.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import hashlib
|
import hashlib
|
||||||
|
import warnings
|
||||||
|
from inspect import getargspec
|
||||||
|
|
||||||
from django.template.base import Template, TemplateDoesNotExist
|
from django.template.base import Origin, Template, TemplateDoesNotExist
|
||||||
|
from django.utils.deprecation import RemovedInDjango21Warning
|
||||||
from django.utils.encoding import force_bytes
|
from django.utils.encoding import force_bytes
|
||||||
|
|
||||||
from .base import Loader as BaseLoader
|
from .base import Loader as BaseLoader
|
||||||
|
@ -15,20 +18,84 @@ class Loader(BaseLoader):
|
||||||
|
|
||||||
def __init__(self, engine, loaders):
|
def __init__(self, engine, loaders):
|
||||||
self.template_cache = {}
|
self.template_cache = {}
|
||||||
self.find_template_cache = {}
|
self.find_template_cache = {} # RemovedInDjango21Warning
|
||||||
|
self.get_template_cache = {}
|
||||||
self.loaders = engine.get_template_loaders(loaders)
|
self.loaders = engine.get_template_loaders(loaders)
|
||||||
super(Loader, self).__init__(engine)
|
super(Loader, self).__init__(engine)
|
||||||
|
|
||||||
def cache_key(self, template_name, template_dirs):
|
def get_contents(self, origin):
|
||||||
if template_dirs:
|
return origin.loader.get_contents(origin)
|
||||||
# If template directories were specified, use a hash to differentiate
|
|
||||||
return '-'.join([template_name, hashlib.sha1(force_bytes('|'.join(template_dirs))).hexdigest()])
|
def get_template(self, template_name, template_dirs=None, skip=None):
|
||||||
|
key = self.cache_key(template_name, template_dirs, skip)
|
||||||
|
cached = self.get_template_cache.get(key)
|
||||||
|
if cached:
|
||||||
|
if isinstance(cached, TemplateDoesNotExist):
|
||||||
|
raise cached
|
||||||
|
return cached
|
||||||
|
|
||||||
|
try:
|
||||||
|
template = super(Loader, self).get_template(
|
||||||
|
template_name, template_dirs, skip,
|
||||||
|
)
|
||||||
|
except TemplateDoesNotExist as e:
|
||||||
|
self.get_template_cache[key] = e
|
||||||
|
raise
|
||||||
else:
|
else:
|
||||||
return template_name
|
self.get_template_cache[key] = template
|
||||||
|
|
||||||
|
return template
|
||||||
|
|
||||||
|
def get_template_sources(self, template_name, template_dirs=None):
|
||||||
|
for loader in self.loaders:
|
||||||
|
args = [template_name]
|
||||||
|
# RemovedInDjango21Warning: Add template_dirs for compatibility
|
||||||
|
# with old loaders
|
||||||
|
if 'template_dirs' in getargspec(loader.get_template_sources)[0]:
|
||||||
|
args.append(template_dirs)
|
||||||
|
for origin in loader.get_template_sources(*args):
|
||||||
|
yield origin
|
||||||
|
|
||||||
|
def cache_key(self, template_name, template_dirs, skip=None):
|
||||||
|
"""
|
||||||
|
Generate a cache key for the template name, dirs, and skip.
|
||||||
|
|
||||||
|
If skip is provided, only origins that match template_name are included
|
||||||
|
in the cache key. This ensures each template is only parsed and cached
|
||||||
|
once if contained in different extend chains like:
|
||||||
|
|
||||||
|
x -> a -> a
|
||||||
|
y -> a -> a
|
||||||
|
z -> a -> a
|
||||||
|
"""
|
||||||
|
dirs_prefix = ''
|
||||||
|
skip_prefix = ''
|
||||||
|
|
||||||
|
if skip:
|
||||||
|
matching = [origin.name for origin in skip if origin.template_name == template_name]
|
||||||
|
if matching:
|
||||||
|
skip_prefix = self.generate_hash(matching)
|
||||||
|
|
||||||
|
if template_dirs:
|
||||||
|
dirs_prefix = self.generate_hash(template_dirs)
|
||||||
|
|
||||||
|
return ("%s-%s-%s" % (template_name, skip_prefix, dirs_prefix)).strip('-')
|
||||||
|
|
||||||
|
def generate_hash(self, values):
|
||||||
|
return hashlib.sha1(force_bytes('|'.join(values))).hexdigest()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def supports_recursion(self):
|
||||||
|
"""
|
||||||
|
RemovedInDjango21Warning: This is an internal property used by the
|
||||||
|
ExtendsNode during the deprecation of non-recursive loaders.
|
||||||
|
"""
|
||||||
|
return all(hasattr(loader, 'get_contents') for loader in self.loaders)
|
||||||
|
|
||||||
def find_template(self, name, dirs=None):
|
def find_template(self, name, dirs=None):
|
||||||
"""
|
"""
|
||||||
Helper method. Lookup the template :param name: in all the configured loaders
|
RemovedInDjango21Warning: An internal method to lookup the template
|
||||||
|
name in all the configured loaders.
|
||||||
"""
|
"""
|
||||||
key = self.cache_key(name, dirs)
|
key = self.cache_key(name, dirs)
|
||||||
try:
|
try:
|
||||||
|
@ -41,7 +108,11 @@ class Loader(BaseLoader):
|
||||||
except TemplateDoesNotExist:
|
except TemplateDoesNotExist:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
origin = self.engine.make_origin(display_name, loader, name, dirs)
|
origin = Origin(
|
||||||
|
name=display_name,
|
||||||
|
template_name=name,
|
||||||
|
loader=loader,
|
||||||
|
)
|
||||||
result = template, origin
|
result = template, origin
|
||||||
break
|
break
|
||||||
self.find_template_cache[key] = result
|
self.find_template_cache[key] = result
|
||||||
|
@ -52,6 +123,10 @@ class Loader(BaseLoader):
|
||||||
raise TemplateDoesNotExist(name)
|
raise TemplateDoesNotExist(name)
|
||||||
|
|
||||||
def load_template(self, template_name, template_dirs=None):
|
def load_template(self, template_name, template_dirs=None):
|
||||||
|
warnings.warn(
|
||||||
|
'The load_template() method is deprecated. Use get_template() '
|
||||||
|
'instead.', RemovedInDjango21Warning,
|
||||||
|
)
|
||||||
key = self.cache_key(template_name, template_dirs)
|
key = self.cache_key(template_name, template_dirs)
|
||||||
template_tuple = self.template_cache.get(key)
|
template_tuple = self.template_cache.get(key)
|
||||||
# A cached previous failure:
|
# A cached previous failure:
|
||||||
|
@ -74,4 +149,5 @@ class Loader(BaseLoader):
|
||||||
def reset(self):
|
def reset(self):
|
||||||
"Empty the template cache."
|
"Empty the template cache."
|
||||||
self.template_cache.clear()
|
self.template_cache.clear()
|
||||||
self.find_template_cache.clear()
|
self.find_template_cache.clear() # RemovedInDjango21Warning
|
||||||
|
self.get_template_cache.clear()
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
# Wrapper for loading templates from eggs via pkg_resources.resource_string.
|
# Wrapper for loading templates from eggs via pkg_resources.resource_string.
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import warnings
|
||||||
|
|
||||||
from django.apps import apps
|
from django.apps import apps
|
||||||
from django.template.base import TemplateDoesNotExist
|
from django.template.base import Origin, TemplateDoesNotExist
|
||||||
from django.utils import six
|
from django.utils import six
|
||||||
|
from django.utils.deprecation import RemovedInDjango21Warning
|
||||||
|
|
||||||
from .base import Loader as BaseLoader
|
from .base import Loader as BaseLoader
|
||||||
|
|
||||||
|
@ -13,6 +16,14 @@ except ImportError:
|
||||||
resource_string = None
|
resource_string = None
|
||||||
|
|
||||||
|
|
||||||
|
class EggOrigin(Origin):
|
||||||
|
|
||||||
|
def __init__(self, app_name, pkg_name, *args, **kwargs):
|
||||||
|
self.app_name = app_name
|
||||||
|
self.pkg_name = pkg_name
|
||||||
|
return super(EggOrigin, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class Loader(BaseLoader):
|
class Loader(BaseLoader):
|
||||||
|
|
||||||
def __init__(self, engine):
|
def __init__(self, engine):
|
||||||
|
@ -20,19 +31,42 @@ class Loader(BaseLoader):
|
||||||
raise RuntimeError("Setuptools must be installed to use the egg loader")
|
raise RuntimeError("Setuptools must be installed to use the egg loader")
|
||||||
super(Loader, self).__init__(engine)
|
super(Loader, self).__init__(engine)
|
||||||
|
|
||||||
|
def get_contents(self, origin):
|
||||||
|
try:
|
||||||
|
source = resource_string(origin.app_name, origin.pkg_name)
|
||||||
|
except:
|
||||||
|
raise TemplateDoesNotExist(origin)
|
||||||
|
|
||||||
|
if six.PY2:
|
||||||
|
source = source.decode(self.engine.file_charset)
|
||||||
|
|
||||||
|
return source
|
||||||
|
|
||||||
|
def get_template_sources(self, template_name):
|
||||||
|
pkg_name = 'templates/' + template_name
|
||||||
|
for app_config in apps.get_app_configs():
|
||||||
|
yield EggOrigin(
|
||||||
|
app_name=app_config.name,
|
||||||
|
pkg_name=pkg_name,
|
||||||
|
name="egg:%s:%s" % (app_config.name, pkg_name),
|
||||||
|
template_name=template_name,
|
||||||
|
loader=self,
|
||||||
|
)
|
||||||
|
|
||||||
def load_template_source(self, template_name, template_dirs=None):
|
def load_template_source(self, template_name, template_dirs=None):
|
||||||
"""
|
"""
|
||||||
Loads templates from Python eggs via pkg_resource.resource_string.
|
Loads templates from Python eggs via pkg_resource.resource_string.
|
||||||
|
|
||||||
For every installed app, it tries to get the resource (app, template_name).
|
For every installed app, it tries to get the resource (app, template_name).
|
||||||
"""
|
"""
|
||||||
pkg_name = 'templates/' + template_name
|
warnings.warn(
|
||||||
for app_config in apps.get_app_configs():
|
'The load_template_sources() method is deprecated. Use '
|
||||||
|
'get_template() or get_contents() instead.',
|
||||||
|
RemovedInDjango21Warning,
|
||||||
|
)
|
||||||
|
for origin in self.get_template_sources(template_name):
|
||||||
try:
|
try:
|
||||||
resource = resource_string(app_config.name, pkg_name)
|
return self.get_contents(origin), origin.name
|
||||||
except Exception:
|
except TemplateDoesNotExist:
|
||||||
continue
|
pass
|
||||||
if six.PY2:
|
|
||||||
resource = resource.decode(self.engine.file_charset)
|
|
||||||
return (resource, 'egg:%s:%s' % (app_config.name, pkg_name))
|
|
||||||
raise TemplateDoesNotExist(template_name)
|
raise TemplateDoesNotExist(template_name)
|
||||||
|
|
|
@ -4,10 +4,12 @@ Wrapper for loading templates from the filesystem.
|
||||||
|
|
||||||
import errno
|
import errno
|
||||||
import io
|
import io
|
||||||
|
import warnings
|
||||||
|
|
||||||
from django.core.exceptions import SuspiciousFileOperation
|
from django.core.exceptions import SuspiciousFileOperation
|
||||||
from django.template.base import TemplateDoesNotExist
|
from django.template.base import Origin, TemplateDoesNotExist
|
||||||
from django.utils._os import safe_join
|
from django.utils._os import safe_join
|
||||||
|
from django.utils.deprecation import RemovedInDjango21Warning
|
||||||
|
|
||||||
from .base import Loader as BaseLoader
|
from .base import Loader as BaseLoader
|
||||||
|
|
||||||
|
@ -17,28 +19,46 @@ class Loader(BaseLoader):
|
||||||
def get_dirs(self):
|
def get_dirs(self):
|
||||||
return self.engine.dirs
|
return self.engine.dirs
|
||||||
|
|
||||||
|
def get_contents(self, origin):
|
||||||
|
try:
|
||||||
|
with io.open(origin.name, encoding=self.engine.file_charset) as fp:
|
||||||
|
return fp.read()
|
||||||
|
except IOError as e:
|
||||||
|
if e.errno == errno.ENOENT:
|
||||||
|
raise TemplateDoesNotExist(origin)
|
||||||
|
raise
|
||||||
|
|
||||||
def get_template_sources(self, template_name, template_dirs=None):
|
def get_template_sources(self, template_name, template_dirs=None):
|
||||||
"""
|
"""
|
||||||
Returns the absolute paths to "template_name", when appended to each
|
Return an Origin object pointing to an absolute path in each directory
|
||||||
directory in "template_dirs". Any paths that don't lie inside one of the
|
in template_dirs. For security reasons, if a path doesn't lie inside
|
||||||
template dirs are excluded from the result set, for security reasons.
|
one of the template_dirs it is excluded from the result set.
|
||||||
"""
|
"""
|
||||||
if not template_dirs:
|
if not template_dirs:
|
||||||
template_dirs = self.get_dirs()
|
template_dirs = self.get_dirs()
|
||||||
for template_dir in template_dirs:
|
for template_dir in template_dirs:
|
||||||
try:
|
try:
|
||||||
yield safe_join(template_dir, template_name)
|
name = safe_join(template_dir, template_name)
|
||||||
except SuspiciousFileOperation:
|
except SuspiciousFileOperation:
|
||||||
# The joined path was located outside of this template_dir
|
# The joined path was located outside of this template_dir
|
||||||
# (it might be inside another one, so this isn't fatal).
|
# (it might be inside another one, so this isn't fatal).
|
||||||
pass
|
continue
|
||||||
|
|
||||||
|
yield Origin(
|
||||||
|
name=name,
|
||||||
|
template_name=template_name,
|
||||||
|
loader=self,
|
||||||
|
)
|
||||||
|
|
||||||
def load_template_source(self, template_name, template_dirs=None):
|
def load_template_source(self, template_name, template_dirs=None):
|
||||||
for filepath in self.get_template_sources(template_name, template_dirs):
|
warnings.warn(
|
||||||
|
'The load_template_sources() method is deprecated. Use '
|
||||||
|
'get_template() or get_contents() instead.',
|
||||||
|
RemovedInDjango21Warning,
|
||||||
|
)
|
||||||
|
for origin in self.get_template_sources(template_name, template_dirs):
|
||||||
try:
|
try:
|
||||||
with io.open(filepath, encoding=self.engine.file_charset) as fp:
|
return self.get_contents(origin), origin.name
|
||||||
return fp.read(), filepath
|
except TemplateDoesNotExist:
|
||||||
except IOError as e:
|
pass
|
||||||
if e.errno != errno.ENOENT:
|
|
||||||
raise
|
|
||||||
raise TemplateDoesNotExist(template_name)
|
raise TemplateDoesNotExist(template_name)
|
||||||
|
|
|
@ -2,7 +2,10 @@
|
||||||
Wrapper for loading templates from a plain Python dict.
|
Wrapper for loading templates from a plain Python dict.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from django.template.base import TemplateDoesNotExist
|
import warnings
|
||||||
|
|
||||||
|
from django.template.base import Origin, TemplateDoesNotExist
|
||||||
|
from django.utils.deprecation import RemovedInDjango21Warning
|
||||||
|
|
||||||
from .base import Loader as BaseLoader
|
from .base import Loader as BaseLoader
|
||||||
|
|
||||||
|
@ -13,7 +16,25 @@ class Loader(BaseLoader):
|
||||||
self.templates_dict = templates_dict
|
self.templates_dict = templates_dict
|
||||||
super(Loader, self).__init__(engine)
|
super(Loader, self).__init__(engine)
|
||||||
|
|
||||||
|
def get_contents(self, origin):
|
||||||
|
try:
|
||||||
|
return self.templates_dict[origin.name]
|
||||||
|
except KeyError:
|
||||||
|
raise TemplateDoesNotExist(origin)
|
||||||
|
|
||||||
|
def get_template_sources(self, template_name):
|
||||||
|
yield Origin(
|
||||||
|
name=template_name,
|
||||||
|
template_name=template_name,
|
||||||
|
loader=self,
|
||||||
|
)
|
||||||
|
|
||||||
def load_template_source(self, template_name, template_dirs=None):
|
def load_template_source(self, template_name, template_dirs=None):
|
||||||
|
warnings.warn(
|
||||||
|
'The load_template_sources() method is deprecated. Use '
|
||||||
|
'get_template() or get_contents() instead.',
|
||||||
|
RemovedInDjango21Warning,
|
||||||
|
)
|
||||||
try:
|
try:
|
||||||
return self.templates_dict[template_name], template_name
|
return self.templates_dict[template_name], template_name
|
||||||
except KeyError:
|
except KeyError:
|
||||||
|
|
|
@ -37,6 +37,26 @@ details on these changes.
|
||||||
|
|
||||||
* The ``GeoManager`` and ``GeoQuerySet`` classes will be removed.
|
* The ``GeoManager`` and ``GeoQuerySet`` classes will be removed.
|
||||||
|
|
||||||
|
* The ``supports_recursion`` check for template loaders will be removed from:
|
||||||
|
|
||||||
|
* ``django.template.engine.Engine.find_template()``
|
||||||
|
* ``django.template.loader_tags.ExtendsNode.find_template()``
|
||||||
|
* ``django.template.loaders.base.Loader.supports_recursion()``
|
||||||
|
* ``django.template.loaders.cached.Loader.supports_recursion()``
|
||||||
|
|
||||||
|
* The ``load_template and ``load_template_sources`` template loader methods
|
||||||
|
will be removed.
|
||||||
|
|
||||||
|
* The ``template_dirs`` argument for template loaders will be removed:
|
||||||
|
|
||||||
|
* ``django.template.loaders.base.Loader.get_template()``
|
||||||
|
* ``django.template.loaders.cached.Loader.cache_key()``
|
||||||
|
* ``django.template.loaders.cached.Loader.get_template()``
|
||||||
|
* ``django.template.loaders.cached.Loader.get_template_sources()``
|
||||||
|
* ``django.template.loaders.filesystem.Loader.get_template_sources()``
|
||||||
|
|
||||||
|
* The ``django.template.loaders.base.Loader.__call__`` method will be removed.
|
||||||
|
|
||||||
.. _deprecation-removed-in-2.0:
|
.. _deprecation-removed-in-2.0:
|
||||||
|
|
||||||
2.0
|
2.0
|
||||||
|
|
|
@ -33,8 +33,12 @@ class TemplateLoaderTests(SimpleTestCase):
|
||||||
self.assertEqual(template.render(), "Hello! (Django templates)\n")
|
self.assertEqual(template.render(), "Hello! (Django templates)\n")
|
||||||
|
|
||||||
def test_get_template_not_found(self):
|
def test_get_template_not_found(self):
|
||||||
with self.assertRaises(TemplateDoesNotExist):
|
with self.assertRaises(TemplateDoesNotExist) as e:
|
||||||
get_template("template_loader/unknown.html")
|
get_template("template_loader/unknown.html")
|
||||||
|
self.assertEqual(
|
||||||
|
e.exception.tried[-1][0].template_name,
|
||||||
|
'template_loader/unknown.html',
|
||||||
|
)
|
||||||
|
|
||||||
def test_select_template_first_engine(self):
|
def test_select_template_first_engine(self):
|
||||||
template = select_template(["template_loader/unknown.html",
|
template = select_template(["template_loader/unknown.html",
|
||||||
|
@ -56,9 +60,17 @@ class TemplateLoaderTests(SimpleTestCase):
|
||||||
select_template([])
|
select_template([])
|
||||||
|
|
||||||
def test_select_template_not_found(self):
|
def test_select_template_not_found(self):
|
||||||
with self.assertRaises(TemplateDoesNotExist):
|
with self.assertRaises(TemplateDoesNotExist) as e:
|
||||||
select_template(["template_loader/unknown.html",
|
select_template(["template_loader/unknown.html",
|
||||||
"template_loader/missing.html"])
|
"template_loader/missing.html"])
|
||||||
|
self.assertEqual(
|
||||||
|
e.exception.tried[0][0].template_name,
|
||||||
|
'template_loader/unknown.html',
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
e.exception.tried[-1][0].template_name,
|
||||||
|
'template_loader/missing.html',
|
||||||
|
)
|
||||||
|
|
||||||
def test_select_template_tries_all_engines_before_names(self):
|
def test_select_template_tries_all_engines_before_names(self):
|
||||||
template = select_template(["template_loader/goodbye.html",
|
template = select_template(["template_loader/goodbye.html",
|
||||||
|
@ -83,8 +95,12 @@ class TemplateLoaderTests(SimpleTestCase):
|
||||||
self.assertEqual(content, "Hello! (Django templates)\n")
|
self.assertEqual(content, "Hello! (Django templates)\n")
|
||||||
|
|
||||||
def test_render_to_string_not_found(self):
|
def test_render_to_string_not_found(self):
|
||||||
with self.assertRaises(TemplateDoesNotExist):
|
with self.assertRaises(TemplateDoesNotExist) as e:
|
||||||
render_to_string("template_loader/unknown.html")
|
render_to_string("template_loader/unknown.html")
|
||||||
|
self.assertEqual(
|
||||||
|
e.exception.tried[-1][0].template_name,
|
||||||
|
'template_loader/unknown.html',
|
||||||
|
)
|
||||||
|
|
||||||
def test_render_to_string_with_list_first_engine(self):
|
def test_render_to_string_with_list_first_engine(self):
|
||||||
content = render_to_string(["template_loader/unknown.html",
|
content = render_to_string(["template_loader/unknown.html",
|
||||||
|
@ -106,9 +122,17 @@ class TemplateLoaderTests(SimpleTestCase):
|
||||||
render_to_string([])
|
render_to_string([])
|
||||||
|
|
||||||
def test_render_to_string_with_list_not_found(self):
|
def test_render_to_string_with_list_not_found(self):
|
||||||
with self.assertRaises(TemplateDoesNotExist):
|
with self.assertRaises(TemplateDoesNotExist) as e:
|
||||||
render_to_string(["template_loader/unknown.html",
|
render_to_string(["template_loader/unknown.html",
|
||||||
"template_loader/missing.html"])
|
"template_loader/missing.html"])
|
||||||
|
self.assertEqual(
|
||||||
|
e.exception.tried[0][0].template_name,
|
||||||
|
'template_loader/unknown.html',
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
e.exception.tried[-1][0].template_name,
|
||||||
|
'template_loader/missing.html',
|
||||||
|
)
|
||||||
|
|
||||||
def test_render_to_string_with_list_tries_all_engines_before_names(self):
|
def test_render_to_string_with_list_tries_all_engines_before_names(self):
|
||||||
content = render_to_string(["template_loader/goodbye.html",
|
content = render_to_string(["template_loader/goodbye.html",
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
{% extends "missing.html" %}
|
|
@ -0,0 +1,3 @@
|
||||||
|
{% extends "two.html" %}
|
||||||
|
|
||||||
|
{% block content %}{{ block.super }} one{% endblock %}
|
|
@ -0,0 +1 @@
|
||||||
|
{% extends "recursive.html" %}
|
|
@ -0,0 +1,3 @@
|
||||||
|
{% extends "recursive.html" %}
|
||||||
|
|
||||||
|
{% block content %}{{ block.super }} fs/recursive{% endblock %}
|
|
@ -0,0 +1 @@
|
||||||
|
{% extends "self.html" %}
|
|
@ -0,0 +1 @@
|
||||||
|
{% block content %}three{% endblock %}
|
|
@ -0,0 +1,3 @@
|
||||||
|
{% extends "three.html" %}
|
||||||
|
|
||||||
|
{% block content %}{{ block.super }} two{% endblock %}
|
|
@ -0,0 +1,3 @@
|
||||||
|
{% extends "recursive.html" %}
|
||||||
|
|
||||||
|
{% block content %}{{ block.super }} fs2/recursive{% endblock %}
|
|
@ -0,0 +1 @@
|
||||||
|
{% block content %}fs3/recursive{% endblock %}
|
|
@ -55,7 +55,7 @@ class LoaderTests(SimpleTestCase):
|
||||||
def test_origin(self):
|
def test_origin(self):
|
||||||
engine = Engine(dirs=[TEMPLATE_DIR], debug=True)
|
engine = Engine(dirs=[TEMPLATE_DIR], debug=True)
|
||||||
template = engine.get_template('index.html')
|
template = engine.get_template('index.html')
|
||||||
self.assertEqual(template.origin.loadname, 'index.html')
|
self.assertEqual(template.origin.template_name, 'index.html')
|
||||||
|
|
||||||
def test_loader_priority(self):
|
def test_loader_priority(self):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -0,0 +1,178 @@
|
||||||
|
import os
|
||||||
|
|
||||||
|
from django.template import Context, Engine, TemplateDoesNotExist
|
||||||
|
from django.template.loader_tags import ExtendsError
|
||||||
|
from django.template.loaders.base import Loader
|
||||||
|
from django.test import SimpleTestCase, ignore_warnings
|
||||||
|
from django.utils.deprecation import RemovedInDjango21Warning
|
||||||
|
|
||||||
|
from .utils import ROOT
|
||||||
|
|
||||||
|
RECURSIVE = os.path.join(ROOT, 'recursive_templates')
|
||||||
|
|
||||||
|
|
||||||
|
class ExtendsBehaviorTests(SimpleTestCase):
|
||||||
|
|
||||||
|
def test_normal_extend(self):
|
||||||
|
engine = Engine(dirs=[os.path.join(RECURSIVE, 'fs')])
|
||||||
|
template = engine.get_template('one.html')
|
||||||
|
output = template.render(Context({}))
|
||||||
|
self.assertEqual(output.strip(), 'three two one')
|
||||||
|
|
||||||
|
def test_extend_recursive(self):
|
||||||
|
engine = Engine(dirs=[
|
||||||
|
os.path.join(RECURSIVE, 'fs'),
|
||||||
|
os.path.join(RECURSIVE, 'fs2'),
|
||||||
|
os.path.join(RECURSIVE, 'fs3'),
|
||||||
|
])
|
||||||
|
template = engine.get_template('recursive.html')
|
||||||
|
output = template.render(Context({}))
|
||||||
|
self.assertEqual(output.strip(), 'fs3/recursive fs2/recursive fs/recursive')
|
||||||
|
|
||||||
|
def test_extend_missing(self):
|
||||||
|
engine = Engine(dirs=[os.path.join(RECURSIVE, 'fs')])
|
||||||
|
template = engine.get_template('extend-missing.html')
|
||||||
|
with self.assertRaises(TemplateDoesNotExist) as e:
|
||||||
|
template.render(Context({}))
|
||||||
|
|
||||||
|
tried = e.exception.tried
|
||||||
|
self.assertEqual(len(tried), 1)
|
||||||
|
self.assertEqual(tried[0][0].template_name, 'missing.html')
|
||||||
|
|
||||||
|
def test_recursive_multiple_loaders(self):
|
||||||
|
engine = Engine(
|
||||||
|
dirs=[os.path.join(RECURSIVE, 'fs')],
|
||||||
|
loaders=[
|
||||||
|
('django.template.loaders.locmem.Loader', {
|
||||||
|
'one.html': '{% extends "one.html" %}{% block content %}{{ block.super }} locmem-one{% endblock %}',
|
||||||
|
'two.html': '{% extends "two.html" %}{% block content %}{{ block.super }} locmem-two{% endblock %}',
|
||||||
|
'three.html': (
|
||||||
|
'{% extends "three.html" %}{% block content %}{{ block.super }} locmem-three{% endblock %}'
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
'django.template.loaders.filesystem.Loader',
|
||||||
|
],
|
||||||
|
)
|
||||||
|
template = engine.get_template('one.html')
|
||||||
|
output = template.render(Context({}))
|
||||||
|
self.assertEqual(output.strip(), 'three locmem-three two locmem-two one locmem-one')
|
||||||
|
|
||||||
|
def test_extend_self_error(self):
|
||||||
|
"""
|
||||||
|
Catch if a template extends itself and no other matching
|
||||||
|
templates are found.
|
||||||
|
"""
|
||||||
|
engine = Engine(dirs=[os.path.join(RECURSIVE, 'fs')])
|
||||||
|
template = engine.get_template('self.html')
|
||||||
|
with self.assertRaises(TemplateDoesNotExist):
|
||||||
|
template.render(Context({}))
|
||||||
|
|
||||||
|
def test_extend_cached(self):
|
||||||
|
engine = Engine(
|
||||||
|
dirs=[
|
||||||
|
os.path.join(RECURSIVE, 'fs'),
|
||||||
|
os.path.join(RECURSIVE, 'fs2'),
|
||||||
|
os.path.join(RECURSIVE, 'fs3'),
|
||||||
|
],
|
||||||
|
loaders=[
|
||||||
|
('django.template.loaders.cached.Loader', [
|
||||||
|
'django.template.loaders.filesystem.Loader',
|
||||||
|
]),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
template = engine.get_template('recursive.html')
|
||||||
|
output = template.render(Context({}))
|
||||||
|
self.assertEqual(output.strip(), 'fs3/recursive fs2/recursive fs/recursive')
|
||||||
|
|
||||||
|
cache = engine.template_loaders[0].get_template_cache
|
||||||
|
self.assertEqual(len(cache), 3)
|
||||||
|
self.assertTrue(cache['recursive.html'].origin.name.endswith('fs/recursive.html'))
|
||||||
|
|
||||||
|
# Render another path that uses the same templates from the cache
|
||||||
|
template = engine.get_template('other-recursive.html')
|
||||||
|
output = template.render(Context({}))
|
||||||
|
self.assertEqual(output.strip(), 'fs3/recursive fs2/recursive fs/recursive')
|
||||||
|
|
||||||
|
# Template objects should not be duplicated.
|
||||||
|
self.assertEqual(len(cache), 4)
|
||||||
|
self.assertTrue(cache['other-recursive.html'].origin.name.endswith('fs/other-recursive.html'))
|
||||||
|
|
||||||
|
def test_unique_history_per_loader(self):
|
||||||
|
"""
|
||||||
|
Extending should continue even if two loaders return the same
|
||||||
|
name for a template.
|
||||||
|
"""
|
||||||
|
engine = Engine(
|
||||||
|
loaders=[
|
||||||
|
['django.template.loaders.locmem.Loader', {
|
||||||
|
'base.html': '{% extends "base.html" %}{% block content %}{{ block.super }} loader1{% endblock %}',
|
||||||
|
}],
|
||||||
|
['django.template.loaders.locmem.Loader', {
|
||||||
|
'base.html': '{% block content %}loader2{% endblock %}',
|
||||||
|
}],
|
||||||
|
]
|
||||||
|
)
|
||||||
|
template = engine.get_template('base.html')
|
||||||
|
output = template.render(Context({}))
|
||||||
|
self.assertEqual(output.strip(), 'loader2 loader1')
|
||||||
|
|
||||||
|
|
||||||
|
class NonRecursiveLoader(Loader):
|
||||||
|
|
||||||
|
def __init__(self, engine, templates_dict):
|
||||||
|
self.templates_dict = templates_dict
|
||||||
|
super(NonRecursiveLoader, self).__init__(engine)
|
||||||
|
|
||||||
|
def load_template_source(self, template_name, template_dirs=None):
|
||||||
|
try:
|
||||||
|
return self.templates_dict[template_name], template_name
|
||||||
|
except KeyError:
|
||||||
|
raise TemplateDoesNotExist(template_name)
|
||||||
|
|
||||||
|
|
||||||
|
@ignore_warnings(category=RemovedInDjango21Warning)
|
||||||
|
class NonRecursiveLoaderExtendsTests(SimpleTestCase):
|
||||||
|
|
||||||
|
loaders = [
|
||||||
|
('template_tests.test_extends.NonRecursiveLoader', {
|
||||||
|
'base.html': 'base',
|
||||||
|
'index.html': '{% extends "base.html" %}',
|
||||||
|
'recursive.html': '{% extends "recursive.html" %}',
|
||||||
|
'other-recursive.html': '{% extends "recursive.html" %}',
|
||||||
|
'a.html': '{% extends "b.html" %}',
|
||||||
|
'b.html': '{% extends "a.html" %}',
|
||||||
|
}),
|
||||||
|
]
|
||||||
|
|
||||||
|
def test_extend(self):
|
||||||
|
engine = Engine(loaders=self.loaders)
|
||||||
|
output = engine.render_to_string('index.html')
|
||||||
|
self.assertEqual(output, 'base')
|
||||||
|
|
||||||
|
def test_extend_cached(self):
|
||||||
|
engine = Engine(loaders=[
|
||||||
|
('django.template.loaders.cached.Loader', self.loaders),
|
||||||
|
])
|
||||||
|
output = engine.render_to_string('index.html')
|
||||||
|
self.assertEqual(output, 'base')
|
||||||
|
|
||||||
|
cache = engine.template_loaders[0].template_cache
|
||||||
|
self.assertTrue('base.html' in cache)
|
||||||
|
self.assertTrue('index.html' in cache)
|
||||||
|
|
||||||
|
# Render a second time from cache
|
||||||
|
output = engine.render_to_string('index.html')
|
||||||
|
self.assertEqual(output, 'base')
|
||||||
|
|
||||||
|
def test_extend_error(self):
|
||||||
|
engine = Engine(loaders=self.loaders)
|
||||||
|
msg = 'Cannot extend templates recursively when using non-recursive template loaders'
|
||||||
|
|
||||||
|
with self.assertRaisesMessage(ExtendsError, msg):
|
||||||
|
engine.render_to_string('recursive.html')
|
||||||
|
|
||||||
|
with self.assertRaisesMessage(ExtendsError, msg):
|
||||||
|
engine.render_to_string('other-recursive.html')
|
||||||
|
|
||||||
|
with self.assertRaisesMessage(ExtendsError, msg):
|
||||||
|
engine.render_to_string('a.html')
|
|
@ -10,8 +10,9 @@ from contextlib import contextmanager
|
||||||
|
|
||||||
from django.template import Context, TemplateDoesNotExist
|
from django.template import Context, TemplateDoesNotExist
|
||||||
from django.template.engine import Engine
|
from django.template.engine import Engine
|
||||||
from django.test import SimpleTestCase, override_settings
|
from django.test import SimpleTestCase, ignore_warnings, override_settings
|
||||||
from django.utils import six
|
from django.utils import six
|
||||||
|
from django.utils.deprecation import RemovedInDjango21Warning
|
||||||
|
|
||||||
from .utils import TEMPLATE_DIR
|
from .utils import TEMPLATE_DIR
|
||||||
|
|
||||||
|
@ -23,8 +24,9 @@ except ImportError:
|
||||||
|
|
||||||
class CachedLoaderTests(SimpleTestCase):
|
class CachedLoaderTests(SimpleTestCase):
|
||||||
|
|
||||||
def create_engine(self, **kwargs):
|
def setUp(self):
|
||||||
return Engine(
|
self.engine = Engine(
|
||||||
|
dirs=[TEMPLATE_DIR],
|
||||||
loaders=[
|
loaders=[
|
||||||
('django.template.loaders.cached.Loader', [
|
('django.template.loaders.cached.Loader', [
|
||||||
'django.template.loaders.filesystem.Loader',
|
'django.template.loaders.filesystem.Loader',
|
||||||
|
@ -32,26 +34,47 @@ class CachedLoaderTests(SimpleTestCase):
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_templatedir_caching(self):
|
def test_get_template(self):
|
||||||
"""
|
template = self.engine.get_template('index.html')
|
||||||
#13573 -- Template directories should be part of the cache key.
|
self.assertEqual(template.origin.name, os.path.join(TEMPLATE_DIR, 'index.html'))
|
||||||
"""
|
self.assertEqual(template.origin.template_name, 'index.html')
|
||||||
engine = self.create_engine()
|
self.assertEqual(template.origin.loader, self.engine.template_loaders[0].loaders[0])
|
||||||
|
|
||||||
# Retrieve a template specifying a template directory to check
|
cache = self.engine.template_loaders[0].get_template_cache
|
||||||
t1, name = engine.find_template('test.html', (os.path.join(TEMPLATE_DIR, 'first'),))
|
self.assertEqual(cache['index.html'], template)
|
||||||
# Now retrieve the same template name, but from a different directory
|
|
||||||
t2, name = engine.find_template('test.html', (os.path.join(TEMPLATE_DIR, 'second'),))
|
|
||||||
|
|
||||||
# The two templates should not have the same content
|
# Run a second time from cache
|
||||||
self.assertNotEqual(t1.render(Context({})), t2.render(Context({})))
|
template = self.engine.get_template('index.html')
|
||||||
|
self.assertEqual(template.origin.name, os.path.join(TEMPLATE_DIR, 'index.html'))
|
||||||
|
self.assertEqual(template.origin.template_name, 'index.html')
|
||||||
|
self.assertEqual(template.origin.loader, self.engine.template_loaders[0].loaders[0])
|
||||||
|
|
||||||
def test_missing_template_is_cached(self):
|
def test_get_template_missing(self):
|
||||||
|
with self.assertRaises(TemplateDoesNotExist):
|
||||||
|
self.engine.get_template('doesnotexist.html')
|
||||||
|
e = self.engine.template_loaders[0].get_template_cache['doesnotexist.html']
|
||||||
|
self.assertEqual(e.args[0], 'doesnotexist.html')
|
||||||
|
|
||||||
|
@ignore_warnings(category=RemovedInDjango21Warning)
|
||||||
|
def test_load_template(self):
|
||||||
|
loader = self.engine.template_loaders[0]
|
||||||
|
template, origin = loader.load_template('index.html')
|
||||||
|
self.assertEqual(template.origin.template_name, 'index.html')
|
||||||
|
|
||||||
|
cache = self.engine.template_loaders[0].template_cache
|
||||||
|
self.assertEqual(cache['index.html'][0], template)
|
||||||
|
|
||||||
|
# Run a second time from cache
|
||||||
|
loader = self.engine.template_loaders[0]
|
||||||
|
source, name = loader.load_template('index.html')
|
||||||
|
self.assertEqual(template.origin.template_name, 'index.html')
|
||||||
|
|
||||||
|
@ignore_warnings(category=RemovedInDjango21Warning)
|
||||||
|
def test_load_template_missing(self):
|
||||||
"""
|
"""
|
||||||
#19949 -- TemplateDoesNotExist exceptions should be cached.
|
#19949 -- TemplateDoesNotExist exceptions should be cached.
|
||||||
"""
|
"""
|
||||||
engine = self.create_engine()
|
loader = self.engine.template_loaders[0]
|
||||||
loader = engine.template_loaders[0]
|
|
||||||
|
|
||||||
self.assertFalse('missing.html' in loader.template_cache)
|
self.assertFalse('missing.html' in loader.template_cache)
|
||||||
|
|
||||||
|
@ -64,6 +87,18 @@ class CachedLoaderTests(SimpleTestCase):
|
||||||
"Cached loader failed to cache the TemplateDoesNotExist exception",
|
"Cached loader failed to cache the TemplateDoesNotExist exception",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_templatedir_caching(self):
|
||||||
|
"""
|
||||||
|
#13573 -- Template directories should be part of the cache key.
|
||||||
|
"""
|
||||||
|
# Retrieve a template specifying a template directory to check
|
||||||
|
t1, name = self.engine.find_template('test.html', (os.path.join(TEMPLATE_DIR, 'first'),))
|
||||||
|
# Now retrieve the same template name, but from a different directory
|
||||||
|
t2, name = self.engine.find_template('test.html', (os.path.join(TEMPLATE_DIR, 'second'),))
|
||||||
|
|
||||||
|
# The two templates should not have the same content
|
||||||
|
self.assertNotEqual(t1.render(Context({})), t2.render(Context({})))
|
||||||
|
|
||||||
|
|
||||||
@unittest.skipUnless(pkg_resources, 'setuptools is not installed')
|
@unittest.skipUnless(pkg_resources, 'setuptools is not installed')
|
||||||
class EggLoaderTests(SimpleTestCase):
|
class EggLoaderTests(SimpleTestCase):
|
||||||
|
@ -117,22 +152,43 @@ class EggLoaderTests(SimpleTestCase):
|
||||||
del sys.modules[name]
|
del sys.modules[name]
|
||||||
del pkg_resources._provider_factories[MockLoader]
|
del pkg_resources._provider_factories[MockLoader]
|
||||||
|
|
||||||
def setUp(self):
|
@classmethod
|
||||||
engine = Engine(loaders=[
|
def setUpClass(cls):
|
||||||
|
cls.engine = Engine(loaders=[
|
||||||
'django.template.loaders.eggs.Loader',
|
'django.template.loaders.eggs.Loader',
|
||||||
])
|
])
|
||||||
self.loader = engine.template_loaders[0]
|
cls.loader = cls.engine.template_loaders[0]
|
||||||
|
super(EggLoaderTests, cls).setUpClass()
|
||||||
|
|
||||||
def test_existing(self):
|
def test_get_template(self):
|
||||||
templates = {
|
templates = {
|
||||||
os.path.normcase('templates/y.html'): six.StringIO("y"),
|
os.path.normcase('templates/y.html'): six.StringIO("y"),
|
||||||
}
|
}
|
||||||
|
|
||||||
with self.create_egg('egg', templates):
|
with self.create_egg('egg', templates):
|
||||||
with override_settings(INSTALLED_APPS=['egg']):
|
with override_settings(INSTALLED_APPS=['egg']):
|
||||||
contents, template_name = self.loader.load_template_source("y.html")
|
template = self.engine.get_template("y.html")
|
||||||
self.assertEqual(contents, "y")
|
|
||||||
self.assertEqual(template_name, "egg:egg:templates/y.html")
|
self.assertEqual(template.origin.name, 'egg:egg:templates/y.html')
|
||||||
|
self.assertEqual(template.origin.template_name, 'y.html')
|
||||||
|
self.assertEqual(template.origin.loader, self.engine.template_loaders[0])
|
||||||
|
|
||||||
|
output = template.render(Context({}))
|
||||||
|
self.assertEqual(output, "y")
|
||||||
|
|
||||||
|
@ignore_warnings(category=RemovedInDjango21Warning)
|
||||||
|
def test_load_template_source(self):
|
||||||
|
loader = self.engine.template_loaders[0]
|
||||||
|
templates = {
|
||||||
|
os.path.normcase('templates/y.html'): six.StringIO("y"),
|
||||||
|
}
|
||||||
|
|
||||||
|
with self.create_egg('egg', templates):
|
||||||
|
with override_settings(INSTALLED_APPS=['egg']):
|
||||||
|
source, name = loader.load_template_source('y.html')
|
||||||
|
|
||||||
|
self.assertEqual(source.strip(), 'y')
|
||||||
|
self.assertEqual(name, 'egg:egg:templates/y.html')
|
||||||
|
|
||||||
def test_non_existing(self):
|
def test_non_existing(self):
|
||||||
"""
|
"""
|
||||||
|
@ -141,7 +197,7 @@ class EggLoaderTests(SimpleTestCase):
|
||||||
with self.create_egg('egg', {}):
|
with self.create_egg('egg', {}):
|
||||||
with override_settings(INSTALLED_APPS=['egg']):
|
with override_settings(INSTALLED_APPS=['egg']):
|
||||||
with self.assertRaises(TemplateDoesNotExist):
|
with self.assertRaises(TemplateDoesNotExist):
|
||||||
self.loader.load_template_source("not-existing.html")
|
self.engine.get_template('not-existing.html')
|
||||||
|
|
||||||
def test_not_installed(self):
|
def test_not_installed(self):
|
||||||
"""
|
"""
|
||||||
|
@ -153,13 +209,15 @@ class EggLoaderTests(SimpleTestCase):
|
||||||
|
|
||||||
with self.create_egg('egg', templates):
|
with self.create_egg('egg', templates):
|
||||||
with self.assertRaises(TemplateDoesNotExist):
|
with self.assertRaises(TemplateDoesNotExist):
|
||||||
self.loader.load_template_source("y.html")
|
self.engine.get_template('y.html')
|
||||||
|
|
||||||
|
|
||||||
class FileSystemLoaderTests(SimpleTestCase):
|
class FileSystemLoaderTests(SimpleTestCase):
|
||||||
|
|
||||||
def setUp(self):
|
@classmethod
|
||||||
self.engine = Engine(dirs=[TEMPLATE_DIR])
|
def setUpClass(cls):
|
||||||
|
cls.engine = Engine(dirs=[TEMPLATE_DIR])
|
||||||
|
super(FileSystemLoaderTests, cls).setUpClass()
|
||||||
|
|
||||||
@contextmanager
|
@contextmanager
|
||||||
def set_dirs(self, dirs):
|
def set_dirs(self, dirs):
|
||||||
|
@ -177,13 +235,27 @@ class FileSystemLoaderTests(SimpleTestCase):
|
||||||
def check_sources(path, expected_sources):
|
def check_sources(path, expected_sources):
|
||||||
expected_sources = [os.path.abspath(s) for s in expected_sources]
|
expected_sources = [os.path.abspath(s) for s in expected_sources]
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
list(loader.get_template_sources(path)),
|
[origin.name for origin in loader.get_template_sources(path)],
|
||||||
expected_sources,
|
expected_sources,
|
||||||
)
|
)
|
||||||
|
|
||||||
with self.set_dirs(dirs):
|
with self.set_dirs(dirs):
|
||||||
yield check_sources
|
yield check_sources
|
||||||
|
|
||||||
|
def test_get_template(self):
|
||||||
|
template = self.engine.get_template('index.html')
|
||||||
|
self.assertEqual(template.origin.name, os.path.join(TEMPLATE_DIR, 'index.html'))
|
||||||
|
self.assertEqual(template.origin.template_name, 'index.html')
|
||||||
|
self.assertEqual(template.origin.loader, self.engine.template_loaders[0])
|
||||||
|
self.assertEqual(template.origin.loader_name, 'django.template.loaders.filesystem.Loader')
|
||||||
|
|
||||||
|
@ignore_warnings(category=RemovedInDjango21Warning)
|
||||||
|
def test_load_template_source(self):
|
||||||
|
loader = self.engine.template_loaders[0]
|
||||||
|
source, name = loader.load_template_source('index.html')
|
||||||
|
self.assertEqual(source.strip(), 'index')
|
||||||
|
self.assertEqual(name, os.path.join(TEMPLATE_DIR, 'index.html'))
|
||||||
|
|
||||||
def test_directory_security(self):
|
def test_directory_security(self):
|
||||||
with self.source_checker(['/dir1', '/dir2']) as check_sources:
|
with self.source_checker(['/dir1', '/dir2']) as check_sources:
|
||||||
check_sources('index.html', ['/dir1/index.html', '/dir2/index.html'])
|
check_sources('index.html', ['/dir1/index.html', '/dir2/index.html'])
|
||||||
|
@ -248,18 +320,56 @@ class FileSystemLoaderTests(SimpleTestCase):
|
||||||
self.engine.get_template('first')
|
self.engine.get_template('first')
|
||||||
|
|
||||||
|
|
||||||
class AppDirectoriesLoaderTest(SimpleTestCase):
|
class AppDirectoriesLoaderTests(SimpleTestCase):
|
||||||
|
|
||||||
def setUp(self):
|
@classmethod
|
||||||
self.engine = Engine(
|
def setUpClass(cls):
|
||||||
|
cls.engine = Engine(
|
||||||
loaders=['django.template.loaders.app_directories.Loader'],
|
loaders=['django.template.loaders.app_directories.Loader'],
|
||||||
)
|
)
|
||||||
|
super(AppDirectoriesLoaderTests, cls).setUpClass()
|
||||||
|
|
||||||
@override_settings(INSTALLED_APPS=['template_tests'])
|
@override_settings(INSTALLED_APPS=['template_tests'])
|
||||||
def test_load_template(self):
|
def test_get_template(self):
|
||||||
self.engine.get_template('index.html')
|
template = self.engine.get_template('index.html')
|
||||||
|
self.assertEqual(template.origin.name, os.path.join(TEMPLATE_DIR, 'index.html'))
|
||||||
|
self.assertEqual(template.origin.template_name, 'index.html')
|
||||||
|
self.assertEqual(template.origin.loader, self.engine.template_loaders[0])
|
||||||
|
|
||||||
|
@ignore_warnings(category=RemovedInDjango21Warning)
|
||||||
|
@override_settings(INSTALLED_APPS=['template_tests'])
|
||||||
|
def test_load_template_source(self):
|
||||||
|
loader = self.engine.template_loaders[0]
|
||||||
|
source, name = loader.load_template_source('index.html')
|
||||||
|
self.assertEqual(source.strip(), 'index')
|
||||||
|
self.assertEqual(name, os.path.join(TEMPLATE_DIR, 'index.html'))
|
||||||
|
|
||||||
@override_settings(INSTALLED_APPS=[])
|
@override_settings(INSTALLED_APPS=[])
|
||||||
def test_not_installed(self):
|
def test_not_installed(self):
|
||||||
with self.assertRaises(TemplateDoesNotExist):
|
with self.assertRaises(TemplateDoesNotExist):
|
||||||
self.engine.get_template('index.html')
|
self.engine.get_template('index.html')
|
||||||
|
|
||||||
|
|
||||||
|
class LocmemLoaderTests(SimpleTestCase):
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls):
|
||||||
|
cls.engine = Engine(
|
||||||
|
loaders=[('django.template.loaders.locmem.Loader', {
|
||||||
|
'index.html': 'index',
|
||||||
|
})],
|
||||||
|
)
|
||||||
|
super(LocmemLoaderTests, cls).setUpClass()
|
||||||
|
|
||||||
|
def test_get_template(self):
|
||||||
|
template = self.engine.get_template('index.html')
|
||||||
|
self.assertEqual(template.origin.name, 'index.html')
|
||||||
|
self.assertEqual(template.origin.template_name, 'index.html')
|
||||||
|
self.assertEqual(template.origin.loader, self.engine.template_loaders[0])
|
||||||
|
|
||||||
|
@ignore_warnings(category=RemovedInDjango21Warning)
|
||||||
|
def test_load_template_source(self):
|
||||||
|
loader = self.engine.template_loaders[0]
|
||||||
|
source, name = loader.load_template_source('index.html')
|
||||||
|
self.assertEqual(source.strip(), 'index')
|
||||||
|
self.assertEqual(name, 'index.html')
|
||||||
|
|
|
@ -6,6 +6,7 @@ import sys
|
||||||
from django.contrib.auth.models import Group
|
from django.contrib.auth.models import Group
|
||||||
from django.core import urlresolvers
|
from django.core import urlresolvers
|
||||||
from django.template import Context, Engine, TemplateSyntaxError
|
from django.template import Context, Engine, TemplateSyntaxError
|
||||||
|
from django.template.base import UNKNOWN_SOURCE
|
||||||
from django.test import SimpleTestCase, override_settings
|
from django.test import SimpleTestCase, override_settings
|
||||||
|
|
||||||
|
|
||||||
|
@ -13,7 +14,9 @@ class TemplateTests(SimpleTestCase):
|
||||||
|
|
||||||
def test_string_origin(self):
|
def test_string_origin(self):
|
||||||
template = Engine().from_string('string template')
|
template = Engine().from_string('string template')
|
||||||
self.assertEqual(template.origin.source, 'string template')
|
self.assertEqual(template.origin.name, UNKNOWN_SOURCE)
|
||||||
|
self.assertEqual(template.origin.loader_name, None)
|
||||||
|
self.assertEqual(template.source, 'string template')
|
||||||
|
|
||||||
@override_settings(SETTINGS_MODULE=None)
|
@override_settings(SETTINGS_MODULE=None)
|
||||||
def test_url_reverse_no_settings_module(self):
|
def test_url_reverse_no_settings_module(self):
|
||||||
|
|
Loading…
Reference in New Issue