Permalink
Browse files

Fixed #6262 -- Added a cached template loader, and modified existing …

…template loaders and tag to be cacheable. Thanks to Mike Malone for the patch.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@11862 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
1 parent 5a23505 commit 44b9076bbed3e629230d9b77a8765e4c906036d1 @freakboy3742 freakboy3742 committed Dec 14, 2009
View
@@ -288,6 +288,7 @@ answer newbie questions, and generally made Django that much better:
Martin Mahner <http://www.mahner.org/>
Matt McClanahan <http://mmcc.cx/>
Frantisek Malina <vizualbod@vizualbod.com>
+ Mike Malone <mjmalone@gmail.com>
Martin Maney <http://www.chipy.org/Martin_Maney>
masonsimon+django@gmail.com
Manuzhai
@@ -158,9 +158,9 @@
# See the comments in django/core/template/loader.py for interface
# documentation.
TEMPLATE_LOADERS = (
- 'django.template.loaders.filesystem.load_template_source',
- 'django.template.loaders.app_directories.load_template_source',
-# 'django.template.loaders.eggs.load_template_source',
+ 'django.template.loaders.filesystem.Loader',
+ 'django.template.loaders.app_directories.Loader',
+# 'django.template.loaders.eggs.Loader',
)
# List of processors used by RequestContext to populate the context.
@@ -52,9 +52,9 @@
# List of callables that know how to import templates from various sources.
TEMPLATE_LOADERS = (
- 'django.template.loaders.filesystem.load_template_source',
- 'django.template.loaders.app_directories.load_template_source',
-# 'django.template.loaders.eggs.load_template_source',
+ 'django.template.loaders.filesystem.Loader',
+ 'django.template.loaders.app_directories.Loader',
+# 'django.template.loaders.eggs.Loader',
)
MIDDLEWARE_CLASSES = (
@@ -173,9 +173,16 @@ def __iter__(self):
for subnode in node:
yield subnode
+ def _render(self, context):
+ return self.nodelist.render(context)
+
def render(self, context):
"Display stage -- can be called many times"
- return self.nodelist.render(context)
+ context.render_context.push()
+ try:
+ return self._render(context)
+ finally:
+ context.render_context.pop()
def compile_string(template_string, origin):
"Compiles template_string into NodeList ready for rendering"
@@ -12,67 +12,101 @@ class ContextPopException(Exception):
"pop() has been called more times than push()"
pass
-class Context(object):
- "A stack container for variable context"
- def __init__(self, dict_=None, autoescape=True, current_app=None):
+class BaseContext(object):
+ def __init__(self, dict_=None):
dict_ = dict_ or {}
self.dicts = [dict_]
- self.autoescape = autoescape
- self.current_app = current_app
def __repr__(self):
return repr(self.dicts)
def __iter__(self):
- for d in self.dicts:
+ for d in reversed(self.dicts):
yield d
def push(self):
d = {}
- self.dicts = [d] + self.dicts
+ self.dicts.append(d)
return d
def pop(self):
if len(self.dicts) == 1:
raise ContextPopException
- return self.dicts.pop(0)
+ return self.dicts.pop()
def __setitem__(self, key, value):
"Set a variable in the current context"
- self.dicts[0][key] = value
+ self.dicts[-1][key] = value
def __getitem__(self, key):
"Get a variable's value, starting at the current context and going upward"
- for d in self.dicts:
+ for d in reversed(self.dicts):
if key in d:
return d[key]
raise KeyError(key)
def __delitem__(self, key):
"Delete a variable from the current context"
- del self.dicts[0][key]
+ del self.dicts[-1][key]
def has_key(self, key):
for d in self.dicts:
if key in d:
return True
return False
- __contains__ = has_key
+ def __contains__(self, key):
+ return self.has_key(key)
def get(self, key, otherwise=None):
- for d in self.dicts:
+ for d in reversed(self.dicts):
if key in d:
return d[key]
return otherwise
+class Context(BaseContext):
+ "A stack container for variable context"
+ def __init__(self, dict_=None, autoescape=True, current_app=None):
+ self.autoescape = autoescape
+ self.current_app = current_app
+ self.render_context = RenderContext()
+ super(Context, self).__init__(dict_)
+
def update(self, other_dict):
"Like dict.update(). Pushes an entire dictionary's keys and values onto the context."
if not hasattr(other_dict, '__getitem__'):
raise TypeError('other_dict must be a mapping (dictionary-like) object.')
- self.dicts = [other_dict] + self.dicts
+ self.dicts.append(other_dict)
return other_dict
+class RenderContext(BaseContext):
+ """
+ A stack container for storing Template state.
+
+ RenderContext simplifies the implementation of template Nodes by providing a
+ safe place to store state between invocations of a node's `render` method.
+
+ The RenderContext also provides scoping rules that are more sensible for
+ 'template local' variables. The render context stack is pushed before each
+ template is rendered, creating a fresh scope with nothing in it. Name
+ resolution fails if a variable is not found at the top of the RequestContext
+ stack. Thus, variables are local to a specific template and don't affect the
+ rendering of other templates as they would if they were stored in the normal
+ template context.
+ """
+ def __iter__(self):
+ for d in self.dicts[-1]:
+ yield d
+
+ def has_key(self, key):
+ return key in self.dicts[-1]
+
+ def get(self, key, otherwise=None):
+ d = self.dicts[-1]
+ if key in d:
+ return d[key]
+ return otherwise
+
# This is a function rather than module-level procedural code because we only
# want it to execute if somebody uses RequestContext.
def get_standard_processors():
@@ -57,11 +57,14 @@ def render(self, context):
class CycleNode(Node):
def __init__(self, cyclevars, variable_name=None):
- self.cycle_iter = itertools_cycle(cyclevars)
+ self.cyclevars = cyclevars
self.variable_name = variable_name
def render(self, context):
- value = self.cycle_iter.next().resolve(context)
+ if self not in context.render_context:
+ context.render_context[self] = itertools_cycle(self.cyclevars)
+ cycle_iter = context.render_context[self]
+ value = cycle_iter.next().resolve(context)
if self.variable_name:
context[self.variable_name] = value
return value
@@ -27,6 +27,36 @@
template_source_loaders = None
+class BaseLoader(object):
+ is_usable = False
+
+ def __init__(self, *args, **kwargs):
+ pass
+
+ def __call__(self, template_name, template_dirs=None):
+ return self.load_template(template_name, template_dirs)
+
+ def load_template(self, template_name, template_dirs=None):
+ source, origin = self.load_template_source(template_name, template_dirs)
+ template = get_template_from_string(source, name=template_name)
+ return template, origin
+
+ def load_template_source(self, template_name, template_dirs=None):
+ """
+ Returns a tuple containing the source and origin for the given template
+ name.
+
+ """
+ raise NotImplementedError
+
+ def reset(self):
+ """
+ Resets any state maintained by the loader instance (e.g., cached
+ templates or cached loader modules).
+
+ """
+ pass
+
class LoaderOrigin(Origin):
def __init__(self, display_name, loader, name, dirs):
super(LoaderOrigin, self).__init__(display_name)
@@ -41,29 +71,50 @@ def make_origin(display_name, loader, name, dirs):
else:
return None
-def find_template_source(name, dirs=None):
+def find_template_loader(loader):
+ if hasattr(loader, '__iter__'):
+ loader, args = loader[0], loader[1:]
+ else:
+ args = []
+ if isinstance(loader, basestring):
+ module, attr = loader.rsplit('.', 1)
+ try:
+ mod = import_module(module)
+ except ImportError:
+ raise ImproperlyConfigured('Error importing template source loader %s: "%s"' % (loader, e))
+ try:
+ TemplateLoader = getattr(mod, attr)
+ except AttributeError, e:
+ raise ImproperlyConfigured('Error importing template source loader %s: "%s"' % (loader, e))
+
+ if hasattr(TemplateLoader, 'load_template_source'):
+ func = TemplateLoader(*args)
+ else:
+ # Try loading module the old way - string is full path to callable
+ if args:
+ raise ImproperlyConfigured("Error importing template source loader %s - can't pass arguments to function-based loader." % loader)
+ func = TemplateLoader
+
+ if not func.is_usable:
+ import warnings
+ 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 func
+ else:
+ raise ImproperlyConfigured('Loader does not define a "load_template" callable template source loader')
+
+def find_template(name, dirs=None):
# Calculate template_source_loaders the first time the function is executed
# because putting this logic in the module-level namespace may cause
# circular import errors. See Django ticket #1292.
global template_source_loaders
if template_source_loaders is None:
loaders = []
- for path in settings.TEMPLATE_LOADERS:
- i = path.rfind('.')
- module, attr = path[:i], path[i+1:]
- try:
- mod = import_module(module)
- except ImportError, e:
- raise ImproperlyConfigured, 'Error importing template source loader %s: "%s"' % (module, e)
- try:
- func = getattr(mod, attr)
- except AttributeError:
- raise ImproperlyConfigured, 'Module "%s" does not define a "%s" callable template source loader' % (module, attr)
- if not func.is_usable:
- import warnings
- 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." % path)
- else:
- loaders.append(func)
+ for loader_name in settings.TEMPLATE_LOADERS:
+ loader = find_template_loader(loader_name)
+ if loader is not None:
+ loaders.append(loader)
template_source_loaders = tuple(loaders)
for loader in template_source_loaders:
try:
@@ -73,13 +124,27 @@ def find_template_source(name, dirs=None):
pass
raise TemplateDoesNotExist, name
+def find_template_source(name, dirs=None):
+ # For backward compatibility
+ import warnings
+ warnings.warn(
+ "`django.template.loaders.find_template_source` is deprecated; use `django.template.loaders.find_template` instead.",
+ PendingDeprecationWarning
+ )
+ template, origin = find_template(name, dirs)
+ if hasattr(template, 'render'):
+ raise Exception("Found a compiled template that is incompatible with the deprecated `django.template.loaders.find_template_source` function.")
+ return template, origin
+
def get_template(template_name):
"""
Returns a compiled Template object for the given template name,
handling template inheritance recursively.
"""
- source, origin = find_template_source(template_name)
- template = get_template_from_string(source, origin, template_name)
+ template, origin = find_template(template_name)
+ if not hasattr(template, 'render'):
+ # template needs to be compiled
+ template = get_template_from_string(template, origin, template_name)
return template
def get_template_from_string(source, origin=None, name=None):
Oops, something went wrong.

0 comments on commit 44b9076

Please sign in to comment.