Skip to content

Commit

Permalink
Fixed django#6262 -- Added a cached template loader, and modified exi…
Browse files Browse the repository at this point in the history
…sting 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
freakboy3742 committed Dec 14, 2009
1 parent 5a23505 commit 44b9076
Show file tree
Hide file tree
Showing 23 changed files with 667 additions and 194 deletions.
1 change: 1 addition & 0 deletions AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 3 additions & 3 deletions django/conf/global_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
6 changes: 3 additions & 3 deletions django/conf/project_template/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 = (
Expand Down
9 changes: 8 additions & 1 deletion django/template/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
62 changes: 48 additions & 14 deletions django/template/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -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():
Expand Down
7 changes: 5 additions & 2 deletions django/template/defaulttags.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
103 changes: 84 additions & 19 deletions django/template/loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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:
Expand All @@ -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):
Expand Down
Loading

0 comments on commit 44b9076

Please sign in to comment.