Ticket #6262: t6262-rc1.diff

File t6262-rc1.diff, 53.2 KB (added by russellm, 5 years ago)

RC1 of cached templates patch

  • AUTHORS

    diff -r e0fb67da6521 AUTHORS
    a b  
    285285    Martin Mahner <http://www.mahner.org/>
    286286    Matt McClanahan <http://mmcc.cx/>
    287287    Frantisek Malina <vizualbod@vizualbod.com>
     288    Mike Malone <mjmalone@gmail.com>
    288289    Martin Maney <http://www.chipy.org/Martin_Maney>
    289290    masonsimon+django@gmail.com
    290291    Manuzhai
  • django/conf/global_settings.py

    diff -r e0fb67da6521 django/conf/global_settings.py
    a b  
    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/conf/project_template/settings.py

    diff -r e0fb67da6521 django/conf/project_template/settings.py
    a b  
    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/template/__init__.py

    diff -r e0fb67da6521 django/template/__init__.py
    a b  
    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/context.py

    diff -r e0fb67da6521 django/template/context.py
    a b  
    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
    2926
    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/defaulttags.py

    diff -r e0fb67da6521 django/template/defaulttags.py
    a b  
    5757
    5858class CycleNode(Node):
    5959    def __init__(self, cyclevars, variable_name=None):
    60         self.cycle_iter = itertools_cycle(cyclevars)
     60        self.cyclevars = cyclevars
    6161        self.variable_name = variable_name
    6262
    6363    def render(self, context):
    64         value = self.cycle_iter.next().resolve(context)
     64        if self not in context.render_context:
     65            context.render_context[self] = itertools_cycle(self.cyclevars)
     66        cycle_iter = context.render_context[self]
     67        value = cycle_iter.next().resolve(context)
    6568        if self.variable_name:
    6669            context[self.variable_name] = value
    6770        return value
  • django/template/loader.py

    diff -r e0fb67da6521 django/template/loader.py
    a b  
    2727
    2828template_source_loaders = None
    2929
     30class BaseLoader(object):
     31    is_usable = False
     32
     33    def __init__(self, *args, **kwargs):
     34        pass
     35
     36    def __call__(self, template_name, template_dirs=None):
     37        return self.load_template(template_name, template_dirs)
     38
     39    def load_template(self, template_name, template_dirs=None):
     40        source, origin = self.load_template_source(template_name, template_dirs)
     41        template = get_template_from_string(source, name=template_name)
     42        return template, origin
     43
     44    def load_template_source(self, template_name, template_dirs=None):
     45        """
     46        Returns a tuple containing the source and origin for the given template
     47        name.
     48
     49        """
     50        raise NotImplementedError
     51
     52    def reset(self):
     53        """
     54        Resets any state maintained by the loader instance (e.g., cached
     55        templates or cached loader modules).
     56
     57        """
     58        pass
     59
    3060class LoaderOrigin(Origin):
    3161    def __init__(self, display_name, loader, name, dirs):
    3262        super(LoaderOrigin, self).__init__(display_name)
     
    4171    else:
    4272        return None
    4373
    44 def find_template_source(name, dirs=None):
     74def find_template_loader(loader):
     75    if hasattr(loader, '__iter__'):
     76        loader, args = loader[0], loader[1:]
     77    else:
     78        args = []
     79    if isinstance(loader, basestring):
     80        module, attr = loader.rsplit('.', 1)
     81        try:
     82            mod = import_module(module)
     83        except ImportError:
     84            raise ImproperlyConfigured('Error importing template source loader %s: "%s"' % (loader, e))
     85        try:
     86            TemplateLoader = getattr(mod, attr)
     87        except AttributeError, e:
     88            raise ImproperlyConfigured('Error importing template source loader %s: "%s"' % (loader, e))
     89
     90        if hasattr(TemplateLoader, 'load_template_source'):
     91            func = TemplateLoader(*args)
     92        else:
     93            # Try loading module the old way - string is full path to callable
     94            if args:
     95                raise ImproperlyConfigured("Error importing template source loader %s - can't pass arguments to function-based loader." % loader)
     96            func = TemplateLoader
     97
     98        if not func.is_usable:
     99            import warnings
     100            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)
     101            return None
     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):
  • django/template/loader_tags.py

    diff -r e0fb67da6521 django/template/loader_tags.py
    a b  
    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
     82        self.blocks = dict([(n.name, n) for n in nodelist.get_nodes_by_type(BlockNode)])
    4683
    4784    def __repr__(self):
    4885        if self.parent_name_expr:
     
    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]
     111
     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)
    98129
    99130class ConstantIncludeNode(Node):
    100131    def __init__(self, template_path):
  • django/template/loaders/app_directories.py

    diff -r e0fb67da6521 django/template/loaders/app_directories.py
    a b  
    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
     33
     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()
    4765
    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
  • new file django/template/loaders/cached.py

    diff -r e0fb67da6521 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/loaders/eggs.py

    diff -r e0fb67da6521 django/template/loaders/eggs.py
    a b  
    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/filesystem.py

    diff -r e0fb67da6521 django/template/loaders/filesystem.py
    a b  
    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
     12
     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()
    2852
    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/test/utils.py

    diff -r e0fb67da6521 django/test/utils.py
    a b  
    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/views/debug.py

    diff -r e0fb67da6521 django/views/debug.py
    a b  
    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'):
  • docs/howto/custom-template-tags.txt

    diff -r e0fb67da6521 docs/howto/custom-template-tags.txt
    a b  
    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/internals/deprecation.txt

    diff -r e0fb67da6521 docs/internals/deprecation.txt
    a b  
    3636          manager in the ``User`` model (``user.message_set``), and the
    3737          associated methods (``user.message_set.create()`` and
    3838          ``user.get_and_delete_messages()``), which have
    39           been deprecated since the 1.2 release, will be removed.  The 
    40           :ref:`messages framework <ref-contrib-messages>` should be used 
     39          been deprecated since the 1.2 release, will be removed.  The
     40          :ref:`messages framework <ref-contrib-messages>` should be used
    4141          instead.
    4242
    4343        * Authentication backends need to support the ``obj`` parameter for
    4444          permission checking. The ``supports_object_permissions`` variable
    4545          is not checked any longer and can be removed.
    4646
     47        * The ability to specify a callable template loader rather than a
     48          ``Loader`` class will be removed, as will the ``load_template_source``
     49          functions that are included with the built in template loaders for
     50          backwards compatibility. These have been deprecated since the 1.2
     51          release.
     52
    4753    * 2.0
    4854        * ``django.views.defaults.shortcut()``. This function has been moved
    4955          to ``django.contrib.contenttypes.views.shortcut()`` as part of the
  • docs/ref/contrib/sitemaps.txt

    diff -r e0fb67da6521 docs/ref/contrib/sitemaps.txt
    a b  
    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

    diff -r e0fb67da6521 docs/ref/settings.txt
    a b  
    11501150
    11511151Default::
    11521152
    1153      ('django.template.loaders.filesystem.load_template_source',
    1154       'django.template.loaders.app_directories.load_template_source')
     1153     ('django.template.loaders.filesystem.Loader',
     1154      'django.template.loaders.app_directories.Loader')
    11551155
    1156 A tuple of callables (as strings) that know how to import templates from
    1157 various sources. See :ref:`ref-templates-api`.
     1156A tuple of template loader classes, specified as strings. Each ``Loader`` class
     1157knows how to import templates from a particular sources. Optionally, a tuple can be
     1158used instead of a string. The first item in the tuple should be the ``Loader``'s
     1159module, subsequent items are passed to the ``Loader`` during initialization. See
     1160:ref:`ref-template-api`.
    11581161
    11591162.. setting:: TEMPLATE_STRING_IF_INVALID
    11601163
  • docs/ref/templates/api.txt

    diff -r e0fb67da6521 docs/ref/templates/api.txt
    a b  
    322322   cannot be turned off by the :setting:`TEMPLATE_CONTEXT_PROCESSORS` setting.
    323323
    324324.. versionadded:: 1.2
    325    The ``'messages'`` context processor was added.  For more information, see 
     325   The ``'messages'`` context processor was added.  For more information, see
    326326   the :ref:`messages documentation <ref-contrib-messages>`.
    327327
    328328Each processor is applied in order. That means, if one processor adds a
     
    379379
    380380.. versionchanged:: 1.2
    381381   Prior to version 1.2, the ``messages`` variable was a lazy accessor for
    382    ``user.get_and_delete_messages()``. It has been changed to include any 
     382   ``user.get_and_delete_messages()``. It has been changed to include any
    383383   messages added via the :ref:`messages framework <ref-contrib-messages`.
    384384
    385385django.core.context_processors.debug
     
    448448   context processor.  For backwards compatibility the ``'auth'`` context
    449449   processor will continue to supply the ``messages`` variable until Django
    450450   1.4.  If you use the ``messages`` variable, your project will work with
    451    either (or both) context processors, but it is recommended to add 
     451   either (or both) context processors, but it is recommended to add
    452452   ``django.contrib.messages.context_processors.messages`` so your project
    453453   will be prepared for the future upgrade.
    454454
     
    571571should be a tuple of strings, where each string represents a template loader.
    572572Here are the template loaders that come with Django:
    573573
    574 ``django.template.loaders.filesystem.load_template_source``
     574``django.template.loaders.filesystem.Loader``
    575575    Loads templates from the filesystem, according to :setting:`TEMPLATE_DIRS`.
    576576    This loader is enabled by default.
    577577
    578 ``django.template.loaders.app_directories.load_template_source``
     578``django.template.loaders.app_directories.Loader``
    579579    Loads templates from Django apps on the filesystem. For each app in
    580580    :setting:`INSTALLED_APPS`, the loader looks for a ``templates``
    581581    subdirectory. If the directory exists, Django looks for templates in there.
     
    599599
    600600    This loader is enabled by default.
    601601
    602 ``django.template.loaders.eggs.load_template_source``
     602``django.template.loaders.eggs.Loader``
    603603    Just like ``app_directories`` above, but it loads templates from Python
    604604    eggs rather than from the filesystem.
    605605
    606606    This loader is disabled by default.
    607607
     608``django.template.loaders.cached.Loader``
     609    By default, the templating system will read and compile your templates every
     610    time they need to be rendered. While the Django templating system is quite
     611    fast, the overhead from reading and compiling templates can add up.
     612
     613    The cached template loader is a class-based loader that you configure with
     614    a list of other loaders that it should wrap. The wrapped loaders are used to
     615    locate unknown templates when they are first encountered. The cached loader
     616    then stores the compiled ``Template`` in memory. The cached ``Template``
     617    instance is returned for subsequent requests to load the same template.
     618
     619    For example, to enable template caching with the ``filesystem`` and
     620    ``app_directories`` template loaders you might use the following settings::
     621
     622        TEMPLATE_LOADERS = (
     623            ('django.template.loaders.cached.Loader', (
     624                'django.template.loaders.filesystem.Loader',
     625                'django.template.loaders.app_directories.Loader',
     626            )),
     627        )
     628
     629    .. note::
     630        All of the built-in Django template tags are safe to use with the cached
     631        loader, but if you're using custom template tags that come from third
     632        party packages, or that you wrote yourself, you should ensure that the
     633        ``Node`` implementation for each tag is thread-safe. For more
     634        information, see
     635        :ref:`template tag thread safety considerations<template_tag_thread_safety>`.
     636
     637    This loader is disabled by default.
     638
    608639Django uses the template loaders in order according to the
    609640:setting:`TEMPLATE_LOADERS` setting. It uses each loader until a loader finds a
    610641match.
     
    667698and :setting:`TEMPLATE_DEBUG`. All available settings are described in the
    668699:ref:`settings documentation <ref-settings>`, and any setting starting with
    669700``TEMPLATE_`` is of obvious interest.
     701
     702Using an alternative template language
     703======================================
     704
     705.. versionadded 1.2
     706
     707The Django ``Template`` and ``Loader`` classes implement a simple API for
     708loading and rendering templates. By providing some simple wrapper classes that
     709implement this API we can use third party template systems like `Jinja2
     710<http://jinja.pocoo.org/2/>`_ or `Cheetah <http://www.cheetahtemplate.org/>`_. This
     711allows us to use third-party template libraries without giving up useful Django
     712features like the Django ``Context`` object and handy shortcuts like
     713``render_to_response()``.
     714
     715The core component of the Django templating system is the ``Template`` class.
     716This class has a very simple interface: it has a constructor that takes a single
     717positional argument specifying the template string, and a ``render()`` method
     718that takes a ``django.template.context.Context`` object and returns a string
     719containing the rendered response.
     720
     721Suppose we're using a template language that defines a ``Template`` object with
     722a ``render()`` method that takes a dictionary rather than a ``Context`` object.
     723We can write a simple wrapper that implements the Django ``Template`` interface.
     724
     725    import some_template_language
     726    class Template(some_template_language.Template):
     727        def render(self, context):
     728            # flatten the Django Context into a single dictionary.
     729            context_dict = {}
     730            for d in context.dicts:
     731                context_dict.update(d)
     732            return super(Template, self).render(context_dict)
     733
     734That's all that's required to make our fictional ``Template`` class compatible
     735with teh Django loading and rendering system!
     736
     737The next step is to write a ``Loader`` class that returns instances of our custom
     738template class instead of the default ``django.template.Template``. Custom ``Loader``
     739classes should inherit from ``django.template.loader.BaseLoader`` and override
     740the ``load_template_source()`` method, which takes a ``template_name`` argument,
     741loads the template from disk (or elsewhere), and returns a tuple:
     742``(template_string, template_origin)``.
     743
     744The ``load_template()`` method of the ``Loader`` class retrieves the template
     745string by calling ``load_template_source()``, instantiates a ``Template`` from
     746the template source, and returns a tuple: ``(template, template_origin)``. Since
     747this is the method that actually instantiates the ``Template``, we'll need to
     748override it to use our custom template class instead. We can inherit from the
     749builtin ``django.template.loaders.app_directories.Loader`` to take advantage of
     750the ``load_template_source()`` method implemented there::
     751
     752    from django.template.loaders import app_directories
     753    class Loader(app_directories.Loader):
     754        is_usable = True
     755
     756        def load_template(self, template_name, template_dirs=None):
     757            source, origin = self.load_template_source(template_name, template_dirs)
     758            template = Template(source)
     759            return template, origin
     760
     761Finally, we need to modify our project settings, telling Django to use our custom
     762loader. Now we can write all of our templates in our alternative template
     763language while continuing to use the rest of the Django templating system.
  • tests/regressiontests/templates/context.py

    diff -r e0fb67da6521 tests/regressiontests/templates/context.py
    a b  
    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
  • tests/regressiontests/templates/tests.py

    diff -r e0fb67da6521 tests/regressiontests/templates/tests.py
    a b  
    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
     
    101101
    102102class Templates(unittest.TestCase):
    103103    def test_loaders_security(self):
     104        ad_loader = app_directories.Loader()
     105        fs_loader = filesystem.Loader()
    104106        def test_template_sources(path, template_dirs, expected_sources):
    105107            if isinstance(expected_sources, list):
    106108                # Fix expected sources so they are normcased and abspathed
    107109                expected_sources = [os.path.normcase(os.path.abspath(s)) for s in expected_sources]
    108110            # Test the two loaders (app_directores and filesystem).
    109             func1 = lambda p, t: list(app_directories.get_template_sources(p, t))
    110             func2 = lambda p, t: list(filesystem.get_template_sources(p, t))
     111            func1 = lambda p, t: list(ad_loader.get_template_sources(p, t))
     112            func2 = lambda p, t: list(fs_loader.get_template_sources(p, t))
    111113            for func in (func1, func2):
    112114                if isinstance(expected_sources, list):
    113115                    self.assertEqual(func(path, template_dirs), expected_sources)
     
    198200            except KeyError:
    199201                raise template.TemplateDoesNotExist, template_name
    200202
     203        cache_loader = cached.Loader(('test_template_loader',))
     204        cache_loader._cached_loaders = (test_template_loader,)
     205
    201206        old_template_loaders = loader.template_source_loaders
    202         loader.template_source_loaders = [test_template_loader]
     207        loader.template_source_loaders = [cache_loader]
    203208
    204209        failures = []
    205210        tests = template_tests.items()
     
    232237            for invalid_str, result in [('', normal_string_result),
    233238                                        (expected_invalid_str, invalid_string_result)]:
    234239                settings.TEMPLATE_STRING_IF_INVALID = invalid_str
    235                 try:
    236                     test_template = loader.get_template(name)
    237                     output = self.render(test_template, vals)
    238                 except ContextStackException:
    239                     failures.append("Template test (TEMPLATE_STRING_IF_INVALID='%s'): %s -- FAILED. Context stack was left imbalanced" % (invalid_str, name))
    240                     continue
    241                 except Exception:
    242                     exc_type, exc_value, exc_tb = sys.exc_info()
    243                     if exc_type != result:
    244                         tb = '\n'.join(traceback.format_exception(exc_type, exc_value, exc_tb))
    245                         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))
    246                     continue
    247                 if output != result:
    248                     failures.append("Template test (TEMPLATE_STRING_IF_INVALID='%s'): %s -- FAILED. Expected %r, got %r" % (invalid_str, name, result, output))
     240                for is_cached in (False, True):
     241                    try:
     242                        test_template = loader.get_template(name)
     243                        output = self.render(test_template, vals)
     244                    except ContextStackException:
     245                        failures.append("Template test (Cached='%s', TEMPLATE_STRING_IF_INVALID='%s'): %s -- FAILED. Context stack was left imbalanced" % (is_cached, invalid_str, name))
     246                        continue
     247                    except Exception:
     248                        exc_type, exc_value, exc_tb = sys.exc_info()
     249                        if exc_type != result:
     250                            tb = '\n'.join(traceback.format_exception(exc_type, exc_value, exc_tb))
     251                            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))
     252                        continue
     253                    if output != result:
     254                        failures.append("Template test (Cached='%s', TEMPLATE_STRING_IF_INVALID='%s'): %s -- FAILED. Expected %r, got %r" % (is_cached, invalid_str, name, result, output))
     255                cache_loader.reset()
    249256
    250257            if 'LANGUAGE_CODE' in vals[1]:
    251258                deactivate()
  • tests/regressiontests/test_client_regress/models.py

    diff -r e0fb67da6521 tests/regressiontests/test_client_regress/models.py
    a b  
    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
Back to Top