summaryrefslogtreecommitdiff
path: root/django/template
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
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')
-rw-r--r--django/template/__init__.py189
-rw-r--r--django/template/context.py9
-rw-r--r--django/template/defaultfilters.py135
-rw-r--r--django/template/defaulttags.py347
-rw-r--r--django/template/loader.py33
-rw-r--r--django/template/loader_tags.py27
6 files changed, 531 insertions, 209 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
diff --git a/django/template/context.py b/django/template/context.py
index ba23e95ab7..59650b05fe 100644
--- a/django/template/context.py
+++ b/django/template/context.py
@@ -35,7 +35,7 @@ class Context(object):
def __getitem__(self, key):
"Get a variable's value, starting at the current context and going upward"
for d in self.dicts:
- if d.has_key(key):
+ if key in d:
return d[key]
raise KeyError(key)
@@ -45,13 +45,16 @@ class Context(object):
def has_key(self, key):
for d in self.dicts:
- if d.has_key(key):
+ if key in d:
return True
return False
+ def __contains__(self, key):
+ return self.has_key(key)
+
def get(self, key, otherwise=None):
for d in self.dicts:
- if d.has_key(key):
+ if key in d:
return d[key]
return otherwise
diff --git a/django/template/defaultfilters.py b/django/template/defaultfilters.py
index 969ef7b28b..bbaceba24a 100644
--- a/django/template/defaultfilters.py
+++ b/django/template/defaultfilters.py
@@ -2,12 +2,44 @@
from django.template import resolve_variable, Library
from django.conf import settings
-from django.utils.translation import gettext
+from django.utils.translation import gettext, ngettext
import re
import random as random_module
register = Library()
+#######################
+# STRING DECORATOR #
+#######################
+
+def smart_string(obj):
+ # FUTURE: Unicode strings should probably be normalized to a specific
+ # encoding and non-unicode strings should be converted to unicode too.
+# if isinstance(obj, unicode):
+# obj = obj.encode(settings.DEFAULT_CHARSET)
+# else:
+# obj = unicode(obj, settings.DEFAULT_CHARSET)
+ # FUTURE: Replace dumb string logic below with cool unicode logic above.
+ if not isinstance(obj, basestring):
+ obj = str(obj)
+ return obj
+
+def stringfilter(func):
+ """
+ Decorator for filters which should only receive strings. The object passed
+ as the first positional argument will be converted to a string.
+ """
+ def _dec(*args, **kwargs):
+ if args:
+ args = list(args)
+ args[0] = smart_string(args[0])
+ return func(*args, **kwargs)
+
+ # Include a reference to the real function (used to check original
+ # arguments by the template parser).
+ _dec._decorated_function = getattr(func, '_decorated_function', func)
+ return _dec
+
###################
# STRINGS #
###################
@@ -16,31 +48,52 @@ register = Library()
def addslashes(value):
"Adds slashes - useful for passing strings to JavaScript, for example."
return value.replace('\\', '\\\\').replace('"', '\\"').replace("'", "\\'")
+addslashes = stringfilter(addslashes)
def capfirst(value):
"Capitalizes the first character of the value"
- value = str(value)
return value and value[0].upper() + value[1:]
-
+capfirst = stringfilter(capfirst)
+
def fix_ampersands(value):
"Replaces ampersands with ``&amp;`` entities"
from django.utils.html import fix_ampersands
return fix_ampersands(value)
+fix_ampersands = stringfilter(fix_ampersands)
-def floatformat(text):
+def floatformat(text, arg=-1):
"""
- Displays a floating point number as 34.2 (with one decimal place) -- but
- only if there's a point to be displayed
+ If called without an argument, displays a floating point
+ number as 34.2 -- but only if there's a point to be displayed.
+ With a positive numeric argument, it displays that many decimal places
+ always.
+ With a negative numeric argument, it will display that many decimal
+ places -- but only if there's places to be displayed.
+ Examples:
+
+ * num1 = 34.23234
+ * num2 = 34.00000
+ * num1|floatformat results in 34.2
+ * num2|floatformat is 34
+ * num1|floatformat:3 is 34.232
+ * num2|floatformat:3 is 34.000
+ * num1|floatformat:-3 is 34.232
+ * num2|floatformat:-3 is 34
"""
try:
f = float(text)
except ValueError:
return ''
+ try:
+ d = int(arg)
+ except ValueError:
+ return smart_string(f)
m = f - int(f)
- if m:
- return '%.1f' % f
- else:
+ if not m and d < 0:
return '%d' % int(f)
+ else:
+ formatstr = '%%.%df' % abs(d)
+ return formatstr % f
def linenumbers(value):
"Displays text with line numbers"
@@ -51,22 +104,26 @@ def linenumbers(value):
for i, line in enumerate(lines):
lines[i] = ("%0" + width + "d. %s") % (i + 1, escape(line))
return '\n'.join(lines)
+linenumbers = stringfilter(linenumbers)
def lower(value):
"Converts a string into all lowercase"
return value.lower()
+lower = stringfilter(lower)
def make_list(value):
"""
Returns the value turned into a list. For an integer, it's a list of
digits. For a string, it's a list of characters.
"""
- return list(str(value))
+ return list(value)
+make_list = stringfilter(make_list)
def slugify(value):
"Converts to lowercase, removes non-alpha chars and converts spaces to hyphens"
value = re.sub('[^\w\s-]', '', value).strip().lower()
return re.sub('[-\s]+', '-', value)
+slugify = stringfilter(slugify)
def stringformat(value, arg):
"""
@@ -78,13 +135,14 @@ def stringformat(value, arg):
of Python string formatting
"""
try:
- return ("%" + arg) % value
+ return ("%" + str(arg)) % value
except (ValueError, TypeError):
return ""
def title(value):
"Converts a string into titlecase"
return re.sub("([a-z])'([A-Z])", lambda m: m.group(0).lower(), value.title())
+title = stringfilter(title)
def truncatewords(value, arg):
"""
@@ -100,20 +158,42 @@ def truncatewords(value, arg):
if not isinstance(value, basestring):
value = str(value)
return truncate_words(value, length)
+truncatewords = stringfilter(truncatewords)
+
+def truncatewords_html(value, arg):
+ """
+ Truncates HTML after a certain number of words
+
+ Argument: Number of words to truncate after
+ """
+ from django.utils.text import truncate_html_words
+ try:
+ length = int(arg)
+ except ValueError: # invalid literal for int()
+ return value # Fail silently.
+ if not isinstance(value, basestring):
+ value = str(value)
+ return truncate_html_words(value, length)
+truncatewords_html = stringfilter(truncatewords_html)
def upper(value):
"Converts a string into all uppercase"
return value.upper()
+upper = stringfilter(upper)
def urlencode(value):
"Escapes a value for use in a URL"
import urllib
+ if not isinstance(value, basestring):
+ value = str(value)
return urllib.quote(value)
+urlencode = stringfilter(urlencode)
def urlize(value):
"Converts URLs in plain text into clickable links"
from django.utils.html import urlize
return urlize(value, nofollow=True)
+urlize = stringfilter(urlize)
def urlizetrunc(value, limit):
"""
@@ -124,10 +204,12 @@ def urlizetrunc(value, limit):
"""
from django.utils.html import urlize
return urlize(value, trim_url_limit=int(limit), nofollow=True)
+urlizetrunc = stringfilter(urlizetrunc)
def wordcount(value):
"Returns the number of words"
return len(value.split())
+wordcount = stringfilter(wordcount)
def wordwrap(value, arg):
"""
@@ -136,7 +218,8 @@ def wordwrap(value, arg):
Argument: number of characters to wrap the text at.
"""
from django.utils.text import wrap
- return wrap(str(value), int(arg))
+ return wrap(value, int(arg))
+wordwrap = stringfilter(wordwrap)
def ljust(value, arg):
"""
@@ -144,7 +227,8 @@ def ljust(value, arg):
Argument: field size
"""
- return str(value).ljust(int(arg))
+ return value.ljust(int(arg))
+ljust = stringfilter(ljust)
def rjust(value, arg):
"""
@@ -152,15 +236,18 @@ def rjust(value, arg):
Argument: field size
"""
- return str(value).rjust(int(arg))
+ return value.rjust(int(arg))
+rjust = stringfilter(rjust)
def center(value, arg):
"Centers the value in a field of a given width"
- return str(value).center(int(arg))
+ return value.center(int(arg))
+center = stringfilter(center)
def cut(value, arg):
"Removes all values of arg from the given string"
return value.replace(arg, '')
+cut = stringfilter(cut)
###################
# HTML STRINGS #
@@ -170,15 +257,18 @@ def escape(value):
"Escapes a string's HTML"
from django.utils.html import escape
return escape(value)
+escape = stringfilter(escape)
def linebreaks(value):
"Converts newlines into <p> and <br />s"
from django.utils.html import linebreaks
return linebreaks(value)
+linebreaks = stringfilter(linebreaks)
def linebreaksbr(value):
"Converts newlines into <br />s"
return value.replace('\n', '<br />')
+linebreaksbr = stringfilter(linebreaksbr)
def removetags(value, tags):
"Removes a space separated list of [X]HTML tags from the output"
@@ -189,13 +279,13 @@ def removetags(value, tags):
value = starttag_re.sub('', value)
value = endtag_re.sub('', value)
return value
+removetags = stringfilter(removetags)
def striptags(value):
"Strips all [X]HTML tags"
from django.utils.html import strip_tags
- if not isinstance(value, basestring):
- value = str(value)
return strip_tags(value)
+striptags = stringfilter(striptags)
###################
# LISTS #
@@ -230,7 +320,7 @@ def first(value):
def join(value, arg):
"Joins a list with a string, like Python's ``str.join(list)``"
try:
- return arg.join(map(str, value))
+ return arg.join(map(smart_string, value))
except AttributeError: # fail silently but nicely
return value
@@ -427,12 +517,12 @@ def filesizeformat(bytes):
return "0 bytes"
if bytes < 1024:
- return "%d byte%s" % (bytes, bytes != 1 and 's' or '')
+ return ngettext("%(size)d byte", "%(size)d bytes", bytes) % {'size': bytes}
if bytes < 1024 * 1024:
- return "%.1f KB" % (bytes / 1024)
+ return gettext("%.1f KB") % (bytes / 1024)
if bytes < 1024 * 1024 * 1024:
- return "%.1f MB" % (bytes / (1024 * 1024))
- return "%.1f GB" % (bytes / (1024 * 1024 * 1024))
+ return gettext("%.1f MB") % (bytes / (1024 * 1024))
+ return gettext("%.1f GB") % (bytes / (1024 * 1024 * 1024))
def pluralize(value, arg='s'):
"""
@@ -516,6 +606,7 @@ register.filter(timesince)
register.filter(timeuntil)
register.filter(title)
register.filter(truncatewords)
+register.filter(truncatewords_html)
register.filter(unordered_list)
register.filter(upper)
register.filter(urlencode)
diff --git a/django/template/defaulttags.py b/django/template/defaulttags.py
index 3f3f4bda56..77fac6bec5 100644
--- a/django/template/defaulttags.py
+++ b/django/template/defaulttags.py
@@ -4,13 +4,22 @@ from django.template import Node, NodeList, Template, Context, resolve_variable
from django.template import TemplateSyntaxError, VariableDoesNotExist, BLOCK_TAG_START, BLOCK_TAG_END, VARIABLE_TAG_START, VARIABLE_TAG_END, SINGLE_BRACE_START, SINGLE_BRACE_END, COMMENT_TAG_START, COMMENT_TAG_END
from django.template import get_library, Library, InvalidTemplateLibrary
from django.conf import settings
+from django.utils.itercompat import groupby
import sys
+import re
+
+if not hasattr(__builtins__, 'reversed'):
+ # For Python 2.3.
+ # From http://www.python.org/doc/current/tut/node11.html
+ def reversed(data):
+ for index in xrange(len(data)-1, -1, -1):
+ yield data[index]
register = Library()
class CommentNode(Node):
- def render(self, context):
- return ''
+ def iter_render(self, context):
+ return ()
class CycleNode(Node):
def __init__(self, cyclevars, variable_name=None):
@@ -19,6 +28,9 @@ class CycleNode(Node):
self.counter = -1
self.variable_name = variable_name
+ def iter_render(self, context):
+ return (self.render(context),)
+
def render(self, context):
self.counter += 1
value = self.cyclevars[self.counter % self.cyclevars_len]
@@ -27,26 +39,32 @@ class CycleNode(Node):
return value
class DebugNode(Node):
- def render(self, context):
+ def iter_render(self, context):
from pprint import pformat
- output = [pformat(val) for val in context]
- output.append('\n\n')
- output.append(pformat(sys.modules))
- return ''.join(output)
+ for val in context:
+ yield pformat(val)
+ yield "\n\n"
+ yield pformat(sys.modules)
class FilterNode(Node):
def __init__(self, filter_expr, nodelist):
self.filter_expr, self.nodelist = filter_expr, nodelist
- def render(self, context):
+ def iter_render(self, context):
output = self.nodelist.render(context)
# apply filters
- return self.filter_expr.resolve(Context({'var': output}))
+ context.update({'var': output})
+ filtered = self.filter_expr.resolve(context)
+ context.pop()
+ return (filtered,)
class FirstOfNode(Node):
def __init__(self, vars):
self.vars = vars
+ def iter_render(self, context):
+ return (self.render(context),)
+
def render(self, context):
for var in self.vars:
try:
@@ -58,8 +76,8 @@ class FirstOfNode(Node):
return ''
class ForNode(Node):
- def __init__(self, loopvar, sequence, reversed, nodelist_loop):
- self.loopvar, self.sequence = loopvar, sequence
+ def __init__(self, loopvars, sequence, reversed, nodelist_loop):
+ self.loopvars, self.sequence = loopvars, sequence
self.reversed = reversed
self.nodelist_loop = nodelist_loop
@@ -69,7 +87,7 @@ class ForNode(Node):
else:
reversed = ''
return "<For Node: for %s in %s, tail_len: %d%s>" % \
- (self.loopvar, self.sequence, len(self.nodelist_loop), reversed)
+ (', '.join( self.loopvars ), self.sequence, len(self.nodelist_loop), reversed)
def __iter__(self):
for node in self.nodelist_loop:
@@ -82,28 +100,24 @@ class ForNode(Node):
nodes.extend(self.nodelist_loop.get_nodes_by_type(nodetype))
return nodes
- def render(self, context):
- nodelist = NodeList()
- if context.has_key('forloop'):
+ def iter_render(self, context):
+ if 'forloop' in context:
parentloop = context['forloop']
else:
parentloop = {}
context.push()
try:
values = self.sequence.resolve(context, True)
+ if values is None:
+ values = ()
+ elif not hasattr(values, '__len__'):
+ values = list(values)
except VariableDoesNotExist:
- values = []
- if values is None:
- values = []
- if not hasattr(values, '__len__'):
- values = list(values)
+ values = ()
len_values = len(values)
if self.reversed:
- # From http://www.python.org/doc/current/tut/node11.html
- def reverse(data):
- for index in range(len(data)-1, -1, -1):
- yield data[index]
- values = reverse(values)
+ values = reversed(values)
+ unpack = len(self.loopvars) > 1
for i, item in enumerate(values):
context['forloop'] = {
# shortcuts for current loop iteration number
@@ -117,11 +131,26 @@ class ForNode(Node):
'last': (i == len_values - 1),
'parentloop': parentloop,
}
- context[self.loopvar] = item
+ if unpack:
+ # If there are multiple loop variables, unpack the item into
+ # them.
+ context.update(dict(zip(self.loopvars, item)))
+ else:
+ context[self.loopvars[0]] = item
+
+ # We inline this to avoid the overhead since ForNode is pretty
+ # common.
for node in self.nodelist_loop:
- nodelist.append(node.render(context))
+ for chunk in node.iter_render(context):
+ yield chunk
+ if unpack:
+ # The loop variables were pushed on to the context so pop them
+ # off again. This is necessary because the tag lets the length
+ # of loopvars differ to the length of each set of items and we
+ # don't want to leave any vars from the previous loop on the
+ # context.
+ context.pop()
context.pop()
- return nodelist.render(context)
class IfChangedNode(Node):
def __init__(self, nodelist, *varlist):
@@ -129,8 +158,8 @@ class IfChangedNode(Node):
self._last_seen = None
self._varlist = varlist
- def render(self, context):
- if context.has_key('forloop') and context['forloop']['first']:
+ def iter_render(self, context):
+ if 'forloop' in context and context['forloop']['first']:
self._last_seen = None
try:
if self._varlist:
@@ -140,18 +169,16 @@ class IfChangedNode(Node):
else:
compare_to = self.nodelist.render(context)
except VariableDoesNotExist:
- compare_to = None
+ compare_to = None
if compare_to != self._last_seen:
firstloop = (self._last_seen == None)
self._last_seen = compare_to
context.push()
context['ifchanged'] = {'firstloop': firstloop}
- content = self.nodelist.render(context)
+ for chunk in self.nodelist.iter_render(context):
+ yield chunk
context.pop()
- return content
- else:
- return ''
class IfEqualNode(Node):
def __init__(self, var1, var2, nodelist_true, nodelist_false, negate):
@@ -162,7 +189,7 @@ class IfEqualNode(Node):
def __repr__(self):
return "<IfEqualNode>"
- def render(self, context):
+ def iter_render(self, context):
try:
val1 = resolve_variable(self.var1, context)
except VariableDoesNotExist:
@@ -172,8 +199,8 @@ class IfEqualNode(Node):
except VariableDoesNotExist:
val2 = None
if (self.negate and val1 != val2) or (not self.negate and val1 == val2):
- return self.nodelist_true.render(context)
- return self.nodelist_false.render(context)
+ return self.nodelist_true.iter_render(context)
+ return self.nodelist_false.iter_render(context)
class IfNode(Node):
def __init__(self, bool_exprs, nodelist_true, nodelist_false, link_type):
@@ -198,7 +225,7 @@ class IfNode(Node):
nodes.extend(self.nodelist_false.get_nodes_by_type(nodetype))
return nodes
- def render(self, context):
+ def iter_render(self, context):
if self.link_type == IfNode.LinkTypes.or_:
for ifnot, bool_expr in self.bool_exprs:
try:
@@ -206,8 +233,8 @@ class IfNode(Node):
except VariableDoesNotExist:
value = None
if (value and not ifnot) or (ifnot and not value):
- return self.nodelist_true.render(context)
- return self.nodelist_false.render(context)
+ return self.nodelist_true.iter_render(context)
+ return self.nodelist_false.iter_render(context)
else:
for ifnot, bool_expr in self.bool_exprs:
try:
@@ -215,8 +242,8 @@ class IfNode(Node):
except VariableDoesNotExist:
value = None
if not ((value and not ifnot) or (ifnot and not value)):
- return self.nodelist_false.render(context)
- return self.nodelist_true.render(context)
+ return self.nodelist_false.iter_render(context)
+ return self.nodelist_true.iter_render(context)
class LinkTypes:
and_ = 0,
@@ -227,21 +254,16 @@ class RegroupNode(Node):
self.target, self.expression = target, expression
self.var_name = var_name
- def render(self, context):
+ def iter_render(self, context):
obj_list = self.target.resolve(context, True)
if obj_list == None: # target_var wasn't found in context; fail silently
context[self.var_name] = []
- return ''
- output = [] # list of dictionaries in the format {'grouper': 'key', 'list': [list of contents]}
- for obj in obj_list:
- grouper = self.expression.resolve(Context({'var': obj}), True)
- # TODO: Is this a sensible way to determine equality?
- if output and repr(output[-1]['grouper']) == repr(grouper):
- output[-1]['list'].append(obj)
- else:
- output.append({'grouper': grouper, 'list': [obj]})
- context[self.var_name] = output
- return ''
+ return ()
+ # List of dictionaries in the format
+ # {'grouper': 'key', 'list': [list of contents]}.
+ context[self.var_name] = [{'grouper':key, 'list':list(val)} for key, val in
+ groupby(obj_list, lambda v, f=self.expression.resolve: f(v, True))]
+ return ()
def include_is_allowed(filepath):
for root in settings.ALLOWED_INCLUDE_ROOTS:
@@ -253,10 +275,10 @@ class SsiNode(Node):
def __init__(self, filepath, parsed):
self.filepath, self.parsed = filepath, parsed
- def render(self, context):
+ def iter_render(self, context):
if not include_is_allowed(self.filepath):
if settings.DEBUG:
- return "[Didn't have permission to include file]"
+ return ("[Didn't have permission to include file]",)
else:
return '' # Fail silently for invalid includes.
try:
@@ -267,23 +289,25 @@ class SsiNode(Node):
output = ''
if self.parsed:
try:
- t = Template(output, name=self.filepath)
- return t.render(context)
+ return Template(output, name=self.filepath).iter_render(context)
except TemplateSyntaxError, e:
if settings.DEBUG:
return "[Included template had syntax error: %s]" % e
else:
return '' # Fail silently for invalid included templates.
- return output
+ return (output,)
class LoadNode(Node):
- def render(self, context):
- return ''
+ def iter_render(self, context):
+ return ()
class NowNode(Node):
def __init__(self, format_string):
self.format_string = format_string
+ def iter_render(self, context):
+ return (self.render(context),)
+
def render(self, context):
from datetime import datetime
from django.utils.dateformat import DateFormat
@@ -312,15 +336,40 @@ class TemplateTagNode(Node):
def __init__(self, tagtype):
self.tagtype = tagtype
+ def iter_render(self, context):
+ return (self.render(context),)
+
def render(self, context):
return self.mapping.get(self.tagtype, '')
+class URLNode(Node):
+ def __init__(self, view_name, args, kwargs):
+ self.view_name = view_name
+ self.args = args
+ self.kwargs = kwargs
+
+ def iter_render(self, context):
+ from django.core.urlresolvers import reverse, NoReverseMatch
+ args = [arg.resolve(context) for arg in self.args]
+ kwargs = dict([(k, v.resolve(context)) for k, v in self.kwargs.items()])
+ try:
+ return (reverse(self.view_name, args=args, kwargs=kwargs),)
+ except NoReverseMatch:
+ try:
+ project_name = settings.SETTINGS_MODULE.split('.')[0]
+ return reverse(project_name + '.' + self.view_name, args=args, kwargs=kwargs)
+ except NoReverseMatch:
+ return ()
+
class WidthRatioNode(Node):
def __init__(self, val_expr, max_expr, max_width):
self.val_expr = val_expr
self.max_expr = max_expr
self.max_width = max_width
+ def iter_render(self, context):
+ return (self.render(context),)
+
def render(self, context):
try:
value = self.val_expr.resolve(context)
@@ -335,6 +384,23 @@ class WidthRatioNode(Node):
return ''
return str(int(round(ratio)))
+class WithNode(Node):
+ def __init__(self, var, name, nodelist):
+ self.var = var
+ self.name = name
+ self.nodelist = nodelist
+
+ def __repr__(self):
+ return "<WithNode>"
+
+ def iter_render(self, context):
+ val = self.var.resolve(context)
+ context.push()
+ context[self.name] = val
+ for chunk in self.nodelist.iter_render(context):
+ yield chunk
+ context.pop()
+
#@register.tag
def comment(parser, token):
"""
@@ -393,7 +459,7 @@ def cycle(parser, token):
name = args[1]
if not hasattr(parser, '_namedCycleNodes'):
raise TemplateSyntaxError("No named cycles in template: '%s' is not defined" % name)
- if not parser._namedCycleNodes.has_key(name):
+ if name not in parser._namedCycleNodes:
raise TemplateSyntaxError("Named cycle '%s' does not exist" % name)
return parser._namedCycleNodes[name]
@@ -416,6 +482,15 @@ def cycle(parser, token):
cycle = register.tag(cycle)
def debug(parser, token):
+ """
+ Output a whole load of debugging information, including the current context and imported modules.
+
+ Sample usage::
+
+ <pre>
+ {% debug %}
+ </pre>
+ """
return DebugNode()
debug = register.tag(debug)
@@ -438,7 +513,7 @@ def do_filter(parser, token):
nodelist = parser.parse(('endfilter',))
parser.delete_first_token()
return FilterNode(filter_expr, nodelist)
-filter = register.tag("filter", do_filter)
+do_filter = register.tag("filter", do_filter)
#@register.tag
def firstof(parser, token):
@@ -482,8 +557,14 @@ def do_for(parser, token):
{% endfor %}
</ul>
- You can also loop over a list in reverse by using
+ You can loop over a list in reverse by using
``{% for obj in list reversed %}``.
+
+ You can also unpack multiple values from a two-dimensional array::
+
+ {% for key,value in dict.items %}
+ {{ key }}: {{ value }}
+ {% endfor %}
The for loop sets a number of variables available within the loop:
@@ -504,36 +585,26 @@ def do_for(parser, token):
"""
bits = token.contents.split()
- if len(bits) == 5 and bits[4] != 'reversed':
- raise TemplateSyntaxError, "'for' statements with five words should end in 'reversed': %s" % token.contents
- if len(bits) not in (4, 5):
- raise TemplateSyntaxError, "'for' statements should have either four or five words: %s" % token.contents
- if bits[2] != 'in':
- raise TemplateSyntaxError, "'for' statement must contain 'in' as the second word: %s" % token.contents
- loopvar = bits[1]
- sequence = parser.compile_filter(bits[3])
- reversed = (len(bits) == 5)
+ if len(bits) < 4:
+ raise TemplateSyntaxError, "'for' statements should have at least four words: %s" % token.contents
+
+ reversed = bits[-1] == 'reversed'
+ in_index = reversed and -3 or -2
+ if bits[in_index] != 'in':
+ raise TemplateSyntaxError, "'for' statements should use the format 'for x in y': %s" % token.contents
+
+ loopvars = re.sub(r' *, *', ',', ' '.join(bits[1:in_index])).split(',')
+ for var in loopvars:
+ if not var or ' ' in var:
+ raise TemplateSyntaxError, "'for' tag received an invalid argument: %s" % token.contents
+
+ sequence = parser.compile_filter(bits[in_index+1])
nodelist_loop = parser.parse(('endfor',))
parser.delete_first_token()
- return ForNode(loopvar, sequence, reversed, nodelist_loop)
+ return ForNode(loopvars, sequence, reversed, nodelist_loop)
do_for = register.tag("for", do_for)
def do_ifequal(parser, token, negate):
- """
- Output the contents of the block if the two arguments equal/don't equal each other.
-
- Examples::
-
- {% ifequal user.id comment.user_id %}
- ...
- {% endifequal %}
-
- {% ifnotequal user.id comment.user_id %}
- ...
- {% else %}
- ...
- {% endifnotequal %}
- """
bits = list(token.split_contents())
if len(bits) != 3:
raise TemplateSyntaxError, "%r takes two arguments" % bits[0]
@@ -549,11 +620,27 @@ def do_ifequal(parser, token, negate):
#@register.tag
def ifequal(parser, token):
+ """
+ Output the contents of the block if the two arguments equal each other.
+
+ Examples::
+
+ {% ifequal user.id comment.user_id %}
+ ...
+ {% endifequal %}
+
+ {% ifnotequal user.id comment.user_id %}
+ ...
+ {% else %}
+ ...
+ {% endifnotequal %}
+ """
return do_ifequal(parser, token, False)
ifequal = register.tag(ifequal)
#@register.tag
def ifnotequal(parser, token):
+ """Output the contents of the block if the two arguments are not equal. See ifequal."""
return do_ifequal(parser, token, True)
ifnotequal = register.tag(ifnotequal)
@@ -566,8 +653,8 @@ def do_if(parser, token):
::
- {% if althlete_list %}
- Number of athletes: {{ althete_list|count }}
+ {% if athlete_list %}
+ Number of athletes: {{ athlete_list|count }}
{% else %}
No athletes.
{% endif %}
@@ -798,7 +885,7 @@ def regroup(parser, token):
if lastbits_reversed[1][::-1] != 'as':
raise TemplateSyntaxError, "next-to-last argument to 'regroup' tag must be 'as'"
- expression = parser.compile_filter('var.%s' % lastbits_reversed[2][::-1])
+ expression = parser.compile_filter(lastbits_reversed[2][::-1])
var_name = lastbits_reversed[0][::-1]
return RegroupNode(target, expression, var_name)
@@ -806,7 +893,7 @@ regroup = register.tag(regroup)
def spaceless(parser, token):
"""
- Normalize whitespace between HTML tags to a single space. This includes tab
+ Removes whitespace between HTML tags. This includes tab
characters and newlines.
Example usage::
@@ -819,7 +906,7 @@ def spaceless(parser, token):
This example would return this HTML::
- <p> <a href="foo/">Foo</a> </p>
+ <p><a href="foo/">Foo</a></p>
Only space between *tags* is normalized -- not space between tags and text. In
this example, the space around ``Hello`` won't be stripped::
@@ -862,12 +949,58 @@ def templatetag(parser, token):
if len(bits) != 2:
raise TemplateSyntaxError, "'templatetag' statement takes one argument"
tag = bits[1]
- if not TemplateTagNode.mapping.has_key(tag):
+ if tag not in TemplateTagNode.mapping:
raise TemplateSyntaxError, "Invalid templatetag argument: '%s'. Must be one of: %s" % \
(tag, TemplateTagNode.mapping.keys())
return TemplateTagNode(tag)
templatetag = register.tag(templatetag)
+def url(parser, token):
+ """
+ Returns an absolute URL matching given view with its parameters.
+
+ This is a way to define links that aren't tied to a particular URL configuration::
+
+ {% url path.to.some_view arg1,arg2,name1=value1 %}
+
+ The first argument is a path to a view. It can be an absolute python path
+ or just ``app_name.view_name`` without the project name if the view is
+ located inside the project. Other arguments are comma-separated values
+ that will be filled in place of positional and keyword arguments in the
+ URL. All arguments for the URL should be present.
+
+ For example if you have a view ``app_name.client`` taking client's id and
+ the corresponding line in a URLconf looks like this::
+
+ ('^client/(\d+)/$', 'app_name.client')
+
+ and this app's URLconf is included into the project's URLconf under some
+ path::
+
+ ('^clients/', include('project_name.app_name.urls'))
+
+ then in a template you can create a link for a certain client like this::
+
+ {% url app_name.client client.id %}
+
+ The URL will look like ``/clients/client/123/``.
+ """
+ bits = token.contents.split(' ', 2)
+ if len(bits) < 2:
+ raise TemplateSyntaxError, "'%s' takes at least one argument (path to a view)" % bits[0]
+ args = []
+ kwargs = {}
+ if len(bits) > 2:
+ for arg in bits[2].split(','):
+ if '=' in arg:
+ k, v = arg.split('=', 1)
+ k = k.strip()
+ kwargs[k] = parser.compile_filter(v)
+ else:
+ args.append(parser.compile_filter(arg))
+ return URLNode(bits[1], args, kwargs)
+url = register.tag(url)
+
#@register.tag
def widthratio(parser, token):
"""
@@ -893,3 +1026,25 @@ def widthratio(parser, token):
return WidthRatioNode(parser.compile_filter(this_value_expr),
parser.compile_filter(max_value_expr), max_width)
widthratio = register.tag(widthratio)
+
+#@register.tag
+def do_with(parser, token):
+ """
+ Add a value to the context (inside of this block) for caching and easy
+ access.
+
+ For example::
+
+ {% with person.some_sql_method as total %}
+ {{ total }} object{{ total|pluralize }}
+ {% endwith %}
+ """
+ bits = list(token.split_contents())
+ if len(bits) != 4 or bits[2] != "as":
+ raise TemplateSyntaxError, "%r expected format is 'value as name'" % bits[0]
+ var = parser.compile_filter(bits[1])
+ name = bits[3]
+ nodelist = parser.parse(('endwith',))
+ parser.delete_first_token()
+ return WithNode(var, name, nodelist)
+do_with = register.tag('with', do_with)
diff --git a/django/template/loader.py b/django/template/loader.py
index 03e6f8d49d..45cf5a9d7c 100644
--- a/django/template/loader.py
+++ b/django/template/loader.py
@@ -87,14 +87,12 @@ def get_template_from_string(source, origin=None, name=None):
"""
return Template(source, origin, name)
-def render_to_string(template_name, dictionary=None, context_instance=None):
+def _render_setup(template_name, dictionary=None, context_instance=None):
"""
- Loads the given template_name and renders it with the given dictionary as
- context. The template_name may be a string to load a single template using
- get_template, or it may be a tuple to use select_template to find one of
- the templates in the list. Returns a string.
+ Common setup code for render_to_string and render_to_iter.
"""
- dictionary = dictionary or {}
+ if dictionary is None:
+ dictionary = {}
if isinstance(template_name, (list, tuple)):
t = select_template(template_name)
else:
@@ -103,7 +101,28 @@ def render_to_string(template_name, dictionary=None, context_instance=None):
context_instance.update(dictionary)
else:
context_instance = Context(dictionary)
- return t.render(context_instance)
+ return t, context_instance
+
+def render_to_string(template_name, dictionary=None, context_instance=None):
+ """
+ Loads the given template_name and renders it with the given dictionary as
+ context. The template_name may be a string to load a single template using
+ get_template, or it may be a tuple to use select_template to find one of
+ the templates in the list. Returns a string.
+ """
+ t, c = _render_setup(template_name, dictionary=dictionary, context_instance=context_instance)
+ return t.render(c)
+
+def render_to_iter(template_name, dictionary=None, context_instance=None):
+ """
+ Loads the given template_name and renders it with the given dictionary as
+ context. The template_name may be a string to load a single template using
+ get_template, or it may be a tuple to use select_template to find one of
+ the templates in the list. Returns a string.
+ """
+ t, c = _render_setup(template_name, dictionary=dictionary, context_instance=context_instance)
+ return t.iter_render(c)
+
def select_template(template_name_list):
"Given a list of template names, returns the first that can be loaded."
diff --git a/django/template/loader_tags.py b/django/template/loader_tags.py
index e329b1bb36..d12d0b55ad 100644
--- a/django/template/loader_tags.py
+++ b/django/template/loader_tags.py
@@ -15,14 +15,14 @@ class BlockNode(Node):
def __repr__(self):
return "<Block Node: %s. Contents: %r>" % (self.name, self.nodelist)
- def render(self, context):
+ def iter_render(self, context):
context.push()
# Save context in case of block.super().
self.context = context
context['block'] = self
- result = self.nodelist.render(context)
+ for chunk in self.nodelist.iter_render(context):
+ yield chunk
context.pop()
- return result
def super(self):
if self.parent:
@@ -59,7 +59,7 @@ class ExtendsNode(Node):
else:
return get_template_from_string(source, origin, parent)
- def render(self, context):
+ def iter_render(self, context):
compiled_parent = self.get_parent(context)
parent_is_child = isinstance(compiled_parent.nodelist[0], ExtendsNode)
parent_blocks = dict([(n.name, n) for n in compiled_parent.nodelist.get_nodes_by_type(BlockNode)])
@@ -79,7 +79,7 @@ class ExtendsNode(Node):
parent_block.parent = block_node.parent
parent_block.add_parent(parent_block.nodelist)
parent_block.nodelist = block_node.nodelist
- return compiled_parent.render(context)
+ return compiled_parent.iter_render(context)
class ConstantIncludeNode(Node):
def __init__(self, template_path):
@@ -91,27 +91,26 @@ class ConstantIncludeNode(Node):
raise
self.template = None
- def render(self, context):
+ def iter_render(self, context):
if self.template:
- return self.template.render(context)
- else:
- return ''
+ return self.template.iter_render(context)
+ return ()
class IncludeNode(Node):
def __init__(self, template_name):
self.template_name = template_name
- def render(self, context):
+ def iter_render(self, context):
try:
template_name = resolve_variable(self.template_name, context)
t = get_template(template_name)
- return t.render(context)
+ return t.iter_render(context)
except TemplateSyntaxError, e:
if settings.TEMPLATE_DEBUG:
raise
- return ''
+ return ()
except:
- return '' # Fail silently for invalid included templates.
+ return () # Fail silently for invalid included templates.
def do_block(parser, token):
"""
@@ -129,7 +128,7 @@ def do_block(parser, token):
parser.__loaded_blocks.append(block_name)
except AttributeError: # parser.__loaded_blocks isn't a list yet
parser.__loaded_blocks = [block_name]
- nodelist = parser.parse(('endblock',))
+ nodelist = parser.parse(('endblock', 'endblock %s' % block_name))
parser.delete_first_token()
return BlockNode(block_name, nodelist)