diff -r b758351d191f -r cc9b7e14412b web/lib/django/template/__init__.py --- 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'' """ +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="<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')