diff options
| author | Christopher Long <indirecthit@gmail.com> | 2007-06-17 22:18:54 +0000 |
|---|---|---|
| committer | Christopher Long <indirecthit@gmail.com> | 2007-06-17 22:18:54 +0000 |
| commit | ae22b6d403dcf25098c77f0dfcf59ae58b186461 (patch) | |
| tree | c37fc631e99a7e4d909d6b6d236f495003731ea7 /django/template/__init__.py | |
| parent | 0cf7bc439129c66df8d64601e885f83b256b4f25 (diff) | |
per-object-permissions: Merged to trunk [5486] NOTE: Not fully tested, will be working on this over the next few weeks.
git-svn-id: http://code.djangoproject.com/svn/django/branches/per-object-permissions@5488 bcc190cf-cafb-0310-a4f2-bffc1f526a37
Diffstat (limited to 'django/template/__init__.py')
| -rw-r--r-- | django/template/__init__.py | 189 |
1 files changed, 122 insertions, 67 deletions
diff --git a/django/template/__init__.py b/django/template/__init__.py index 7718801684..7495eea878 100644 --- a/django/template/__init__.py +++ b/django/template/__init__.py @@ -55,6 +55,7 @@ times with multiple contexts) '\n<html>\n\n</html>\n' """ import re +import types from inspect import getargspec from django.conf import settings from django.template.context import Context, RequestContext, ContextPopException @@ -91,12 +92,18 @@ UNKNOWN_SOURCE="<unknown source>" tag_re = re.compile('(%s.*?%s|%s.*?%s|%s.*?%s)' % (re.escape(BLOCK_TAG_START), re.escape(BLOCK_TAG_END), re.escape(VARIABLE_TAG_START), re.escape(VARIABLE_TAG_END), re.escape(COMMENT_TAG_START), re.escape(COMMENT_TAG_END))) +# matches if the string is valid number +number_re = re.compile(r'[-+]?(\d+|\d*\.\d+)$') # global dictionary of libraries that have been loaded using get_library libraries = {} # global list of libraries to load by default for a new parser builtins = [] +# True if TEMPLATE_STRING_IF_INVALID contains a format string (%s). None means +# uninitialised. +invalid_var_format_string = None + class TemplateSyntaxError(Exception): def __str__(self): try: @@ -117,7 +124,13 @@ class TemplateDoesNotExist(Exception): pass class VariableDoesNotExist(Exception): - pass + + def __init__(self, msg, params=()): + self.msg = msg + self.params = params + + def __str__(self): + return self.msg % self.params class InvalidTemplateLibrary(Exception): pass @@ -155,9 +168,12 @@ class Template(object): for subnode in node: yield subnode - def render(self, context): + def iter_render(self, context): "Display stage -- can be called many times" - return self.nodelist.render(context) + return self.nodelist.iter_render(context) + + def render(self, context): + return ''.join(self.iter_render(context)) def compile_string(template_string, origin): "Compiles template_string into NodeList ready for rendering" @@ -185,18 +201,27 @@ class Lexer(object): def tokenize(self): "Return a list of tokens from a given template_string" - # remove all empty strings, because the regex has a tendency to add them - bits = filter(None, tag_re.split(self.template_string)) - return map(self.create_token, bits) + in_tag = False + result = [] + for bit in tag_re.split(self.template_string): + if bit: + result.append(self.create_token(bit, in_tag)) + in_tag = not in_tag + return result - def create_token(self,token_string): - "Convert the given token string into a new Token object and return it" - if token_string.startswith(VARIABLE_TAG_START): - token = Token(TOKEN_VAR, token_string[len(VARIABLE_TAG_START):-len(VARIABLE_TAG_END)].strip()) - elif token_string.startswith(BLOCK_TAG_START): - token = Token(TOKEN_BLOCK, token_string[len(BLOCK_TAG_START):-len(BLOCK_TAG_END)].strip()) - elif token_string.startswith(COMMENT_TAG_START): - token = Token(TOKEN_COMMENT, '') + def create_token(self, token_string, in_tag): + """ + Convert the given token string into a new Token object and return it. + If in_tag is True, we are processing something that matched a tag, + otherwise it should be treated as a literal string. + """ + if in_tag: + if token_string.startswith(VARIABLE_TAG_START): + token = Token(TOKEN_VAR, token_string[len(VARIABLE_TAG_START):-len(VARIABLE_TAG_END)].strip()) + elif token_string.startswith(BLOCK_TAG_START): + token = Token(TOKEN_BLOCK, token_string[len(BLOCK_TAG_START):-len(BLOCK_TAG_END)].strip()) + elif token_string.startswith(COMMENT_TAG_START): + token = Token(TOKEN_COMMENT, '') else: token = Token(TOKEN_TEXT, token_string) return token @@ -207,22 +232,22 @@ class DebugLexer(Lexer): def tokenize(self): "Return a list of tokens from a given template_string" - token_tups, upto = [], 0 + result, upto = [], 0 for match in tag_re.finditer(self.template_string): start, end = match.span() if start > upto: - token_tups.append( (self.template_string[upto:start], (upto, start)) ) + result.append(self.create_token(self.template_string[upto:start], (upto, start), False)) upto = start - token_tups.append( (self.template_string[start:end], (start,end)) ) + result.append(self.create_token(self.template_string[start:end], (start, end), True)) upto = end last_bit = self.template_string[upto:] if last_bit: - token_tups.append( (last_bit, (upto, upto + len(last_bit))) ) - return [self.create_token(tok, (self.origin, loc)) for tok, loc in token_tups] + result.append(self.create_token(last_bit, (upto, upto + len(last_bit)), False)) + return result - def create_token(self, token_string, source): - token = super(DebugLexer, self).create_token(token_string) - token.source = source + def create_token(self, token_string, source, in_tag): + token = super(DebugLexer, self).create_token(token_string, in_tag) + token.source = self.origin, source return token class Parser(object): @@ -330,7 +355,7 @@ class Parser(object): return FilterExpression(token, self) def find_filter(self, filter_name): - if self.filters.has_key(filter_name): + if filter_name in self.filters: return self.filters[filter_name] else: raise TemplateSyntaxError, "Invalid filter: '%s'" % filter_name @@ -458,7 +483,7 @@ class TokenParser(object): 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" % subject + raise TemplateSyntaxError, "Searching for value. Unexpected end of string in column %d: %s" % (i, subject) i += 1 s = subject[p:i] while i < len(subject) and subject[i] in (' ', '\t'): @@ -467,9 +492,6 @@ class TokenParser(object): self.pointer = i return s - - - filter_raw_string = r""" ^%(i18n_open)s"(?P<i18n_constant>%(str)s)"%(i18n_close)s| ^"(?P<constant>%(str)s)"| @@ -547,7 +569,7 @@ class FilterExpression(object): filters.append( (filter_func,args)) upto = match.end() if upto != len(token): - raise TemplateSyntaxError, "Could not parse the remainder: %s" % token[upto:] + raise TemplateSyntaxError, "Could not parse the remainder: '%s' from '%s'" % (token[upto:], token) self.var, self.filters = var, filters def resolve(self, context, ignore_failures=False): @@ -558,6 +580,11 @@ class FilterExpression(object): obj = None else: if settings.TEMPLATE_STRING_IF_INVALID: + global invalid_var_format_string + if invalid_var_format_string is None: + invalid_var_format_string = '%s' in settings.TEMPLATE_STRING_IF_INVALID + if invalid_var_format_string: + return settings.TEMPLATE_STRING_IF_INVALID % self.var return settings.TEMPLATE_STRING_IF_INVALID else: obj = settings.TEMPLATE_STRING_IF_INVALID @@ -574,6 +601,8 @@ class FilterExpression(object): def args_check(name, func, provided): provided = list(provided) plen = len(provided) + # Check to see if a decorator is providing the real function. + func = getattr(func, '_decorated_function', func) args, varargs, varkw, defaults = getargspec(func) # First argument is filter input. args.pop(0) @@ -624,12 +653,9 @@ def resolve_variable(path, context): (The example assumes VARIABLE_ATTRIBUTE_SEPARATOR is '.') """ - if path[0].isdigit(): + if number_re.match(path): number_type = '.' in path and float or int - try: - current = number_type(path) - except ValueError: - current = settings.TEMPLATE_STRING_IF_INVALID + current = number_type(path) elif path[0] in ('"', "'") and path[0] == path[-1]: current = path[1:-1] else: @@ -659,8 +685,12 @@ def resolve_variable(path, context): except (TypeError, AttributeError): try: # list-index lookup current = current[int(bits[0])] - except (IndexError, ValueError, KeyError): - raise VariableDoesNotExist, "Failed lookup for key [%s] in %r" % (bits[0], current) # missing attribute + except (IndexError, # list index out of range + ValueError, # invalid literal for int() + KeyError, # current is a dict without `int(bits[0])` key + TypeError, # unsubscriptable object + ): + raise VariableDoesNotExist("Failed lookup for key [%s] in %r", (bits[0], current)) # missing attribute except Exception, e: if getattr(e, 'silent_variable_failure', False): current = settings.TEMPLATE_STRING_IF_INVALID @@ -669,10 +699,26 @@ def resolve_variable(path, context): del bits[0] return current +class NodeBase(type): + def __new__(cls, name, bases, attrs): + """ + Ensures that either a 'render' or 'render_iter' method is defined on + any Node sub-class. This avoids potential infinite loops at runtime. + """ + if not (isinstance(attrs.get('render'), types.FunctionType) or + isinstance(attrs.get('iter_render'), types.FunctionType)): + raise TypeError('Unable to create Node subclass without either "render" or "iter_render" method.') + return type.__new__(cls, name, bases, attrs) + class Node(object): + __metaclass__ = NodeBase + + def iter_render(self, context): + return (self.render(context),) + def render(self, context): "Return the node rendered as a string" - pass + return ''.join(self.iter_render(context)) def __iter__(self): yield self @@ -688,13 +734,12 @@ class Node(object): class NodeList(list): def render(self, context): - bits = [] + return ''.join(self.iter_render(context)) + + def iter_render(self, context): for node in self: - if isinstance(node, Node): - bits.append(self.render_node(node, context)) - else: - bits.append(node) - return ''.join(bits) + for chunk in node.iter_render(context): + yield chunk def get_nodes_by_type(self, nodetype): "Return a list of all nodes of the given type" @@ -703,24 +748,25 @@ class NodeList(list): nodes.extend(node.get_nodes_by_type(nodetype)) return nodes - def render_node(self, node, context): - return(node.render(context)) - class DebugNodeList(NodeList): - def render_node(self, node, context): - try: - result = node.render(context) - except TemplateSyntaxError, e: - if not hasattr(e, 'source'): - e.source = node.source - raise - except Exception, e: - from sys import exc_info - wrapped = TemplateSyntaxError('Caught an exception while rendering: %s' % e) - wrapped.source = node.source - wrapped.exc_info = exc_info() - raise wrapped - return result + def iter_render(self, context): + for node in self: + if not isinstance(node, Node): + yield node + continue + try: + for chunk in node.iter_render(context): + yield chunk + except TemplateSyntaxError, e: + if not hasattr(e, 'source'): + e.source = node.source + raise + except Exception, e: + from sys import exc_info + wrapped = TemplateSyntaxError('Caught an exception while rendering: %s' % e) + wrapped.source = node.source + wrapped.exc_info = exc_info() + raise wrapped class TextNode(Node): def __init__(self, s): @@ -729,6 +775,9 @@ class TextNode(Node): def __repr__(self): return "<Text Node: '%s'>" % self.s[:25] + def iter_render(self, context): + return (self.s,) + def render(self, context): return self.s @@ -752,6 +801,9 @@ class VariableNode(Node): else: return output + def iter_render(self, context): + return (self.render(context),) + def render(self, context): output = self.filter_expression.resolve(context) return self.encode_output(output) @@ -806,7 +858,7 @@ class Library(object): raise InvalidTemplateLibrary, "Unsupported arguments to Library.tag: (%r, %r)", (name, compile_function) def tag_function(self,func): - self.tags[func.__name__] = func + self.tags[getattr(func, "_decorated_function", func).__name__] = func return func def filter(self, name=None, filter_func=None): @@ -830,7 +882,7 @@ class Library(object): raise InvalidTemplateLibrary, "Unsupported arguments to Library.filter: (%r, %r)", (name, filter_func) def filter_function(self, func): - self.filters[func.__name__] = func + self.filters[getattr(func, "_decorated_function", func).__name__] = func return func def simple_tag(self,func): @@ -840,13 +892,16 @@ class Library(object): def __init__(self, vars_to_resolve): self.vars_to_resolve = vars_to_resolve + #def iter_render(self, context): + # return (self.render(context),) + def render(self, context): resolved_vars = [resolve_variable(var, context) for var in self.vars_to_resolve] return func(*resolved_vars) - compile_func = curry(generic_tag_compiler, params, defaults, func.__name__, SimpleNode) + compile_func = curry(generic_tag_compiler, params, defaults, getattr(func, "_decorated_function", func).__name__, SimpleNode) compile_func.__doc__ = func.__doc__ - self.tag(func.__name__, compile_func) + self.tag(getattr(func, "_decorated_function", func).__name__, compile_func) return func def inclusion_tag(self, file_name, context_class=Context, takes_context=False): @@ -862,7 +917,7 @@ class Library(object): def __init__(self, vars_to_resolve): self.vars_to_resolve = vars_to_resolve - def render(self, context): + def iter_render(self, context): resolved_vars = [resolve_variable(var, context) for var in self.vars_to_resolve] if takes_context: args = [context] + resolved_vars @@ -878,11 +933,11 @@ class Library(object): else: t = get_template(file_name) self.nodelist = t.nodelist - return self.nodelist.render(context_class(dict)) + return self.nodelist.iter_render(context_class(dict)) - compile_func = curry(generic_tag_compiler, params, defaults, func.__name__, InclusionNode) + compile_func = curry(generic_tag_compiler, params, defaults, getattr(func, "_decorated_function", func).__name__, InclusionNode) compile_func.__doc__ = func.__doc__ - self.tag(func.__name__, compile_func) + self.tag(getattr(func, "_decorated_function", func).__name__, compile_func) return func return dec |
