Ticket #6262: cache_templates.5.diff

File cache_templates.5.diff, 48.0 KB (added by Michael Malone, 14 years ago)
  • django/test/utils.py

     
    3737        - Set the email backend to the locmem email backend.
    3838        - Setting the active locale to match the LANGUAGE_CODE setting.
    3939    """
    40     Template.original_render = Template.render
    41     Template.render = instrumented_test_render
     40    Template.original_render = Template._render
     41    Template._render = instrumented_test_render
    4242
    4343    mail.original_SMTPConnection = mail.SMTPConnection
    4444    mail.SMTPConnection = locmem.EmailBackend
     
    5757        - Restoring the email sending functions
    5858
    5959    """
    60     Template.render = Template.original_render
     60    Template._render = Template.original_render
    6161    del Template.original_render
    6262
    6363    mail.SMTPConnection = mail.original_SMTPConnection
  • django/conf/project_template/settings.py

     
    5252
    5353# List of callables that know how to import templates from various sources.
    5454TEMPLATE_LOADERS = (
    55     'django.template.loaders.filesystem.load_template_source',
    56     'django.template.loaders.app_directories.load_template_source',
    57 #     'django.template.loaders.eggs.load_template_source',
     55    'django.template.loaders.filesystem.loader',
     56    'django.template.loaders.app_directories.loader',
     57#     'django.template.loaders.eggs.loader',
    5858)
    5959
    6060MIDDLEWARE_CLASSES = (
  • django/conf/global_settings.py

     
    158158# See the comments in django/core/template/loader.py for interface
    159159# documentation.
    160160TEMPLATE_LOADERS = (
    161     'django.template.loaders.filesystem.load_template_source',
    162     'django.template.loaders.app_directories.load_template_source',
    163 #     'django.template.loaders.eggs.load_template_source',
     161    'django.template.loaders.filesystem.loader',
     162    'django.template.loaders.app_directories.loader',
     163#     'django.template.loaders.eggs.loader',
    164164)
    165165
    166166# List of processors used by RequestContext to populate the context.
  • django/views/debug.py

     
    7676                        for t in source_list_func(str(self.exc_value))]
    7777                except (ImportError, AttributeError):
    7878                    template_list = []
     79                if hasattr(loader, '__class__'):
     80                    loader_name = loader.__module__ + '.' + loader.__class__.__name__
     81                else:
     82                    loader_name = loader.__module__ + '.' + loader.__name__
    7983                self.loader_debug_info.append({
    80                     'loader': loader.__module__ + '.' + loader.__name__,
     84                    'loader': loader_name,
    8185                    'templates': template_list,
    8286                })
    8387        if settings.TEMPLATE_DEBUG and hasattr(self.exc_value, 'source'):
  • django/template/__init__.py

     
    173173            for subnode in node:
    174174                yield subnode
    175175
     176    def _render(self, context):
     177        return self.nodelist.render(context)
     178
    176179    def render(self, context):
    177180        "Display stage -- can be called many times"
    178         return self.nodelist.render(context)
     181        context.render_context.push()
     182        try:
     183            return self._render(context)
     184        finally:
     185            context.render_context.pop()
    179186
    180187def compile_string(template_string, origin):
    181188    "Compiles template_string into NodeList ready for rendering"
  • django/template/loaders/app_directories.py

     
    99from django.conf import settings
    1010from django.core.exceptions import ImproperlyConfigured
    1111from django.template import TemplateDoesNotExist
     12from django.template.loader import BaseLoader
    1213from django.utils._os import safe_join
    1314from django.utils.importlib import import_module
    1415
     
    2728# It won't change, so convert it to a tuple to save memory.
    2829app_template_dirs = tuple(app_template_dirs)
    2930
    30 def get_template_sources(template_name, template_dirs=None):
    31     """
    32     Returns the absolute paths to "template_name", when appended to each
    33     directory in "template_dirs". Any paths that don't lie inside one of the
    34     template dirs are excluded from the result set, for security reasons.
    35     """
    36     if not template_dirs:
    37         template_dirs = app_template_dirs
    38     for template_dir in template_dirs:
    39         try:
    40             yield safe_join(template_dir, template_name)
    41         except UnicodeDecodeError:
    42             # The template dir name was a bytestring that wasn't valid UTF-8.
    43             raise
    44         except ValueError:
    45             # The joined path was located outside of template_dir.
    46             pass
     31class Loader(BaseLoader):
     32    is_usable = True
    4733
     34    def get_template_sources(self, template_name, template_dirs=None):
     35        """
     36        Returns the absolute paths to "template_name", when appended to each
     37        directory in "template_dirs". Any paths that don't lie inside one of the
     38        template dirs are excluded from the result set, for security reasons.
     39        """
     40        if not template_dirs:
     41            template_dirs = app_template_dirs
     42        for template_dir in template_dirs:
     43            try:
     44                yield safe_join(template_dir, template_name)
     45            except UnicodeDecodeError:
     46                # The template dir name was a bytestring that wasn't valid UTF-8.
     47                raise
     48            except ValueError:
     49                # The joined path was located outside of template_dir.
     50                pass
     51
     52    def load_template_source(self, template_name, template_dirs=None):
     53        for filepath in self.get_template_sources(template_name, template_dirs):
     54            try:
     55                file = open(filepath)
     56                try:
     57                    return (file.read().decode(settings.FILE_CHARSET), filepath)
     58                finally:
     59                    file.close()
     60            except IOError:
     61                pass
     62        raise TemplateDoesNotExist, template_name
     63
     64loader = Loader()
     65
    4866def load_template_source(template_name, template_dirs=None):
    49     for filepath in get_template_sources(template_name, template_dirs):
    50         try:
    51             return (open(filepath).read().decode(settings.FILE_CHARSET), filepath)
    52         except IOError:
    53             pass
    54     raise TemplateDoesNotExist, template_name
     67    # For backwards compatibility
     68    return loader.load_template_source(template_name, template_dirs)
    5569load_template_source.is_usable = True
  • django/template/loaders/filesystem.py

     
    44
    55from django.conf import settings
    66from django.template import TemplateDoesNotExist
     7from django.template.loader import BaseLoader
    78from django.utils._os import safe_join
    89
    9 def get_template_sources(template_name, template_dirs=None):
    10     """
    11     Returns the absolute paths to "template_name", when appended to each
    12     directory in "template_dirs". Any paths that don't lie inside one of the
    13     template dirs are excluded from the result set, for security reasons.
    14     """
    15     if not template_dirs:
    16         template_dirs = settings.TEMPLATE_DIRS
    17     for template_dir in template_dirs:
    18         try:
    19             yield safe_join(template_dir, template_name)
    20         except UnicodeDecodeError:
    21             # The template dir name was a bytestring that wasn't valid UTF-8.
    22             raise
    23         except ValueError:
    24             # The joined path was located outside of this particular
    25             # template_dir (it might be inside another one, so this isn't
    26             # fatal).
    27             pass
     10class Loader(BaseLoader):
     11    is_usable = True
    2812
     13    def get_template_sources(self, template_name, template_dirs=None):
     14        """
     15        Returns the absolute paths to "template_name", when appended to each
     16        directory in "template_dirs". Any paths that don't lie inside one of the
     17        template dirs are excluded from the result set, for security reasons.
     18        """
     19        if not template_dirs:
     20            template_dirs = settings.TEMPLATE_DIRS
     21        for template_dir in template_dirs:
     22            try:
     23                yield safe_join(template_dir, template_name)
     24            except UnicodeDecodeError:
     25                # The template dir name was a bytestring that wasn't valid UTF-8.
     26                raise
     27            except ValueError:
     28                # The joined path was located outside of this particular
     29                # template_dir (it might be inside another one, so this isn't
     30                # fatal).
     31                pass
     32
     33    def load_template_source(self, template_name, template_dirs=None):
     34        tried = []
     35        for filepath in self.get_template_sources(template_name, template_dirs):
     36            try:
     37                file = open(filepath)
     38                try:
     39                    return (file.read().decode(settings.FILE_CHARSET), filepath)
     40                finally:
     41                    file.close()
     42            except IOError:
     43                tried.append(filepath)
     44        if tried:
     45            error_msg = "Tried %s" % tried
     46        else:
     47            error_msg = "Your TEMPLATE_DIRS setting is empty. Change it to point to at least one template directory."
     48        raise TemplateDoesNotExist, error_msg
     49    load_template_source.is_usable = True
     50
     51loader = Loader()
     52
    2953def load_template_source(template_name, template_dirs=None):
    30     tried = []
    31     for filepath in get_template_sources(template_name, template_dirs):
    32         try:
    33             return (open(filepath).read().decode(settings.FILE_CHARSET), filepath)
    34         except IOError:
    35             tried.append(filepath)
    36     if tried:
    37         error_msg = "Tried %s" % tried
    38     else:
    39         error_msg = "Your TEMPLATE_DIRS setting is empty. Change it to point to at least one template directory."
    40     raise TemplateDoesNotExist, error_msg
     54    # For backwards compatibility
     55    return loader.load_template_source(template_name, template_dirs)
    4156load_template_source.is_usable = True
  • django/template/loaders/eggs.py

     
    66    resource_string = None
    77
    88from django.template import TemplateDoesNotExist
     9from django.template.loader import BaseLoader
    910from django.conf import settings
    1011
     12class Loader(BaseLoader):
     13    is_usable = resource_string is not None
     14
     15    def load_template_source(self, template_name, template_dirs=None):
     16        """
     17        Loads templates from Python eggs via pkg_resource.resource_string.
     18
     19        For every installed app, it tries to get the resource (app, template_name).
     20        """
     21        if resource_string is not None:
     22            pkg_name = 'templates/' + template_name
     23            for app in settings.INSTALLED_APPS:
     24                try:
     25                    return (resource_string(app, pkg_name).decode(settings.FILE_CHARSET), 'egg:%s:%s' % (app, pkg_name))
     26                except:
     27                    pass
     28        raise TemplateDoesNotExist, template_name
     29
     30loader = Loader()
     31
    1132def load_template_source(template_name, template_dirs=None):
    12     """
    13     Loads templates from Python eggs via pkg_resource.resource_string.
    14 
    15     For every installed app, it tries to get the resource (app, template_name).
    16     """
    17     if resource_string is not None:
    18         pkg_name = 'templates/' + template_name
    19         for app in settings.INSTALLED_APPS:
    20             try:
    21                 return (resource_string(app, pkg_name).decode(settings.FILE_CHARSET), 'egg:%s:%s' % (app, pkg_name))
    22             except:
    23                 pass
    24     raise TemplateDoesNotExist, template_name
     33    return loader.load_template_source(template_name, template_dirs)
    2534load_template_source.is_usable = resource_string is not None
  • django/template/loaders/cached.py

     
     1"""
     2Wrapper class that takes a list of template loaders as an argument and attempts
     3to load templates from them in order, caching the result.
     4"""
     5
     6from django.template import TemplateDoesNotExist
     7from django.template.loader import BaseLoader, get_template_from_string, find_template_loader, make_origin
     8from django.utils.importlib import import_module
     9from django.core.exceptions import ImproperlyConfigured
     10
     11class Loader(BaseLoader):
     12    is_usable = True
     13
     14    def __init__(self, loaders):
     15        self.template_cache = {}
     16        self._loaders = loaders
     17        self._cached_loaders = []
     18
     19    @property
     20    def loaders(self):
     21        # Resolve loaders on demand to avoid circular imports
     22        if not self._cached_loaders:
     23            for loader in self._loaders:
     24                self._cached_loaders.append(find_template_loader(loader))
     25        return self._cached_loaders
     26
     27    def find_template(self, name, dirs=None):
     28        for loader in self.loaders:
     29            try:
     30                template, display_name = loader(name, dirs)
     31                return (template, make_origin(display_name, loader, name, dirs))
     32            except TemplateDoesNotExist:
     33                pass
     34        raise TemplateDoesNotExist, name
     35
     36    def load_template(self, template_name, template_dirs=None):
     37        if template_name not in self.template_cache:
     38            template, origin = self.find_template(template_name, template_dirs)
     39            if not hasattr(template, 'render'):
     40                template = get_template_from_string(template, origin, template_name)
     41            self.template_cache[template_name] = (template, origin)
     42        return self.template_cache[template_name]
     43
     44    def reset(self):
     45        "Empty the template cache."
     46        self.template_cache.clear()
  • django/template/defaulttags.py

     
    5656
    5757class CycleNode(Node):
    5858    def __init__(self, cyclevars, variable_name=None):
    59         self.cycle_iter = itertools_cycle(cyclevars)
     59        self.cyclevars = cyclevars
    6060        self.variable_name = variable_name
    6161
    6262    def render(self, context):
    63         value = self.cycle_iter.next().resolve(context)
     63        if self not in context.render_context:
     64            context.render_context[self] = itertools_cycle(self.cyclevars)
     65        cycle_iter = context.render_context[self]
     66        value = cycle_iter.next().resolve(context)
    6467        if self.variable_name:
    6568            context[self.variable_name] = value
    6669        return value
  • django/template/context.py

     
    1212    "pop() has been called more times than push()"
    1313    pass
    1414
    15 class Context(object):
    16     "A stack container for variable context"
    17     def __init__(self, dict_=None, autoescape=True, current_app=None):
     15class BaseContext(object):
     16    def __init__(self, dict_=None):
    1817        dict_ = dict_ or {}
    1918        self.dicts = [dict_]
    20         self.autoescape = autoescape
    21         self.current_app = current_app
    2219
    2320    def __repr__(self):
    2421        return repr(self.dicts)
    2522
    2623    def __iter__(self):
    27         for d in self.dicts:
     24        for d in reversed(self.dicts):
    2825            yield d
    29 
     26   
    3027    def push(self):
    3128        d = {}
    32         self.dicts = [d] + self.dicts
     29        self.dicts.append(d)
    3330        return d
    3431
    3532    def pop(self):
    3633        if len(self.dicts) == 1:
    3734            raise ContextPopException
    38         return self.dicts.pop(0)
     35        return self.dicts.pop()
    3936
    4037    def __setitem__(self, key, value):
    4138        "Set a variable in the current context"
    42         self.dicts[0][key] = value
     39        self.dicts[-1][key] = value
    4340
    4441    def __getitem__(self, key):
    4542        "Get a variable's value, starting at the current context and going upward"
    46         for d in self.dicts:
     43        for d in reversed(self.dicts):
    4744            if key in d:
    4845                return d[key]
    4946        raise KeyError(key)
    5047
    5148    def __delitem__(self, key):
    5249        "Delete a variable from the current context"
    53         del self.dicts[0][key]
     50        del self.dicts[-1][key]
    5451
    5552    def has_key(self, key):
    5653        for d in self.dicts:
     
    5855                return True
    5956        return False
    6057
    61     __contains__ = has_key
     58    def __contains__(self, key):
     59        return self.has_key(key)
    6260
    6361    def get(self, key, otherwise=None):
    6462        for d in self.dicts:
     
    6664                return d[key]
    6765        return otherwise
    6866
     67class Context(BaseContext):
     68    "A stack container for variable context"
     69    def __init__(self, dict_=None, autoescape=True, current_app=None):
     70        self.autoescape = autoescape
     71        self.current_app = current_app
     72        self.render_context = RenderContext()
     73        super(Context, self).__init__(dict_)
     74
    6975    def update(self, other_dict):
    7076        "Like dict.update(). Pushes an entire dictionary's keys and values onto the context."
    7177        if not hasattr(other_dict, '__getitem__'):
    7278            raise TypeError('other_dict must be a mapping (dictionary-like) object.')
    73         self.dicts = [other_dict] + self.dicts
     79        self.dicts.append(other_dict)
    7480        return other_dict
    7581
     82class RenderContext(BaseContext):
     83    """
     84    A stack container for storing Template state.
     85
     86    RenderContext simplifies the implementation of template Nodes by providing a
     87    safe place to store state between invocations of a node's `render` method.
     88
     89    The RenderContext also provides scoping rules that are more sensible for
     90    'template local' variables. The render context stack is pushed before each
     91    template is renderd, creating a fresh scope with nothing in it. Name
     92    resolution fails if a variable is not found at the top of the RequestContext
     93    stack. Thus, variables are local to a specific template and don't affect the
     94    rendering of other templates as they would if they were stored in the normal
     95    template context.
     96    """
     97    def __iter__(self):
     98        for d in self.dicts[-1]:
     99            yield d
     100
     101    def has_key(self, key):
     102        return key in self.dicts[-1]
     103
     104    def get(self, key, otherwise=None):
     105        d = self.dicts[-1]
     106        if key in d:
     107            return d[key]
     108        return otherwise
     109
    76110# This is a function rather than module-level procedural code because we only
    77111# want it to execute if somebody uses RequestContext.
    78112def get_standard_processors():
  • django/template/loader_tags.py

     
    11from django.template import TemplateSyntaxError, TemplateDoesNotExist, Variable
    22from django.template import Library, Node, TextNode
    3 from django.template.loader import get_template, get_template_from_string, find_template_source
     3from django.template.loader import get_template
    44from django.conf import settings
    55from django.utils.safestring import mark_safe
    66
    77register = Library()
    88
     9BLOCK_CONTEXT_KEY = 'block_context'
     10
    911class ExtendsError(Exception):
    1012    pass
    1113
     14class BlockContext(object):
     15    def __init__(self):
     16        # Dictionary of FIFO queues.
     17        self.blocks = {}
     18
     19    def add_blocks(self, blocks):
     20        for name, block in blocks.iteritems():
     21            if name in self.blocks:
     22                self.blocks[name].insert(0, block)
     23            else:
     24                self.blocks[name] = [block]
     25
     26    def pop(self, name):
     27        try:
     28            return self.blocks[name].pop()
     29        except (IndexError, KeyError):
     30            return None
     31
     32    def push(self, name, block):
     33        self.blocks[name].append(block)
     34
     35    def get_block(self, name):
     36        try:
     37            return self.blocks[name][-1]
     38        except (IndexError, KeyError):
     39            return None
     40
    1241class BlockNode(Node):
    1342    def __init__(self, name, nodelist, parent=None):
    1443        self.name, self.nodelist, self.parent = name, nodelist, parent
     
    1746        return "<Block Node: %s. Contents: %r>" % (self.name, self.nodelist)
    1847
    1948    def render(self, context):
     49        block_context = context.render_context.get(BLOCK_CONTEXT_KEY)
    2050        context.push()
    21         # Save context in case of block.super().
    22         self.context = context
    23         context['block'] = self
    24         result = self.nodelist.render(context)
     51        if block_context is None:
     52            context['block'] = self
     53            result = self.nodelist.render(context)
     54        else:
     55            push = block = block_context.pop(self.name)
     56            if block is None:
     57                block = self
     58            # Create new block so we can store context without thread-safety issues.
     59            block = BlockNode(block.name, block.nodelist)
     60            block.context = context
     61            context['block'] = block
     62            result = block.nodelist.render(context)
     63            if push is not None:
     64                block_context.push(self.name, push)
    2565        context.pop()
    2666        return result
    2767
    2868    def super(self):
    29         if self.parent:
    30             return mark_safe(self.parent.render(self.context))
     69        render_context = self.context.render_context
     70        if (BLOCK_CONTEXT_KEY in render_context and
     71            render_context[BLOCK_CONTEXT_KEY].get_block(self.name) is not None):
     72            return mark_safe(self.render(self.context))
    3173        return ''
    3274
    33     def add_parent(self, nodelist):
    34         if self.parent:
    35             self.parent.add_parent(nodelist)
    36         else:
    37             self.parent = BlockNode(self.name, nodelist)
    38 
    3975class ExtendsNode(Node):
    4076    must_be_first = True
    4177
     
    4379        self.nodelist = nodelist
    4480        self.parent_name, self.parent_name_expr = parent_name, parent_name_expr
    4581        self.template_dirs = template_dirs
    46 
     82        self.blocks = dict([(n.name, n) for n in nodelist.get_nodes_by_type(BlockNode)])
     83 
    4784    def __repr__(self):
    4885        if self.parent_name_expr:
    4986            return "<ExtendsNode: extends %s>" % self.parent_name_expr.token
     
    6198        if hasattr(parent, 'render'):
    6299            return parent # parent is a Template object
    63100        try:
    64             source, origin = find_template_source(parent, self.template_dirs)
     101            return get_template(parent)
    65102        except TemplateDoesNotExist:
    66103            raise TemplateSyntaxError, "Template %r cannot be extended, because it doesn't exist" % parent
    67         else:
    68             return get_template_from_string(source, origin, parent)
    69104
    70105    def render(self, context):
    71106        compiled_parent = self.get_parent(context)
    72         parent_blocks = dict([(n.name, n) for n in compiled_parent.nodelist.get_nodes_by_type(BlockNode)])
    73         for block_node in self.nodelist.get_nodes_by_type(BlockNode):
    74             # Check for a BlockNode with this node's name, and replace it if found.
    75             try:
    76                 parent_block = parent_blocks[block_node.name]
    77             except KeyError:
    78                 # This BlockNode wasn't found in the parent template, but the
    79                 # parent block might be defined in the parent's *parent*, so we
    80                 # add this BlockNode to the parent's ExtendsNode nodelist, so
    81                 # it'll be checked when the parent node's render() is called.
    82107
    83                 # Find out if the parent template has a parent itself
    84                 for node in compiled_parent.nodelist:
    85                     if not isinstance(node, TextNode):
    86                         # If the first non-text node is an extends, handle it.
    87                         if isinstance(node, ExtendsNode):
    88                             node.nodelist.append(block_node)
    89                         # Extends must be the first non-text node, so once you find
    90                         # the first non-text node you can stop looking.
    91                         break
    92             else:
    93                 # Keep any existing parents and add a new one. Used by BlockNode.
    94                 parent_block.parent = block_node.parent
    95                 parent_block.add_parent(parent_block.nodelist)
    96                 parent_block.nodelist = block_node.nodelist
    97         return compiled_parent.render(context)
     108        if BLOCK_CONTEXT_KEY not in context.render_context:
     109            context.render_context[BLOCK_CONTEXT_KEY] = BlockContext()
     110        block_context = context.render_context[BLOCK_CONTEXT_KEY]
    98111
     112        # Add the block nodes from this node to the block context
     113        block_context.add_blocks(self.blocks)
     114
     115        # If this block's parent doesn't have an extends node it is the root,
     116        # and its block nodes also need to be added to the block context.
     117        for node in compiled_parent.nodelist:
     118            # The ExtendsNode has to be the first non-text node.
     119            if not isinstance(node, TextNode):
     120                if not isinstance(node, ExtendsNode):
     121                    blocks = dict([(n.name, n) for n in
     122                                   compiled_parent.nodelist.get_nodes_by_type(BlockNode)])
     123                    block_context.add_blocks(blocks)
     124                break
     125
     126        # Call Template._render explicitly so the parser context stays
     127        # the same.
     128        return compiled_parent._render(context)
     129
    99130class ConstantIncludeNode(Node):
    100131    def __init__(self, template_path):
    101132        try:
  • django/template/loader.py

     
    2727
    2828template_source_loaders = None
    2929
     30class BaseLoader(object):
     31    is_usable = False
     32
     33    def __call__(self, template_name, template_dirs=None):
     34        return self.load_template(template_name, template_dirs)
     35
     36    def load_template(self, template_name, template_dirs=None):
     37        source, origin = self.load_template_source(template_name, template_dirs)
     38        template = get_template_from_string(source, name=template_name)
     39        return template, origin
     40   
     41    def load_template_source(self, template_name, template_dirs=None):
     42        """
     43        Returns a tuple containing the source and origin for the given template
     44        name.
     45
     46        """
     47        raise NotImplementedError
     48
     49    def reset(self):
     50        """
     51        Resets any state maintained by the loader instance (e.g., cached
     52        templates or cached loader modules).
     53
     54        """
     55        pass
     56
    3057class LoaderOrigin(Origin):
    3158    def __init__(self, display_name, loader, name, dirs):
    3259        super(LoaderOrigin, self).__init__(display_name)
     
    4168    else:
    4269        return None
    4370
    44 def find_template_source(name, dirs=None):
     71def find_template_loader(loader):
     72    if callable(loader):
     73        return loader
     74    elif isinstance(loader, basestring):
     75        i = loader.rfind('.')
     76        module, attr = loader[:i], loader[i+1:]
     77        try:
     78            mod = import_module(module)
     79        except ImportError, e:
     80            raise ImproperlyConfigured, 'Error importing template source loader %s: "%s"' % (module, e)
     81        try:
     82            func = getattr(mod, attr)
     83        except AttributeError:
     84            raise ImproperlyConfigured, 'Module "%s" does not define a "%s" callable template source loader' % (module, attr)
     85        if not func.is_usable:
     86            import warnings
     87            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)
     88        else:
     89            return func
     90    else:
     91        raise ImproperlyConfigured, 'Loader does not define a "load_template" callable template source loader'
     92
     93def find_template(name, dirs=None):
    4594    # Calculate template_source_loaders the first time the function is executed
    4695    # because putting this logic in the module-level namespace may cause
    4796    # circular import errors. See Django ticket #1292.
    4897    global template_source_loaders
    4998    if template_source_loaders is None:
    5099        loaders = []
    51         for path in settings.TEMPLATE_LOADERS:
    52             i = path.rfind('.')
    53             module, attr = path[:i], path[i+1:]
    54             try:
    55                 mod = import_module(module)
    56             except ImportError, e:
    57                 raise ImproperlyConfigured, 'Error importing template source loader %s: "%s"' % (module, e)
    58             try:
    59                 func = getattr(mod, attr)
    60             except AttributeError:
    61                 raise ImproperlyConfigured, 'Module "%s" does not define a "%s" callable template source loader' % (module, attr)
    62             if not func.is_usable:
    63                 import warnings
    64                 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)
    65             else:
    66                 loaders.append(func)
     100        for loader in settings.TEMPLATE_LOADERS:
     101            loaders.append(find_template_loader(loader))
    67102        template_source_loaders = tuple(loaders)
    68103    for loader in template_source_loaders:
    69104        try:
     
    78113    Returns a compiled Template object for the given template name,
    79114    handling template inheritance recursively.
    80115    """
    81     source, origin = find_template_source(template_name)
    82     template = get_template_from_string(source, origin, template_name)
     116    template, origin = find_template(template_name)
     117    if not hasattr(template, 'render'):
     118        # template needs to be compiled
     119        template = get_template_from_string(template, origin, template_name)
    83120    return template
    84121
    85122def get_template_from_string(source, origin=None, name=None):
  • tests/regressiontests/test_client_regress/models.py

     
    1010from django.core.urlresolvers import reverse
    1111from django.core.exceptions import SuspiciousOperation
    1212from django.template import TemplateDoesNotExist, TemplateSyntaxError, Context
     13from django.template.loader import find_template_loader
    1314
    1415class AssertContainsTests(TestCase):
    1516    def setUp(self):
     
    436437
    437438class TemplateExceptionTests(TestCase):
    438439    def setUp(self):
     440        # Reset the loaders so they don't try to render cached templates.
     441        for loader in settings.TEMPLATE_LOADERS:
     442            loader = find_template_loader(loader)
     443            if hasattr(loader, 'reset'):
     444                loader.reset()
    439445        self.old_templates = settings.TEMPLATE_DIRS
    440446        settings.TEMPLATE_DIRS = ()
    441447
  • tests/regressiontests/templates/tests.py

     
    1515from django import template
    1616from django.core import urlresolvers
    1717from django.template import loader
    18 from django.template.loaders import app_directories, filesystem
     18from django.template.loaders import app_directories, filesystem, cached
    1919from django.utils.translation import activate, deactivate, ugettext as _
    2020from django.utils.safestring import mark_safe
    2121from django.utils.tzinfo import LocalTimezone
     
    105105                # Fix expected sources so they are normcased and abspathed
    106106                expected_sources = [os.path.normcase(os.path.abspath(s)) for s in expected_sources]
    107107            # Test the two loaders (app_directores and filesystem).
    108             func1 = lambda p, t: list(app_directories.get_template_sources(p, t))
    109             func2 = lambda p, t: list(filesystem.get_template_sources(p, t))
     108            func1 = lambda p, t: list(app_directories.loader.get_template_sources(p, t))
     109            func2 = lambda p, t: list(filesystem.loader.get_template_sources(p, t))
    110110            for func in (func1, func2):
    111111                if isinstance(expected_sources, list):
    112112                    self.assertEqual(func(path, template_dirs), expected_sources)
     
    197197            except KeyError:
    198198                raise template.TemplateDoesNotExist, template_name
    199199
     200        cache_loader = cached.Loader((test_template_loader,))
     201
    200202        old_template_loaders = loader.template_source_loaders
    201         loader.template_source_loaders = [test_template_loader]
     203        loader.template_source_loaders = [cache_loader]
    202204
    203205        failures = []
    204206        tests = template_tests.items()
     
    231233            for invalid_str, result in [('', normal_string_result),
    232234                                        (expected_invalid_str, invalid_string_result)]:
    233235                settings.TEMPLATE_STRING_IF_INVALID = invalid_str
    234                 try:
    235                     test_template = loader.get_template(name)
    236                     output = self.render(test_template, vals)
    237                 except ContextStackException:
    238                     failures.append("Template test (TEMPLATE_STRING_IF_INVALID='%s'): %s -- FAILED. Context stack was left imbalanced" % (invalid_str, name))
    239                     continue
    240                 except Exception:
    241                     exc_type, exc_value, exc_tb = sys.exc_info()
    242                     if exc_type != result:
    243                         tb = '\n'.join(traceback.format_exception(exc_type, exc_value, exc_tb))
    244                         failures.append("Template test (TEMPLATE_STRING_IF_INVALID='%s'): %s -- FAILED. Got %s, exception: %s\n%s" % (invalid_str, name, exc_type, exc_value, tb))
    245                     continue
    246                 if output != result:
    247                     failures.append("Template test (TEMPLATE_STRING_IF_INVALID='%s'): %s -- FAILED. Expected %r, got %r" % (invalid_str, name, result, output))
     236                for is_cached in (False, True):
     237                    try:
     238                        test_template = loader.get_template(name)
     239                        output = self.render(test_template, vals)
     240                    except ContextStackException:
     241                        failures.append("Template test (Cached='%s', TEMPLATE_STRING_IF_INVALID='%s'): %s -- FAILED. Context stack was left imbalanced" % (is_cached, invalid_str, name))
     242                        continue
     243                    except Exception:
     244                        exc_type, exc_value, exc_tb = sys.exc_info()
     245                        if exc_type != result:
     246                            tb = '\n'.join(traceback.format_exception(exc_type, exc_value, exc_tb))
     247                            failures.append("Template test (Cached='%s', TEMPLATE_STRING_IF_INVALID='%s'): %s -- FAILED. Got %s, exception: %s\n%s" % (is_cached, invalid_str, name, exc_type, exc_value, tb))
     248                        continue
     249                    if output != result:
     250                        failures.append("Template test (Cached='%s', TEMPLATE_STRING_IF_INVALID='%s'): %s -- FAILED. Expected %r, got %r" % (is_cached, invalid_str, name, result, output))
     251                cache_loader.reset()
    248252
    249253            if 'LANGUAGE_CODE' in vals[1]:
    250254                deactivate()
  • AUTHORS

     
    283283    Martin Mahner <http://www.mahner.org/>
    284284    Matt McClanahan <http://mmcc.cx/>
    285285    Frantisek Malina <vizualbod@vizualbod.com>
     286    Mike Malone <mjmalone@gmail.com>
    286287    Martin Maney <http://www.chipy.org/Martin_Maney>
    287288    masonsimon+django@gmail.com
    288289    Manuzhai
  • docs/howto/custom-template-tags.txt

     
    463463automatically escaped, which may not be the desired behavior if the template
    464464tag is used inside a ``{% autoescape off %}`` block.
    465465
     466.. _template_tag_thread_safety:
     467
     468Thread-safety considerations
     469~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     470
     471.. versionadded:: 1.2
     472
     473Once a node is parsed, its ``render`` method may be called any number of times.
     474Since Django is sometimes run in multi-threaded environments, a single node may
     475be simultaneously rendering with different contexts in response to two separate
     476requests. Therefore, it's important to make sure your template tags are thread
     477safe.
     478
     479To make sure your template tags are thread safe, you should never store state
     480information on the node itself. For example, Django provides a builtin ``cycle``
     481template tag that cycles among a list of given strings each time it's rendered::
     482
     483    {% for o in some_list %}
     484        <tr class="{% cycle 'row1' 'row2' %}>
     485            ...
     486        </tr>
     487    {% endfor %}
     488
     489A naive implementation of ``CycleNode`` might look something like this::
     490   
     491    class CycleNode(Node):
     492        def __init__(self, cyclevars):
     493            self.cycle_iter = itertools.cycle(cyclevars)
     494        def render(self, context):
     495            return self.cycle_iter.next()
     496
     497But, suppose we have two templates rendering the template snippet from above at
     498the same time:
     499    1. Thread 1 performs its first loop iteration, ``CycleNode.render()``
     500       returns 'row1'
     501    2. Thread 2 performs its first loop iteration, ``CycleNode.render()``
     502       returns 'row2'
     503    3. Thread 1 performs its second loop iteration, ``CycleNode.render()``
     504       returns 'row1'
     505    4. Thread 2 performs its second loop iteration, ``CycleNode.render()``
     506       returns 'row2'
     507
     508The CycleNode is iterating, but it's iterating globally. As far as Thread 1
     509and Thread 2 are concerned, it's always returning the same value. This is
     510obviously not what we want!
     511
     512To address this problem, Django provides a ``render_context`` that's associated
     513with the ``context`` of the template that is currently being rendered. The
     514``render_context`` behaves like a Python dictionary, and should be used to store
     515``Node`` state between invocations of the ``render`` method.
     516
     517Let's refactor our ``CycleNode`` implementation to use the ``render_context``::
     518   
     519    class CycleNode(Node):
     520        def __init__(self, cyclevars):
     521            self.cyclevars = cyclevars
     522        def render(self, context):
     523            if self not in context.render_context:
     524                context.render_context[self] = itertools.cycle(self.cyclevars)
     525            cycle_iter = context.render_context[self]
     526            return cycle_iter.next()
     527
     528Note that it's perfectly safe to store global information that will not change
     529throughout the life of the ``Node`` as an attribute. In the case of
     530``CycleNode``, the ``cyclevars`` argument doesn't change after the ``Node`` is
     531instantiated, so we don't need to put it in the ``render_context``. But state
     532information that is specific to the template that is currently being rendered,
     533like the current iteration of the ``CycleNode``, should be stored in the
     534``render_context``.
     535
     536.. note::
     537    Notice how we used ``self`` to scope the ``CycleNode`` specific information
     538    within the ``render_context``. There may be ``CycleNode``'s in a
     539    given template, so we need to be careful not to clobber another node's state
     540    information. The easiest way to do this is to always use ``self`` as the key
     541    into ``render_context``. If you're keeping track of several state variables,
     542    make ``render_context[self]`` a dictionary.
     543
    466544Registering the tag
    467545~~~~~~~~~~~~~~~~~~~
    468546
  • docs/ref/templates/api.txt

     
    546546should be a tuple of strings, where each string represents a template loader.
    547547Here are the template loaders that come with Django:
    548548
    549 ``django.template.loaders.filesystem.load_template_source``
     549``django.template.loaders.filesystem.loader``
    550550    Loads templates from the filesystem, according to :setting:`TEMPLATE_DIRS`.
    551551    This loader is enabled by default.
    552552
    553 ``django.template.loaders.app_directories.load_template_source``
     553``django.template.loaders.app_directories.loader``
    554554    Loads templates from Django apps on the filesystem. For each app in
    555555    :setting:`INSTALLED_APPS`, the loader looks for a ``templates``
    556556    subdirectory. If the directory exists, Django looks for templates in there.
     
    574574
    575575    This loader is enabled by default.
    576576
    577 ``django.template.loaders.eggs.load_template_source``
     577``django.template.loaders.eggs.loader``
    578578    Just like ``app_directories`` above, but it loads templates from Python
    579579    eggs rather than from the filesystem.
    580580
    581581    This loader is disabled by default.
    582582
     583``django.template.loaders.cached.Loader``
     584    By default, the templating system will read and compile your templates every
     585    time they need to be rendered. While the Django templating system is quite
     586    fast, the overhead from reading and compiling templates can add up.
     587   
     588    The cached template loader is a class-based loader that you instantiate with
     589    a list of other loaders that it should wrap. The wrapped loaders are used to
     590    locate unknown templates when they are first encountered. The cached loader
     591    then stores the compiled ``Template`` in memory. The cached ``Template``
     592    instance is returned for subsequent requests to load the same template.
     593
     594    For example, to enable template caching with the ``filesystem`` and
     595    ``app_directories`` template loaders you might use the following settings::
     596
     597        from django.template.loaders import cached
     598        loader = cached.Loader((
     599            'django.template.loaders.filesystem.loader',
     600            'django.template.loaders.app_directories.loader',
     601        ))
     602        TEMPLATE_LOADERS = (loader,)
     603
     604    .. note::
     605        All of the built-in Django template tags are safe to use with the cached
     606        loader, but if you're using custom template tags that come from third
     607        party packages, or that you wrote yourself, you should ensure that the
     608        ``Node`` implementation for each tag is thread-safe. For more
     609        information, see
     610        :ref:`template tag thread safety considerations<template_tag_thread_safety>`.
     611
     612    This loader is disabled by default.
     613
    583614Django uses the template loaders in order according to the
    584615:setting:`TEMPLATE_LOADERS` setting. It uses each loader until a loader finds a
    585616match.
     
    642673and :setting:`TEMPLATE_DEBUG`. All available settings are described in the
    643674:ref:`settings documentation <ref-settings>`, and any setting starting with
    644675``TEMPLATE_`` is of obvious interest.
     676
     677Using an alternative template language
     678======================================
     679
     680.. versionadded 1.2
     681
     682The Django ``Template`` and ``Loader`` classes implement a simple API for
     683loading and rendering templates. By providing some simple wrapper classes that
     684implement this API we can use third party template systems like `Jinja2
     685<http://jinja.pocoo.org/2/>`_ or `Cheetah <http://www.cheetahtemplate.org/>`_. This
     686allows us to use third-party template libraries without giving up useful Django
     687features like the Django ``Context`` object and handy shortcuts like
     688``render_to_response()``.
     689
     690The core component of the Django templating system is the ``Template`` class.
     691This class has a very simple interface: it has a constructor that takes a single
     692positional argument specifying the template string, and a ``render()`` method
     693that takes a ``django.template.context.Context`` object and returns a string
     694containing the rendered response.
     695
     696Jinja2's ``Template`` object implements a similar interface, except that its
     697``render()`` method takes a dictionary rather than a ``Context`` object. We can
     698write a simple wrapper around ``jinja2.Template`` that implements the Django
     699``Template`` interface::
     700   
     701    import jinja2
     702    class Template(jinja2.Template):
     703        def render(self, context):
     704            # flatten the Django Context into a single dictionary.
     705            context_dict = {}
     706            for d in context.dicts:
     707                context_dict.update(d)
     708            return super(Template, self).render(context_dict)
     709
     710That's all that's required to make Jinja2 templates compatible with the Django
     711loading and rendering system!
     712
     713The next step is to write a ``Loader`` class that returns instances of our custom
     714template class instead of the default ``django.template.Template``. Custom ``Loader``
     715classes should inherit from ``django.template.loader.BaseLoader`` and override
     716the ``load_template_source()`` method, which takes a ``template_name`` argument,
     717loads the template from disk (or elsewhere), and returns a tuple:
     718``(template_string, template_origin)``.
     719
     720The ``load_template()`` method of the ``Loader`` class retrieves the template
     721string by calling ``load_template_source()``, instantiates a ``Template`` from
     722the template source, and returns a tuple: ``(template, template_origin)``. Since
     723this is the method that actually instantiates the ``Template``, we'll need to
     724override it to use our custom template class instead. We can inherit from the
     725builtin ``django.template.loaders.app_directories.Loader`` to take advantage of
     726the ``load_template_source()`` method implemented there::
     727   
     728    from django.template.loaders import app_directories
     729    class Loader(app_directories.Loader):
     730        is_usable = True
     731
     732        def load_template(self, template_name, template_dirs=None):
     733            source, origin = self.load_template_source(template_name, template_dirs)
     734            template = Template(source)
     735            return template, origin
     736    loader = Loader()
     737
     738Finally, we need to modify our project settings, telling Django to use our custom
     739Jinja2 loader. Now we can write all of our templates in Jinja2 while continuing to use
     740the rest of the Django templating system.
  • docs/ref/contrib/sitemaps.txt

     
    3636    1. Add ``'django.contrib.sitemaps'`` to your :setting:`INSTALLED_APPS`
    3737       setting.
    3838       
    39     2. Make sure ``'django.template.loaders.app_directories.load_template_source'``
     39    2. Make sure ``'django.template.loaders.app_directories.loader'``
    4040       is in your :setting:`TEMPLATE_LOADERS` setting. It's in there by default,
    4141       so you'll only need to change this if you've changed that setting.
    4242
     
    4545
    4646(Note: The sitemap application doesn't install any database tables. The only
    4747reason it needs to go into :setting:`INSTALLED_APPS` is so that the
    48 :func:`~django.template.loaders.app_directories.load_template_source` template
     48:func:`~django.template.loaders.app_directories.loader` template
    4949loader can find the default templates.)
    5050
    5151Initialization
  • docs/ref/settings.txt

     
    11011101
    11021102Default::
    11031103
    1104      ('django.template.loaders.filesystem.load_template_source',
    1105       'django.template.loaders.app_directories.load_template_source')
     1104     ('django.template.loaders.filesystem.loader',
     1105      'django.template.loaders.app_directories.loader')
    11061106
    11071107A tuple of callables (as strings) that know how to import templates from
    11081108various sources. See :ref:`ref-templates-api`.
Back to Top