web/lib/django/template/__init__.py
changeset 29 cc9b7e14412b
parent 0 0d40e90630ef
--- a/web/lib/django/template/__init__.py	Wed May 19 17:43:59 2010 +0200
+++ b/web/lib/django/template/__init__.py	Tue May 25 02:43:45 2010 +0200
@@ -48,6 +48,7 @@
 >>> t.render(c)
 u'<html></html>'
 """
+import imp
 import re
 from inspect import getargspec
 
@@ -56,11 +57,13 @@
 from django.utils.importlib import import_module
 from django.utils.itercompat import is_iterable
 from django.utils.functional import curry, Promise
-from django.utils.text import smart_split, unescape_string_literal
+from django.utils.text import smart_split, unescape_string_literal, get_text_list
 from django.utils.encoding import smart_unicode, force_unicode, smart_str
 from django.utils.translation import ugettext as _
 from django.utils.safestring import SafeData, EscapeData, mark_safe, mark_for_escaping
+from django.utils.formats import localize
 from django.utils.html import escape
+from django.utils.module_loading import module_has_submodule
 
 __all__ = ('Template', 'Context', 'RequestContext', 'compile_string')
 
@@ -86,7 +89,7 @@
 
 # what to report as the origin for templates that come from non-loader sources
 # (e.g. strings)
-UNKNOWN_SOURCE="&lt;unknown source&gt;"
+UNKNOWN_SOURCE = '<unknown source>'
 
 # match a variable or block tag and capture the entire tag, including start/end delimiters
 tag_re = re.compile('(%s.*?%s|%s.*?%s|%s.*?%s)' % (re.escape(BLOCK_TAG_START), re.escape(BLOCK_TAG_END),
@@ -103,20 +106,7 @@
 invalid_var_format_string = None
 
 class TemplateSyntaxError(Exception):
-    def __str__(self):
-        try:
-            import cStringIO as StringIO
-        except ImportError:
-            import StringIO
-        output = StringIO.StringIO()
-        output.write(Exception.__str__(self))
-        # Check if we wrapped an exception and print that too.
-        if hasattr(self, 'exc_info'):
-            import traceback
-            output.write('\n\nOriginal ')
-            e = self.exc_info
-            traceback.print_exception(e[0], e[1], e[2], 500, output)
-        return output.getvalue()
+    pass
 
 class TemplateDoesNotExist(Exception):
     pass
@@ -173,9 +163,16 @@
             for subnode in node:
                 yield subnode
 
+    def _render(self, context):
+        return self.nodelist.render(context)
+
     def render(self, context):
         "Display stage -- can be called many times"
-        return self.nodelist.render(context)
+        context.render_context.push()
+        try:
+            return self._render(context)
+        finally:
+            context.render_context.pop()
 
 def compile_string(template_string, origin):
     "Compiles template_string into NodeList ready for rendering"
@@ -280,7 +277,7 @@
                 try:
                     compile_func = self.tags[command]
                 except KeyError:
-                    self.invalid_block_tag(token, command)
+                    self.invalid_block_tag(token, command, parse_until)
                 try:
                     compiled_result = compile_func(self, token)
                 except TemplateSyntaxError, e:
@@ -331,7 +328,9 @@
     def empty_block_tag(self, token):
         raise self.error(token, "Empty block tag")
 
-    def invalid_block_tag(self, token, command):
+    def invalid_block_tag(self, token, command, parse_until=None):
+        if parse_until:
+            raise self.error(token, "Invalid block tag: '%s', expected %s" % (command, get_text_list(["'%s'" % p for p in parse_until])))
         raise self.error(token, "Invalid block tag: '%s'" % command)
 
     def unclosed_block_tag(self, parse_until):
@@ -411,6 +410,20 @@
         "A microparser that parses for a value: some string constant or variable name."
         subject = self.subject
         i = self.pointer
+
+        def next_space_index(subject, i):
+            "Increment pointer until a real space (i.e. a space not within quotes) is encountered"
+            while i < len(subject) and subject[i] not in (' ', '\t'):
+                if subject[i] in ('"', "'"):
+                    c = subject[i]
+                    i += 1
+                    while i < len(subject) and subject[i] != c:
+                        i += 1
+                    if i >= len(subject):
+                        raise TemplateSyntaxError("Searching for value. Unexpected end of string in column %d: %s" % (i, subject))
+                i += 1
+            return i
+
         if i >= len(subject):
             raise TemplateSyntaxError("Searching for value. Expected another value but found end of string: %s" % subject)
         if subject[i] in ('"', "'"):
@@ -421,6 +434,10 @@
             if i >= len(subject):
                 raise TemplateSyntaxError("Searching for value. Unexpected end of string in column %d: %s" % (i, subject))
             i += 1
+
+            # Continue parsing until next "real" space, so that filters are also included
+            i = next_space_index(subject, i)
+
             res = subject[p:i]
             while i < len(subject) and subject[i] in (' ', '\t'):
                 i += 1
@@ -429,15 +446,7 @@
             return res
         else:
             p = i
-            while i < len(subject) and subject[i] not in (' ', '\t'):
-                if subject[i] in ('"', "'"):
-                    c = subject[i]
-                    i += 1
-                    while i < len(subject) and subject[i] != c:
-                        i += 1
-                    if i >= len(subject):
-                        raise TemplateSyntaxError("Searching for value. Unexpected end of string in column %d: %s" % (i, subject))
-                i += 1
+            i = next_space_index(subject, i)
             s = subject[p:i]
             while i < len(subject) and subject[i] in (' ', '\t'):
                 i += 1
@@ -518,8 +527,6 @@
                         var_obj = None
                 elif var is None:
                     raise TemplateSyntaxError("Could not find variable at start of %s." % token)
-                elif var.find(VARIABLE_ATTRIBUTE_SEPARATOR + '_') > -1 or var[0] == '_':
-                    raise TemplateSyntaxError("Variables and attributes may not begin with underscores: '%s'" % var)
                 else:
                     var_obj = Variable(var)
             else:
@@ -531,8 +538,8 @@
                 elif var_arg:
                     args.append((True, Variable(var_arg)))
                 filter_func = parser.find_filter(filter_name)
-                self.args_check(filter_name,filter_func, args)
-                filters.append( (filter_func,args))
+                self.args_check(filter_name, filter_func, args)
+                filters.append((filter_func, args))
             upto = match.end()
         if upto != len(token):
             raise TemplateSyntaxError("Could not parse the remainder: '%s' from '%s'" % (token[upto:], token))
@@ -678,6 +685,8 @@
             except ValueError:
                 # Otherwise we'll set self.lookups so that resolve() knows we're
                 # dealing with a bonafide variable
+                if var.find(VARIABLE_ATTRIBUTE_SEPARATOR + '_') > -1 or var[0] == '_':
+                    raise TemplateSyntaxError("Variables and attributes may not begin with underscores: '%s'" % var)
                 self.lookups = tuple(var.split(VARIABLE_ATTRIBUTE_SEPARATOR))
 
     def resolve(self, context):
@@ -743,6 +752,11 @@
                         current = settings.TEMPLATE_STRING_IF_INVALID
                     else:
                         raise
+            except Exception, e:
+                if getattr(e, 'silent_variable_failure', False):
+                    current = settings.TEMPLATE_STRING_IF_INVALID
+                else:
+                    raise
 
         return current
 
@@ -750,6 +764,7 @@
     # Set this to True for nodes that must be first in the template (although
     # they can be preceded by text nodes.
     must_be_first = False
+    child_nodelists = ('nodelist',)
 
     def render(self, context):
         "Return the node rendered as a string"
@@ -763,8 +778,10 @@
         nodes = []
         if isinstance(self, nodetype):
             nodes.append(self)
-        if hasattr(self, 'nodelist'):
-            nodes.extend(self.nodelist.get_nodes_by_type(nodetype))
+        for attr in self.child_nodelists:
+            nodelist = getattr(self, attr, None)
+            if nodelist:
+                nodes.extend(nodelist.get_nodes_by_type(nodetype))
         return nodes
 
 class NodeList(list):
@@ -801,13 +818,14 @@
 
     def render(self, context):
         return self.s
-    
+
 def _render_value_in_context(value, context):
     """
     Converts any value to a string to become part of a rendered template. This
     means escaping, if required, and conversion to a unicode object. If value
     is a string, it is expected to have already been translated.
     """
+    value = localize(value)
     value = force_unicode(value)
     if (context.autoescape and not isinstance(value, SafeData)) or isinstance(value, EscapeData):
         return escape(value)
@@ -942,8 +960,14 @@
                         else:
                             t = get_template(file_name)
                         self.nodelist = t.nodelist
-                    return self.nodelist.render(context_class(dict,
-                            autoescape=context.autoescape))
+                    new_context = context_class(dict, autoescape=context.autoescape)
+                    # Copy across the CSRF token, if present, because inclusion
+                    # tags are often used for forms, and we need instructions
+                    # for using CSRF protection to be as simple as possible.
+                    csrf_token = context.get('csrf_token', None)
+                    if csrf_token is not None:
+                        new_context['csrf_token'] = csrf_token
+                    return self.nodelist.render(new_context)
 
             compile_func = curry(generic_tag_compiler, params, defaults, getattr(func, "_decorated_function", func).__name__, InclusionNode)
             compile_func.__doc__ = func.__doc__
@@ -951,22 +975,78 @@
             return func
         return dec
 
-def get_library(module_name):
-    lib = libraries.get(module_name, None)
+def import_library(taglib_module):
+    """Load a template tag library module.
+
+    Verifies that the library contains a 'register' attribute, and
+    returns that attribute as the representation of the library
+    """
+    app_path, taglib = taglib_module.rsplit('.',1)
+    app_module = import_module(app_path)
+    try:
+        mod = import_module(taglib_module)
+    except ImportError, e:
+        # If the ImportError is because the taglib submodule does not exist, that's not
+        # an error that should be raised. If the submodule exists and raised an ImportError
+        # on the attempt to load it, that we want to raise.
+        if not module_has_submodule(app_module, taglib):
+            return None
+        else:
+            raise InvalidTemplateLibrary("ImportError raised loading %s: %s" % (taglib_module, e))
+    try:
+        return mod.register
+    except AttributeError:
+        raise InvalidTemplateLibrary("Template library %s does not have a variable named 'register'" % taglib_module)
+
+templatetags_modules = []
+
+def get_templatetags_modules():
+    """Return the list of all available template tag modules.
+
+    Caches the result for faster access.
+    """
+    global templatetags_modules
+    if not templatetags_modules:
+        _templatetags_modules = []
+        # Populate list once per thread.
+        for app_module in ['django'] + list(settings.INSTALLED_APPS):
+            try:
+                templatetag_module = '%s.templatetags' % app_module
+                import_module(templatetag_module)
+                _templatetags_modules.append(templatetag_module)
+            except ImportError:
+                continue
+        templatetags_modules = _templatetags_modules
+    return templatetags_modules
+
+def get_library(library_name):
+    """
+    Load the template library module with the given name.
+
+    If library is not already loaded loop over all templatetags modules to locate it.
+
+    {% load somelib %} and {% load someotherlib %} loops twice.
+
+    Subsequent loads eg. {% load somelib %} in the same process will grab the cached
+    module from libraries.
+    """
+    lib = libraries.get(library_name, None)
     if not lib:
-        try:
-            mod = import_module(module_name)
-        except ImportError, e:
-            raise InvalidTemplateLibrary("Could not load template library from %s, %s" % (module_name, e))
-        try:
-            lib = mod.register
-            libraries[module_name] = lib
-        except AttributeError:
-            raise InvalidTemplateLibrary("Template library %s does not have a variable named 'register'" % module_name)
+        templatetags_modules = get_templatetags_modules()
+        tried_modules = []
+        for module in templatetags_modules:
+            taglib_module = '%s.%s' % (module, library_name)
+            tried_modules.append(taglib_module)
+            lib = import_library(taglib_module)
+            if lib:
+                libraries[library_name] = lib
+                break
+        if not lib:
+            raise InvalidTemplateLibrary("Template library %s not found, tried %s" % (library_name, ','.join(tried_modules)))
     return lib
 
-def add_to_builtins(module_name):
-    builtins.append(get_library(module_name))
+def add_to_builtins(module):
+    builtins.append(import_library(module))
 
 add_to_builtins('django.template.defaulttags')
 add_to_builtins('django.template.defaultfilters')