Ticket #6262: cache_templates.7.diff

File cache_templates.7.diff, 51.9 KB (added by mmalone, 6 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',
     56    'django.template.loaders.app_directories',
     57#     'django.template.loaders.eggs',
    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',
     162    'django.template.loaders.app_directories',
     163#     'django.template.loaders.eggs',
    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
     64_loader = 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    import warnings
     69    warnings.warn(
     70        "'django.template.loaders.app_directories.load_template_source' is deprecated; use 'django.template.loaders.app_directories' instead.",
     71        PendingDeprecationWarning
     72    )
     73    return _loader.load_template_source(template_name, template_dirs)
    5574load_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
     51_loader = 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    import warnings
     56    warnings.warn(
     57        "'django.template.loaders.filesystem.load_template_source' is deprecated; use 'django.template.loaders.filesystem' instead.",
     58        PendingDeprecationWarning
     59    )
     60    return _loader.load_template_source(template_name, template_dirs)
    4161load_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
     30_loader = 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    import warnings
     34    warnings.warn(
     35        "'django.template.loaders.eggs.load_template_source' is deprecated; use 'django.template.loaders.eggs' instead.",
     36        PendingDeprecationWarning
     37    )
     38    return _loader.load_template_source(template_name, template_dirs)
    2539load_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):
    64         for d in self.dicts:
     62        for d in reversed(self.dicts):
    6563            if key in d:
    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 rendered, 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 hasattr(loader, '__iter__'):
     73        loader, args = loader[0], loader[1:]
     74    else:
     75        args = []
     76    if isinstance(loader, basestring):
     77        try:
     78            mod = import_module(loader)
     79        except ImportError:
     80            # Try loading module the old way - string is full path to callable
     81            if args:
     82                raise ImproperlyConfigured("Error importing template source loader $s - can't pass arguments to function-based loader.")
     83            i = loader.rfind('.')
     84            module, attr = loader[:i], loader[i+1:]
     85            try:
     86                mod = import_module(module)
     87            except ImportError, e:
     88                raise ImproperlyConfigured('Error importing template source loader %s: "%s"' % (loader, e))
     89            try:
     90                func = getattr(mod, attr)
     91            except AttributeError, e:
     92                raise ImproperlyConfigured('Error importing template source loader %s: "%s"' % (loader, e))
     93        else:
     94            try:
     95                Loader = getattr(mod, 'Loader')
     96            except AttributeError, e:
     97                raise ImproperlyConfigured('Error importing template source loader %s: "%s"' % (loader, e))
     98            func = Loader(*args)
     99        if not func.is_usable:
     100            import warnings
     101            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)
     102        else:
     103            return func
     104    else:
     105        raise ImproperlyConfigured('Loader does not define a "load_template" callable template source loader')
     106
     107def find_template(name, dirs=None):
    45108    # Calculate template_source_loaders the first time the function is executed
    46109    # because putting this logic in the module-level namespace may cause
    47110    # circular import errors. See Django ticket #1292.
    48111    global template_source_loaders
    49112    if template_source_loaders is None:
    50113        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)
     114        for loader_name in settings.TEMPLATE_LOADERS:
     115            loader = find_template_loader(loader_name)
     116            if loader is not None:
     117                loaders.append(loader)
    67118        template_source_loaders = tuple(loaders)
    68119    for loader in template_source_loaders:
    69120        try:
     
    73124            pass
    74125    raise TemplateDoesNotExist, name
    75126
     127def find_template_source(name, dirs=None):
     128    # For backward compatibility
     129    import warnings
     130    warnings.warn(
     131        "`django.template.loaders.find_template_source` is deprecated; use `django.template.loaders.find_template` instead.",
     132        PendingDeprecationWarning
     133    )
     134    template, origin = find_template(name, dirs)
     135    if hasattr(template, 'render'):
     136        raise Exception("Found a compiled template that is incompatible with the deprecated `django.template.loaders.find_template_source` function.")
     137    return template, origin
     138
    76139def get_template(template_name):
    77140    """
    78141    Returns a compiled Template object for the given template name,
    79142    handling template inheritance recursively.
    80143    """
    81     source, origin = find_template_source(template_name)
    82     template = get_template_from_string(source, origin, template_name)
     144    template, origin = find_template(template_name)
     145    if not hasattr(template, 'render'):
     146        # template needs to be compiled
     147        template = get_template_from_string(template, origin, template_name)
    83148    return template
    84149
    85150def 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 import 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        if loader.template_source_loaders is not None:
     442            for template_loader in loader.template_source_loaders:
     443                if hasattr(template_loader, 'reset'):
     444                    template_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
     
    100100
    101101class Templates(unittest.TestCase):
    102102    def test_loaders_security(self):
     103        ad_loader = app_directories.Loader()
     104        fs_loader = filesystem.Loader()
    103105        def test_template_sources(path, template_dirs, expected_sources):
    104106            if isinstance(expected_sources, list):
    105107                # Fix expected sources so they are normcased and abspathed
    106108                expected_sources = [os.path.normcase(os.path.abspath(s)) for s in expected_sources]
    107109            # 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))
     110            func1 = lambda p, t: list(ad_loader.get_template_sources(p, t))
     111            func2 = lambda p, t: list(fs_loader.get_template_sources(p, t))
    110112            for func in (func1, func2):
    111113                if isinstance(expected_sources, list):
    112114                    self.assertEqual(func(path, template_dirs), expected_sources)
     
    197199            except KeyError:
    198200                raise template.TemplateDoesNotExist, template_name
    199201
     202        cache_loader = cached.Loader(('test_template_loader',))
     203        cache_loader._cached_loaders = (test_template_loader,)
     204
    200205        old_template_loaders = loader.template_source_loaders
    201         loader.template_source_loaders = [test_template_loader]
     206        loader.template_source_loaders = [cache_loader]
    202207
    203208        failures = []
    204209        tests = template_tests.items()
     
    231236            for invalid_str, result in [('', normal_string_result),
    232237                                        (expected_invalid_str, invalid_string_result)]:
    233238                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))
     239                for is_cached in (False, True):
     240                    try:
     241                        test_template = loader.get_template(name)
     242                        output = self.render(test_template, vals)
     243                    except ContextStackException:
     244                        failures.append("Template test (Cached='%s', TEMPLATE_STRING_IF_INVALID='%s'): %s -- FAILED. Context stack was left imbalanced" % (is_cached, invalid_str, name))
     245                        continue
     246                    except Exception:
     247                        exc_type, exc_value, exc_tb = sys.exc_info()
     248                        if exc_type != result:
     249                            tb = '\n'.join(traceback.format_exception(exc_type, exc_value, exc_tb))
     250                            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))
     251                        continue
     252                    if output != result:
     253                        failures.append("Template test (Cached='%s', TEMPLATE_STRING_IF_INVALID='%s'): %s -- FAILED. Expected %r, got %r" % (is_cached, invalid_str, name, result, output))
     254                cache_loader.reset()
    248255
    249256            if 'LANGUAGE_CODE' in vals[1]:
    250257                deactivate()
  • tests/regressiontests/templates/context.py

     
    1010>>> c['a'] = 2
    1111>>> c['a']
    12122
     13>>> c.get('a')
     142
    1315>>> c.pop()
    1416{'a': 2}
    1517>>> c['a']
    16181
     19>>> c.get('foo', 42)
     2042
    1721"""
    1822
  • 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/internals/deprecation.txt

     
    2828        * The many to many SQL generation functions on the database backends
    2929          will be removed.  These have been deprecated since the 1.2 release.
    3030
     31        * The ability to specify a callable loader rather than a module with a
     32          ``Loader`` class will be removed, as will the ``load_template_source``
     33          functions that are included with the built in template loaders for
     34          backwards compatibility. These have been deprecated since the 1.2
     35          release.
     36
    3137    * 2.0
    3238        * ``django.views.defaults.shortcut()``. This function has been moved
    3339          to ``django.contrib.contenttypes.views.shortcut()`` as part of the
  • 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 multiple ``CycleNodes`` 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``
    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``
    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``
    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``
     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 configure 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        TEMPLATE_LOADERS = (
     598            ('django.template.loaders.cached', (
     599                'django.template.loaders.filesystem',
     600                'django.template.loaders.app_directories',
     601            )),
     602        )
     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
     696Suppose we're using a template language that defines a ``Template`` object with
     697a ``render()`` method that takes a dictionary rather than a ``Context`` object.
     698We can write a simple wrapper that implements the Django ``Template`` interface.
     699   
     700    import some_template_language
     701    class Template(some_template_language.Template):
     702        def render(self, context):
     703            # flatten the Django Context into a single dictionary.
     704            context_dict = {}
     705            for d in context.dicts:
     706                context_dict.update(d)
     707            return super(Template, self).render(context_dict)
     708
     709That's all that's required to make our fictional ``Template`` class compatible
     710with teh Django loading and rendering system!
     711
     712The next step is to write a ``Loader`` class that returns instances of our custom
     713template class instead of the default ``django.template.Template``. Custom ``Loader``
     714classes should inherit from ``django.template.loader.BaseLoader`` and override
     715the ``load_template_source()`` method, which takes a ``template_name`` argument,
     716loads the template from disk (or elsewhere), and returns a tuple:
     717``(template_string, template_origin)``.
     718
     719The ``load_template()`` method of the ``Loader`` class retrieves the template
     720string by calling ``load_template_source()``, instantiates a ``Template`` from
     721the template source, and returns a tuple: ``(template, template_origin)``. Since
     722this is the method that actually instantiates the ``Template``, we'll need to
     723override it to use our custom template class instead. We can inherit from the
     724builtin ``django.template.loaders.app_directories.Loader`` to take advantage of
     725the ``load_template_source()`` method implemented there::
     726   
     727    from django.template.loaders import app_directories
     728    class Loader(app_directories.Loader):
     729        is_usable = True
     730
     731        def load_template(self, template_name, template_dirs=None):
     732            source, origin = self.load_template_source(template_name, template_dirs)
     733            template = Template(source)
     734            return template, origin
     735
     736Finally, we need to modify our project settings, telling Django to use our custom
     737loader. Now we can write all of our templates in our alternative template
     738language while continuing to use the 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'``
    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` 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',
     1105      'django.template.loaders.app_directories')
    11061106
    1107 A tuple of callables (as strings) that know how to import templates from
    1108 various sources. See :ref:`ref-templates-api`.
     1107A tuple of modules (as strings) that have a top-level ``Loader`` class that
     1108knows how to import templates from various sources. Optionally, a tuple can be
     1109used instead of a string. The first item in the tuple should be the ``Loader``'s
     1110module, subsequent items are passed to the ``Loader`` during initialization. See
     1111:ref:`ref-template-api`.
    11091112
    11101113.. setting:: TEMPLATE_STRING_IF_INVALID
    11111114
Back to Top