summaryrefslogtreecommitdiff
path: root/django/template/__init__.py
diff options
context:
space:
mode:
authorChristopher Long <indirecthit@gmail.com>2007-06-17 22:18:54 +0000
committerChristopher Long <indirecthit@gmail.com>2007-06-17 22:18:54 +0000
commitae22b6d403dcf25098c77f0dfcf59ae58b186461 (patch)
treec37fc631e99a7e4d909d6b6d236f495003731ea7 /django/template/__init__.py
parent0cf7bc439129c66df8d64601e885f83b256b4f25 (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__.py189
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="&lt;unknown source&gt;"
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