Fixed #13956 -- Enabled `*args` and `**kwargs` support for `simple_tag`, `inclusion_tag` and `assignment_tag`. Many thanks to Stephen Burrows for the report and initial patch, to Gregor Müllegger for the initial tests, to SamBull for the suggestions, and to Jannis Leidel for the review and PEP8 cleanup.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@16908 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Julien Phalip 2011-09-27 12:15:15 +00:00
parent 29b8e34dfb
commit 8137027fd7
6 changed files with 848 additions and 265 deletions

View File

@ -3,13 +3,16 @@ from functools import partial
from inspect import getargspec from inspect import getargspec
from django.conf import settings from django.conf import settings
from django.template.context import Context, RequestContext, ContextPopException from django.template.context import (Context, RequestContext,
ContextPopException)
from django.utils.importlib import import_module from django.utils.importlib import import_module
from django.utils.itercompat import is_iterable from django.utils.itercompat import is_iterable
from django.utils.text import smart_split, unescape_string_literal, get_text_list 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.encoding import smart_unicode, force_unicode, smart_str
from django.utils.translation import ugettext_lazy from django.utils.translation import ugettext_lazy
from django.utils.safestring import SafeData, EscapeData, mark_safe, mark_for_escaping from django.utils.safestring import (SafeData, EscapeData, mark_safe,
mark_for_escaping)
from django.utils.formats import localize from django.utils.formats import localize
from django.utils.html import escape from django.utils.html import escape
from django.utils.module_loading import module_has_submodule from django.utils.module_loading import module_has_submodule
@ -19,6 +22,12 @@ TOKEN_TEXT = 0
TOKEN_VAR = 1 TOKEN_VAR = 1
TOKEN_BLOCK = 2 TOKEN_BLOCK = 2
TOKEN_COMMENT = 3 TOKEN_COMMENT = 3
TOKEN_MAPPING = {
TOKEN_TEXT: 'Text',
TOKEN_VAR: 'Var',
TOKEN_BLOCK: 'Block',
TOKEN_COMMENT: 'Comment',
}
# template syntax constants # template syntax constants
FILTER_SEPARATOR = '|' FILTER_SEPARATOR = '|'
@ -34,16 +43,19 @@ TRANSLATOR_COMMENT_MARK = 'Translators'
SINGLE_BRACE_START = '{' SINGLE_BRACE_START = '{'
SINGLE_BRACE_END = '}' SINGLE_BRACE_END = '}'
ALLOWED_VARIABLE_CHARS = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_.' ALLOWED_VARIABLE_CHARS = ('abcdefghijklmnopqrstuvwxyz'
'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_.')
# what to report as the origin for templates that come from non-loader sources # what to report as the origin for templates that come from non-loader sources
# (e.g. strings) # (e.g. strings)
UNKNOWN_SOURCE = '<unknown source>' UNKNOWN_SOURCE = '<unknown source>'
# match a variable or block tag and capture the entire tag, including start/end delimiters # match a variable or block tag and capture the entire tag, including start/end
tag_re = re.compile('(%s.*?%s|%s.*?%s|%s.*?%s)' % (re.escape(BLOCK_TAG_START), re.escape(BLOCK_TAG_END), # delimiters
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(VARIABLE_TAG_START), re.escape(VARIABLE_TAG_END),
re.escape(COMMENT_TAG_START), re.escape(COMMENT_TAG_END))) re.escape(COMMENT_TAG_START), re.escape(COMMENT_TAG_END))))
# global dictionary of libraries that have been loaded using get_library # global dictionary of libraries that have been loaded using get_library
libraries = {} libraries = {}
@ -73,7 +85,8 @@ class VariableDoesNotExist(Exception):
return unicode(self).encode('utf-8') return unicode(self).encode('utf-8')
def __unicode__(self): def __unicode__(self):
return self.msg % tuple([force_unicode(p, errors='replace') for p in self.params]) return self.msg % tuple([force_unicode(p, errors='replace')
for p in self.params])
class InvalidTemplateLibrary(Exception): class InvalidTemplateLibrary(Exception):
pass pass
@ -97,11 +110,13 @@ class StringOrigin(Origin):
return self.source return self.source
class Template(object): class Template(object):
def __init__(self, template_string, origin=None, name='<Unknown Template>'): def __init__(self, template_string, origin=None,
name='<Unknown Template>'):
try: try:
template_string = smart_unicode(template_string) template_string = smart_unicode(template_string)
except UnicodeDecodeError: except UnicodeDecodeError:
raise TemplateEncodingError("Templates can only be constructed from unicode or UTF-8 strings.") raise TemplateEncodingError("Templates can only be constructed "
"from unicode or UTF-8 strings.")
if settings.TEMPLATE_DEBUG and origin is None: if settings.TEMPLATE_DEBUG and origin is None:
origin = StringOrigin(template_string) origin = StringOrigin(template_string)
self.nodelist = compile_string(template_string, origin) self.nodelist = compile_string(template_string, origin)
@ -136,14 +151,15 @@ def compile_string(template_string, origin):
class Token(object): class Token(object):
def __init__(self, token_type, contents): def __init__(self, token_type, contents):
# token_type must be TOKEN_TEXT, TOKEN_VAR, TOKEN_BLOCK or TOKEN_COMMENT. # token_type must be TOKEN_TEXT, TOKEN_VAR, TOKEN_BLOCK or
# TOKEN_COMMENT.
self.token_type, self.contents = token_type, contents self.token_type, self.contents = token_type, contents
self.lineno = None self.lineno = None
def __str__(self): def __str__(self):
return '<%s token: "%s...">' % \ token_name = TOKEN_MAPPING[self.token_type]
({TOKEN_TEXT: 'Text', TOKEN_VAR: 'Var', TOKEN_BLOCK: 'Block', TOKEN_COMMENT: 'Comment'}[self.token_type], return ('<%s token: "%s...">' %
self.contents[:20].replace('\n', '')) (token_name, self.contents[:20].replace('\n', '')))
def split_contents(self): def split_contents(self):
split = [] split = []
@ -167,7 +183,9 @@ class Lexer(object):
self.lineno = 1 self.lineno = 1
def tokenize(self): def tokenize(self):
"Return a list of tokens from a given template_string." """
Return a list of tokens from a given template_string.
"""
in_tag = False in_tag = False
result = [] result = []
for bit in tag_re.split(self.template_string): for bit in tag_re.split(self.template_string):
@ -184,13 +202,21 @@ class Lexer(object):
""" """
if in_tag: if in_tag:
if token_string.startswith(VARIABLE_TAG_START): if token_string.startswith(VARIABLE_TAG_START):
token = Token(TOKEN_VAR, token_string[len(VARIABLE_TAG_START):-len(VARIABLE_TAG_END)].strip()) token = Token(TOKEN_VAR,
token_string[
len(VARIABLE_TAG_START):-len(VARIABLE_TAG_END)
].strip())
elif token_string.startswith(BLOCK_TAG_START): elif token_string.startswith(BLOCK_TAG_START):
token = Token(TOKEN_BLOCK, token_string[len(BLOCK_TAG_START):-len(BLOCK_TAG_END)].strip()) token = Token(TOKEN_BLOCK,
token_string[
len(BLOCK_TAG_START):-len(BLOCK_TAG_END)
].strip())
elif token_string.startswith(COMMENT_TAG_START): elif token_string.startswith(COMMENT_TAG_START):
content = '' content = ''
if token_string.find(TRANSLATOR_COMMENT_MARK): if token_string.find(TRANSLATOR_COMMENT_MARK):
content = token_string[len(COMMENT_TAG_START):-len(COMMENT_TAG_END)].strip() content = token_string[
len(COMMENT_TAG_START):-len(COMMENT_TAG_END)
].strip()
token = Token(TOKEN_COMMENT, content) token = Token(TOKEN_COMMENT, content)
else: else:
token = Token(TOKEN_TEXT, token_string) token = Token(TOKEN_TEXT, token_string)
@ -207,7 +233,8 @@ class Parser(object):
self.add_library(lib) self.add_library(lib)
def parse(self, parse_until=None): def parse(self, parse_until=None):
if parse_until is None: parse_until = [] if parse_until is None:
parse_until = []
nodelist = self.create_nodelist() nodelist = self.create_nodelist()
while self.tokens: while self.tokens:
token = self.next_token() token = self.next_token()
@ -221,14 +248,16 @@ class Parser(object):
self.extend_nodelist(nodelist, var_node, token) self.extend_nodelist(nodelist, var_node, token)
elif token.token_type == TOKEN_BLOCK: elif token.token_type == TOKEN_BLOCK:
if token.contents in parse_until: if token.contents in parse_until:
# put token back on token list so calling code knows why it terminated # put token back on token list so calling
# code knows why it terminated
self.prepend_token(token) self.prepend_token(token)
return nodelist return nodelist
try: try:
command = token.contents.split()[0] command = token.contents.split()[0]
except IndexError: except IndexError:
self.empty_block_tag(token) self.empty_block_tag(token)
# execute callback function for this tag and append resulting node # execute callback function for this tag and append
# resulting node
self.enter_command(command, token) self.enter_command(command, token)
try: try:
compile_func = self.tags[command] compile_func = self.tags[command]
@ -264,7 +293,8 @@ class Parser(object):
if nodelist.contains_nontext: if nodelist.contains_nontext:
raise AttributeError raise AttributeError
except AttributeError: except AttributeError:
raise TemplateSyntaxError("%r must be the first tag in the template." % node) raise TemplateSyntaxError("%r must be the first tag "
"in the template." % node)
if isinstance(nodelist, NodeList) and not isinstance(node, TextNode): if isinstance(nodelist, NodeList) and not isinstance(node, TextNode):
nodelist.contains_nontext = True nodelist.contains_nontext = True
nodelist.append(node) nodelist.append(node)
@ -286,7 +316,8 @@ class Parser(object):
def invalid_block_tag(self, token, command, parse_until=None): def invalid_block_tag(self, token, command, parse_until=None):
if parse_until: 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', expected %s" %
(command, get_text_list(["'%s'" % p for p in parse_until])))
raise self.error(token, "Invalid block tag: '%s'" % command) raise self.error(token, "Invalid block tag: '%s'" % command)
def unclosed_block_tag(self, parse_until): def unclosed_block_tag(self, parse_until):
@ -309,7 +340,9 @@ class Parser(object):
self.filters.update(lib.filters) self.filters.update(lib.filters)
def compile_filter(self, token): def compile_filter(self, token):
"Convenient wrapper for FilterExpression" """
Convenient wrapper for FilterExpression
"""
return FilterExpression(token, self) return FilterExpression(token, self)
def find_filter(self, filter_name): def find_filter(self, filter_name):
@ -320,8 +353,9 @@ class Parser(object):
class TokenParser(object): class TokenParser(object):
""" """
Subclass this and implement the top() method to parse a template line. When Subclass this and implement the top() method to parse a template line.
instantiating the parser, pass in the line from the Django template parser. When instantiating the parser, pass in the line from the Django template
parser.
The parser's "tagname" instance-variable stores the name of the tag that The parser's "tagname" instance-variable stores the name of the tag that
the filter was called with. the filter was called with.
@ -333,25 +367,35 @@ class TokenParser(object):
self.tagname = self.tag() self.tagname = self.tag()
def top(self): def top(self):
"Overload this method to do the actual parsing and return the result." """
Overload this method to do the actual parsing and return the result.
"""
raise NotImplementedError() raise NotImplementedError()
def more(self): def more(self):
"Returns True if there is more stuff in the tag." """
Returns True if there is more stuff in the tag.
"""
return self.pointer < len(self.subject) return self.pointer < len(self.subject)
def back(self): def back(self):
"Undoes the last microparser. Use this for lookahead and backtracking." """
Undoes the last microparser. Use this for lookahead and backtracking.
"""
if not len(self.backout): if not len(self.backout):
raise TemplateSyntaxError("back called without some previous parsing") raise TemplateSyntaxError("back called without some previous "
"parsing")
self.pointer = self.backout.pop() self.pointer = self.backout.pop()
def tag(self): def tag(self):
"A microparser that just returns the next tag from the line." """
A microparser that just returns the next tag from the line.
"""
subject = self.subject subject = self.subject
i = self.pointer i = self.pointer
if i >= len(subject): if i >= len(subject):
raise TemplateSyntaxError("expected another tag, found end of string: %s" % subject) raise TemplateSyntaxError("expected another tag, found "
"end of string: %s" % subject)
p = i p = i
while i < len(subject) and subject[i] not in (' ', '\t'): while i < len(subject) and subject[i] not in (' ', '\t'):
i += 1 i += 1
@ -363,12 +407,18 @@ class TokenParser(object):
return s return s
def value(self): def value(self):
"A microparser that parses for a value: some string constant or variable name." """
A microparser that parses for a value: some string constant or
variable name.
"""
subject = self.subject subject = self.subject
i = self.pointer i = self.pointer
def next_space_index(subject, i): def next_space_index(subject, i):
"Increment pointer until a real space (i.e. a space not within quotes) is encountered" """
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'): while i < len(subject) and subject[i] not in (' ', '\t'):
if subject[i] in ('"', "'"): if subject[i] in ('"', "'"):
c = subject[i] c = subject[i]
@ -376,22 +426,29 @@ class TokenParser(object):
while i < len(subject) and subject[i] != c: while i < len(subject) and subject[i] != c:
i += 1 i += 1
if i >= len(subject): if i >= len(subject):
raise TemplateSyntaxError("Searching for value. Unexpected end of string in column %d: %s" % (i, subject)) raise TemplateSyntaxError("Searching for value. "
"Unexpected end of string in column %d: %s" %
(i, subject))
i += 1 i += 1
return i return i
if i >= len(subject): if i >= len(subject):
raise TemplateSyntaxError("Searching for value. Expected another value but found end of string: %s" % subject) raise TemplateSyntaxError("Searching for value. Expected another "
"value but found end of string: %s" %
subject)
if subject[i] in ('"', "'"): if subject[i] in ('"', "'"):
p = i p = i
i += 1 i += 1
while i < len(subject) and subject[i] != subject[p]: while i < len(subject) and subject[i] != subject[p]:
i += 1 i += 1
if i >= len(subject): if i >= len(subject):
raise TemplateSyntaxError("Searching for value. Unexpected end of string in column %d: %s" % (i, subject)) raise TemplateSyntaxError("Searching for value. Unexpected "
"end of string in column %d: %s" %
(i, subject))
i += 1 i += 1
# Continue parsing until next "real" space, so that filters are also included # Continue parsing until next "real" space,
# so that filters are also included
i = next_space_index(subject, i) i = next_space_index(subject, i)
res = subject[p:i] res = subject[p:i]
@ -448,10 +505,11 @@ filter_raw_string = r"""
filter_re = re.compile(filter_raw_string, re.UNICODE | re.VERBOSE) filter_re = re.compile(filter_raw_string, re.UNICODE | re.VERBOSE)
class FilterExpression(object): class FilterExpression(object):
r""" """
Parses a variable token and its optional filters (all as a single string), Parses a variable token and its optional filters (all as a single string),
and return a list of tuples of the filter name and arguments. and return a list of tuples of the filter name and arguments.
Sample: Sample::
>>> token = 'variable|default:"Default value"|date:"Y-m-d"' >>> token = 'variable|default:"Default value"|date:"Y-m-d"'
>>> p = Parser('') >>> p = Parser('')
>>> fe = FilterExpression(token, p) >>> fe = FilterExpression(token, p)
@ -472,8 +530,10 @@ class FilterExpression(object):
for match in matches: for match in matches:
start = match.start() start = match.start()
if upto != start: if upto != start:
raise TemplateSyntaxError("Could not parse some characters: %s|%s|%s" % \ raise TemplateSyntaxError("Could not parse some characters: "
(token[:upto], token[upto:start], token[start:])) "%s|%s|%s" %
(token[:upto], token[upto:start],
token[start:]))
if var_obj is None: if var_obj is None:
var, constant = match.group("var", "constant") var, constant = match.group("var", "constant")
if constant: if constant:
@ -482,7 +542,8 @@ class FilterExpression(object):
except VariableDoesNotExist: except VariableDoesNotExist:
var_obj = None var_obj = None
elif var is None: elif var is None:
raise TemplateSyntaxError("Could not find variable at start of %s." % token) raise TemplateSyntaxError("Could not find variable at "
"start of %s." % token)
else: else:
var_obj = Variable(var) var_obj = Variable(var)
else: else:
@ -498,7 +559,8 @@ class FilterExpression(object):
filters.append((filter_func, args)) filters.append((filter_func, args))
upto = match.end() upto = match.end()
if upto != len(token): if upto != len(token):
raise TemplateSyntaxError("Could not parse the remainder: '%s' from '%s'" % (token[upto:], token)) raise TemplateSyntaxError("Could not parse the remainder: '%s' "
"from '%s'" % (token[upto:], token))
self.filters = filters self.filters = filters
self.var = var_obj self.var = var_obj
@ -559,7 +621,8 @@ class FilterExpression(object):
provided.pop(0) provided.pop(0)
except IndexError: except IndexError:
# Not enough # Not enough
raise TemplateSyntaxError("%s requires %d arguments, %d provided" % (name, len(nondefs), plen)) raise TemplateSyntaxError("%s requires %d arguments, %d provided" %
(name, len(nondefs), plen))
# Defaults can be overridden. # Defaults can be overridden.
defaults = defaults and list(defaults) or [] defaults = defaults and list(defaults) or []
@ -568,7 +631,8 @@ class FilterExpression(object):
defaults.pop(0) defaults.pop(0)
except IndexError: except IndexError:
# Too many. # Too many.
raise TemplateSyntaxError("%s requires %d arguments, %d provided" % (name, len(nondefs), plen)) raise TemplateSyntaxError("%s requires %d arguments, %d provided" %
(name, len(nondefs), plen))
return True return True
args_check = staticmethod(args_check) args_check = staticmethod(args_check)
@ -586,9 +650,9 @@ def resolve_variable(path, context):
return Variable(path).resolve(context) return Variable(path).resolve(context)
class Variable(object): class Variable(object):
r""" """
A template variable, resolvable against a given context. The variable may be A template variable, resolvable against a given context. The variable may
a hard-coded string (if it begins and ends with single or double quote be a hard-coded string (if it begins and ends with single or double quote
marks):: marks)::
>>> c = {'article': {'section':u'News'}} >>> c = {'article': {'section':u'News'}}
@ -642,7 +706,9 @@ class Variable(object):
# Otherwise we'll set self.lookups so that resolve() knows we're # Otherwise we'll set self.lookups so that resolve() knows we're
# dealing with a bonafide variable # dealing with a bonafide variable
if var.find(VARIABLE_ATTRIBUTE_SEPARATOR + '_') > -1 or var[0] == '_': if var.find(VARIABLE_ATTRIBUTE_SEPARATOR + '_') > -1 or var[0] == '_':
raise TemplateSyntaxError("Variables and attributes may not begin with underscores: '%s'" % var) raise TemplateSyntaxError("Variables and attributes may "
"not begin with underscores: '%s'" %
var)
self.lookups = tuple(var.split(VARIABLE_ATTRIBUTE_SEPARATOR)) self.lookups = tuple(var.split(VARIABLE_ATTRIBUTE_SEPARATOR))
def resolve(self, context): def resolve(self, context):
@ -686,9 +752,10 @@ class Variable(object):
except (IndexError, # list index out of range except (IndexError, # list index out of range
ValueError, # invalid literal for int() ValueError, # invalid literal for int()
KeyError, # current is a dict without `int(bit)` key KeyError, # current is a dict without `int(bit)` key
TypeError, # unsubscriptable object TypeError): # unsubscriptable object
): raise VariableDoesNotExist("Failed lookup for key "
raise VariableDoesNotExist("Failed lookup for key [%s] in %r", (bit, current)) # missing attribute "[%s] in %r",
(bit, current)) # missing attribute
if callable(current): if callable(current):
if getattr(current, 'do_not_call_in_templates', False): if getattr(current, 'do_not_call_in_templates', False):
pass pass
@ -716,14 +783,19 @@ class Node(object):
child_nodelists = ('nodelist',) child_nodelists = ('nodelist',)
def render(self, context): def render(self, context):
"Return the node rendered as a string" """
Return the node rendered as a string.
"""
pass pass
def __iter__(self): def __iter__(self):
yield self yield self
def get_nodes_by_type(self, nodetype): def get_nodes_by_type(self, nodetype):
"Return a list of all nodes (within this node and its nodelist) of the given type" """
Return a list of all nodes (within this node and its nodelist)
of the given type
"""
nodes = [] nodes = []
if isinstance(self, nodetype): if isinstance(self, nodetype):
nodes.append(self) nodes.append(self)
@ -776,7 +848,8 @@ def _render_value_in_context(value, context):
""" """
value = localize(value, use_l10n=context.use_l10n) value = localize(value, use_l10n=context.use_l10n)
value = force_unicode(value) value = force_unicode(value)
if (context.autoescape and not isinstance(value, SafeData)) or isinstance(value, EscapeData): if ((context.autoescape and not isinstance(value, SafeData)) or
isinstance(value, EscapeData)):
return escape(value) return escape(value)
else: else:
return value return value
@ -793,23 +866,159 @@ class VariableNode(Node):
output = self.filter_expression.resolve(context) output = self.filter_expression.resolve(context)
except UnicodeDecodeError: except UnicodeDecodeError:
# Unicode conversion can fail sometimes for reasons out of our # Unicode conversion can fail sometimes for reasons out of our
# control (e.g. exception rendering). In that case, we fail quietly. # control (e.g. exception rendering). In that case, we fail
# quietly.
return '' return ''
return _render_value_in_context(output, context) return _render_value_in_context(output, context)
def generic_tag_compiler(params, defaults, name, node_class, parser, token): # Regex for token keyword arguments
"Returns a template.Node subclass." kwarg_re = re.compile(r"(?:(\w+)=)?(.+)")
bits = token.split_contents()[1:]
bmax = len(params) def token_kwargs(bits, parser, support_legacy=False):
def_len = defaults and len(defaults) or 0 """
bmin = bmax - def_len A utility method for parsing token keyword arguments.
if(len(bits) < bmin or len(bits) > bmax):
if bmin == bmax: :param bits: A list containing remainder of the token (split by spaces)
message = "%s takes %s arguments" % (name, bmin) that is to be checked for arguments. Valid arguments will be removed
from this list.
:param support_legacy: If set to true ``True``, the legacy format
``1 as foo`` will be accepted. Otherwise, only the standard ``foo=1``
format is allowed.
:returns: A dictionary of the arguments retrieved from the ``bits`` token
list.
There is no requirement for all remaining token ``bits`` to be keyword
arguments, so the dictionary will be returned as soon as an invalid
argument format is reached.
"""
if not bits:
return {}
match = kwarg_re.match(bits[0])
kwarg_format = match and match.group(1)
if not kwarg_format:
if not support_legacy:
return {}
if len(bits) < 3 or bits[1] != 'as':
return {}
kwargs = {}
while bits:
if kwarg_format:
match = kwarg_re.match(bits[0])
if not match or not match.group(1):
return kwargs
key, value = match.groups()
del bits[:1]
else: else:
message = "%s takes between %s and %s arguments" % (name, bmin, bmax) if len(bits) < 3 or bits[1] != 'as':
raise TemplateSyntaxError(message) return kwargs
return node_class(bits) key, value = bits[2], bits[0]
del bits[:3]
kwargs[key] = parser.compile_filter(value)
if bits and not kwarg_format:
if bits[0] != 'and':
return kwargs
del bits[:1]
return kwargs
def parse_bits(parser, bits, params, varargs, varkw, defaults,
takes_context, name):
"""
Parses bits for template tag helpers (simple_tag, include_tag and
assignment_tag), in particular by detecting syntax errors and by
extracting positional and keyword arguments.
"""
if takes_context:
if params[0] == 'context':
params = params[1:]
else:
raise TemplateSyntaxError(
"'%s' is decorated with takes_context=True so it must "
"have a first argument of 'context'" % name)
args = []
kwargs = {}
unhandled_params = list(params)
for bit in bits:
# First we try to extract a potential kwarg from the bit
kwarg = token_kwargs([bit], parser)
if kwarg:
# The kwarg was successfully extracted
param, value = kwarg.items()[0]
if param not in params and varkw is None:
# An unexpected keyword argument was supplied
raise TemplateSyntaxError(
"'%s' received unexpected keyword argument '%s'" %
(name, param))
elif param in kwargs:
# The keyword argument has already been supplied once
raise TemplateSyntaxError(
"'%s' received multiple values for keyword argument '%s'" %
(name, param))
else:
# All good, record the keyword argument
kwargs[str(param)] = value
if param in unhandled_params:
# If using the keyword syntax for a positional arg, then
# consume it.
unhandled_params.remove(param)
else:
if kwargs:
raise TemplateSyntaxError(
"'%s' received some positional argument(s) after some "
"keyword argument(s)" % name)
else:
# Record the positional argument
args.append(parser.compile_filter(bit))
try:
# Consume from the list of expected positional arguments
unhandled_params.pop(0)
except IndexError:
if varargs is None:
raise TemplateSyntaxError(
"'%s' received too many positional arguments" %
name)
if defaults is not None:
# Consider the last n params handled, where n is the
# number of defaults.
unhandled_params = unhandled_params[:-len(defaults)]
if unhandled_params:
# Some positional arguments were not supplied
raise TemplateSyntaxError(
u"'%s' did not receive value(s) for the argument(s): %s" %
(name, u", ".join([u"'%s'" % p for p in unhandled_params])))
return args, kwargs
def generic_tag_compiler(parser, token, params, varargs, varkw, defaults,
name, takes_context, node_class):
"""
Returns a template.Node subclass.
"""
bits = token.split_contents()[1:]
args, kwargs = parse_bits(parser, bits, params, varargs, varkw,
defaults, takes_context, name)
return node_class(takes_context, args, kwargs)
class TagHelperNode(Node):
"""
Base class for tag helper nodes such as SimpleNode, InclusionNode and
AssignmentNode. Manages the positional and keyword arguments to be passed
to the decorated function.
"""
def __init__(self, takes_context, args, kwargs):
self.takes_context = takes_context
self.args = args
self.kwargs = kwargs
def get_resolved_arguments(self, context):
resolved_args = [var.resolve(context) for var in self.args]
if self.takes_context:
resolved_args = [context] + resolved_args
resolved_kwargs = dict((k, v.resolve(context))
for k, v in self.kwargs.items())
return resolved_args, resolved_kwargs
class Library(object): class Library(object):
def __init__(self): def __init__(self):
@ -817,10 +1026,10 @@ class Library(object):
self.tags = {} self.tags = {}
def tag(self, name=None, compile_function=None): def tag(self, name=None, compile_function=None):
if name == None and compile_function == None: if name is None and compile_function is None:
# @register.tag() # @register.tag()
return self.tag_function return self.tag_function
elif name != None and compile_function == None: elif name is not None and compile_function is None:
if callable(name): if callable(name):
# @register.tag # @register.tag
return self.tag_function(name) return self.tag_function(name)
@ -829,22 +1038,23 @@ class Library(object):
def dec(func): def dec(func):
return self.tag(name, func) return self.tag(name, func)
return dec return dec
elif name != None and compile_function != None: elif name is not None and compile_function is not None:
# register.tag('somename', somefunc) # register.tag('somename', somefunc)
self.tags[name] = compile_function self.tags[name] = compile_function
return compile_function return compile_function
else: else:
raise InvalidTemplateLibrary("Unsupported arguments to Library.tag: (%r, %r)", (name, compile_function)) raise InvalidTemplateLibrary("Unsupported arguments to "
"Library.tag: (%r, %r)", (name, compile_function))
def tag_function(self, func): def tag_function(self, func):
self.tags[getattr(func, "_decorated_function", func).__name__] = func self.tags[getattr(func, "_decorated_function", func).__name__] = func
return func return func
def filter(self, name=None, filter_func=None): def filter(self, name=None, filter_func=None):
if name == None and filter_func == None: if name is None and filter_func is None:
# @register.filter() # @register.filter()
return self.filter_function return self.filter_function
elif filter_func == None: elif filter_func is None:
if callable(name): if callable(name):
# @register.filter # @register.filter
return self.filter_function(name) return self.filter_function(name)
@ -853,12 +1063,13 @@ class Library(object):
def dec(func): def dec(func):
return self.filter(name, func) return self.filter(name, func)
return dec return dec
elif name != None and filter_func != None: elif name is not None and filter_func is not None:
# register.filter('somename', somefunc) # register.filter('somename', somefunc)
self.filters[name] = filter_func self.filters[name] = filter_func
return filter_func return filter_func
else: else:
raise InvalidTemplateLibrary("Unsupported arguments to Library.filter: (%r, %r)", (name, filter_func)) raise InvalidTemplateLibrary("Unsupported arguments to "
"Library.filter: (%r, %r)", (name, filter_func))
def filter_function(self, func): def filter_function(self, func):
self.filters[getattr(func, "_decorated_function", func).__name__] = func self.filters[getattr(func, "_decorated_function", func).__name__] = func
@ -866,27 +1077,20 @@ class Library(object):
def simple_tag(self, func=None, takes_context=None, name=None): def simple_tag(self, func=None, takes_context=None, name=None):
def dec(func): def dec(func):
params, xx, xxx, defaults = getargspec(func) params, varargs, varkw, defaults = getargspec(func)
if takes_context:
if params[0] == 'context':
params = params[1:]
else:
raise TemplateSyntaxError("Any tag function decorated with takes_context=True must have a first argument of 'context'")
class SimpleNode(Node): class SimpleNode(TagHelperNode):
def __init__(self, vars_to_resolve):
self.vars_to_resolve = map(Variable, vars_to_resolve)
def render(self, context): def render(self, context):
resolved_vars = [var.resolve(context) for var in self.vars_to_resolve] resolved_args, resolved_kwargs = self.get_resolved_arguments(context)
if takes_context: return func(*resolved_args, **resolved_kwargs)
func_args = [context] + resolved_vars
else:
func_args = resolved_vars
return func(*func_args)
function_name = name or getattr(func, '_decorated_function', func).__name__ function_name = (name or
compile_func = partial(generic_tag_compiler, params, defaults, function_name, SimpleNode) getattr(func, '_decorated_function', func).__name__)
compile_func = partial(generic_tag_compiler,
params=params, varargs=varargs, varkw=varkw,
defaults=defaults, name=function_name,
takes_context=takes_context, node_class=SimpleNode)
compile_func.__doc__ = func.__doc__ compile_func.__doc__ = func.__doc__
self.tag(function_name, compile_func) self.tag(function_name, compile_func)
return func return func
@ -902,52 +1106,33 @@ class Library(object):
def assignment_tag(self, func=None, takes_context=None, name=None): def assignment_tag(self, func=None, takes_context=None, name=None):
def dec(func): def dec(func):
params, xx, xxx, defaults = getargspec(func) params, varargs, varkw, defaults = getargspec(func)
if takes_context:
if params[0] == 'context':
params = params[1:]
else:
raise TemplateSyntaxError("Any tag function decorated with takes_context=True must have a first argument of 'context'")
class AssignmentNode(Node): class AssignmentNode(TagHelperNode):
def __init__(self, params_vars, target_var): def __init__(self, takes_context, args, kwargs, target_var):
self.params_vars = map(Variable, params_vars) super(AssignmentNode, self).__init__(takes_context, args, kwargs)
self.target_var = target_var self.target_var = target_var
def render(self, context): def render(self, context):
resolved_vars = [var.resolve(context) for var in self.params_vars] resolved_args, resolved_kwargs = self.get_resolved_arguments(context)
if takes_context: context[self.target_var] = func(*resolved_args, **resolved_kwargs)
func_args = [context] + resolved_vars
else:
func_args = resolved_vars
context[self.target_var] = func(*func_args)
return '' return ''
function_name = (name or
getattr(func, '_decorated_function', func).__name__)
def compile_func(parser, token): def compile_func(parser, token):
bits = token.split_contents() bits = token.split_contents()[1:]
tag_name = bits[0] if len(bits) < 2 or bits[-2] != 'as':
bits = bits[1:]
params_max = len(params)
defaults_length = defaults and len(defaults) or 0
params_min = params_max - defaults_length
if (len(bits) < 2 or bits[-2] != 'as'):
raise TemplateSyntaxError( raise TemplateSyntaxError(
"'%s' tag takes at least 2 arguments and the " "'%s' tag takes at least 2 arguments and the "
"second last argument must be 'as'" % tag_name) "second last argument must be 'as'" % function_name)
params_vars = bits[:-2]
target_var = bits[-1] target_var = bits[-1]
if (len(params_vars) < params_min or bits = bits[:-2]
len(params_vars) > params_max): args, kwargs = parse_bits(parser, bits, params,
if params_min == params_max: varargs, varkw, defaults, takes_context, function_name)
raise TemplateSyntaxError( return AssignmentNode(takes_context, args, kwargs, target_var)
"%s takes %s arguments" % (tag_name, params_min))
else:
raise TemplateSyntaxError(
"%s takes between %s and %s arguments"
% (tag_name, params_min, params_max))
return AssignmentNode(params_vars, target_var)
function_name = name or getattr(func, '_decorated_function', func).__name__
compile_func.__doc__ = func.__doc__ compile_func.__doc__ = func.__doc__
self.tag(function_name, compile_func) self.tag(function_name, compile_func)
return func return func
@ -963,25 +1148,13 @@ class Library(object):
def inclusion_tag(self, file_name, context_class=Context, takes_context=False, name=None): def inclusion_tag(self, file_name, context_class=Context, takes_context=False, name=None):
def dec(func): def dec(func):
params, xx, xxx, defaults = getargspec(func) params, varargs, varkw, defaults = getargspec(func)
if takes_context:
if params[0] == 'context':
params = params[1:]
else:
raise TemplateSyntaxError("Any tag function decorated with takes_context=True must have a first argument of 'context'")
class InclusionNode(Node): class InclusionNode(TagHelperNode):
def __init__(self, vars_to_resolve):
self.vars_to_resolve = map(Variable, vars_to_resolve)
def render(self, context): def render(self, context):
resolved_vars = [var.resolve(context) for var in self.vars_to_resolve] resolved_args, resolved_kwargs = self.get_resolved_arguments(context)
if takes_context: _dict = func(*resolved_args, **resolved_kwargs)
args = [context] + resolved_vars
else:
args = resolved_vars
dict = func(*args)
if not getattr(self, 'nodelist', False): if not getattr(self, 'nodelist', False):
from django.template.loader import get_template, select_template from django.template.loader import get_template, select_template
@ -992,28 +1165,34 @@ class Library(object):
else: else:
t = get_template(file_name) t = get_template(file_name)
self.nodelist = t.nodelist self.nodelist = t.nodelist
new_context = context_class(dict, **{ new_context = context_class(_dict, **{
'autoescape': context.autoescape, 'autoescape': context.autoescape,
'current_app': context.current_app, 'current_app': context.current_app,
'use_l10n': context.use_l10n, 'use_l10n': context.use_l10n,
}) })
# Copy across the CSRF token, if present, because inclusion # Copy across the CSRF token, if present, because
# tags are often used for forms, and we need instructions # inclusion tags are often used for forms, and we need
# for using CSRF protection to be as simple as possible. # instructions for using CSRF protection to be as simple
# as possible.
csrf_token = context.get('csrf_token', None) csrf_token = context.get('csrf_token', None)
if csrf_token is not None: if csrf_token is not None:
new_context['csrf_token'] = csrf_token new_context['csrf_token'] = csrf_token
return self.nodelist.render(new_context) return self.nodelist.render(new_context)
function_name = name or getattr(func, '_decorated_function', func).__name__ function_name = (name or
compile_func = partial(generic_tag_compiler, params, defaults, function_name, InclusionNode) getattr(func, '_decorated_function', func).__name__)
compile_func = partial(generic_tag_compiler,
params=params, varargs=varargs, varkw=varkw,
defaults=defaults, name=function_name,
takes_context=takes_context, node_class=InclusionNode)
compile_func.__doc__ = func.__doc__ compile_func.__doc__ = func.__doc__
self.tag(function_name, compile_func) self.tag(function_name, compile_func)
return func return func
return dec return dec
def import_library(taglib_module): def import_library(taglib_module):
"""Load a template tag library module. """
Load a template tag library module.
Verifies that the library contains a 'register' attribute, and Verifies that the library contains a 'register' attribute, and
returns that attribute as the representation of the library returns that attribute as the representation of the library
@ -1023,31 +1202,36 @@ def import_library(taglib_module):
try: try:
mod = import_module(taglib_module) mod = import_module(taglib_module)
except ImportError, e: except ImportError, e:
# If the ImportError is because the taglib submodule does not exist, that's not # If the ImportError is because the taglib submodule does not exist,
# an error that should be raised. If the submodule exists and raised an ImportError # that's not an error that should be raised. If the submodule exists
# on the attempt to load it, that we want to raise. # and raised an ImportError on the attempt to load it, that we want
# to raise.
if not module_has_submodule(app_module, taglib): if not module_has_submodule(app_module, taglib):
return None return None
else: else:
raise InvalidTemplateLibrary("ImportError raised loading %s: %s" % (taglib_module, e)) raise InvalidTemplateLibrary("ImportError raised loading %s: %s" %
(taglib_module, e))
try: try:
return mod.register return mod.register
except AttributeError: except AttributeError:
raise InvalidTemplateLibrary("Template library %s does not have a variable named 'register'" % taglib_module) raise InvalidTemplateLibrary("Template library %s does not have "
"a variable named 'register'" %
taglib_module)
templatetags_modules = [] templatetags_modules = []
def get_templatetags_modules(): def get_templatetags_modules():
"""Return the list of all available template tag modules. """
Return the list of all available template tag modules.
Caches the result for faster access. Caches the result for faster access.
""" """
global templatetags_modules global templatetags_modules
if not templatetags_modules: if not templatetags_modules:
_templatetags_modules = [] _templatetags_modules = []
# Populate list once per process. Mutate the local list first, and then # Populate list once per process. Mutate the local list first, and
# assign it to the global name to ensure there are no cases where two # then assign it to the global name to ensure there are no cases where
# threads try to populate it simultaneously. # two threads try to populate it simultaneously.
for app_module in ['django'] + list(settings.INSTALLED_APPS): for app_module in ['django'] + list(settings.INSTALLED_APPS):
try: try:
templatetag_module = '%s.templatetags' % app_module templatetag_module = '%s.templatetags' % app_module
@ -1062,12 +1246,13 @@ def get_library(library_name):
""" """
Load the template library module with the given name. Load the template library module with the given name.
If library is not already loaded loop over all templatetags modules to locate it. If library is not already loaded loop over all templatetags modules
to locate it.
{% load somelib %} and {% load someotherlib %} loops twice. {% load somelib %} and {% load someotherlib %} loops twice.
Subsequent loads eg. {% load somelib %} in the same process will grab the cached Subsequent loads eg. {% load somelib %} in the same process will grab
module from libraries. the cached module from libraries.
""" """
lib = libraries.get(library_name, None) lib = libraries.get(library_name, None)
if not lib: if not lib:
@ -1081,11 +1266,16 @@ def get_library(library_name):
libraries[library_name] = lib libraries[library_name] = lib
break break
if not lib: if not lib:
raise InvalidTemplateLibrary("Template library %s not found, tried %s" % (library_name, ','.join(tried_modules))) raise InvalidTemplateLibrary("Template library %s not found, "
"tried %s" %
(library_name,
','.join(tried_modules)))
return lib return lib
def add_to_builtins(module): def add_to_builtins(module):
builtins.append(import_library(module)) builtins.append(import_library(module))
add_to_builtins('django.template.defaulttags') add_to_builtins('django.template.defaulttags')
add_to_builtins('django.template.defaultfilters') add_to_builtins('django.template.defaultfilters')

View File

@ -10,64 +10,13 @@ from django.template.base import (Node, NodeList, Template, Library,
TemplateSyntaxError, VariableDoesNotExist, InvalidTemplateLibrary, TemplateSyntaxError, VariableDoesNotExist, InvalidTemplateLibrary,
BLOCK_TAG_START, BLOCK_TAG_END, VARIABLE_TAG_START, VARIABLE_TAG_END, BLOCK_TAG_START, BLOCK_TAG_END, VARIABLE_TAG_START, VARIABLE_TAG_END,
SINGLE_BRACE_START, SINGLE_BRACE_END, COMMENT_TAG_START, COMMENT_TAG_END, SINGLE_BRACE_START, SINGLE_BRACE_END, COMMENT_TAG_START, COMMENT_TAG_END,
get_library) get_library, token_kwargs, kwarg_re)
from django.template.smartif import IfParser, Literal from django.template.smartif import IfParser, Literal
from django.template.defaultfilters import date from django.template.defaultfilters import date
from django.utils.encoding import smart_str, smart_unicode from django.utils.encoding import smart_str, smart_unicode
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
register = Library() register = Library()
# Regex for token keyword arguments
kwarg_re = re.compile(r"(?:(\w+)=)?(.+)")
def token_kwargs(bits, parser, support_legacy=False):
"""
A utility method for parsing token keyword arguments.
:param bits: A list containing remainder of the token (split by spaces)
that is to be checked for arguments. Valid arguments will be removed
from this list.
:param support_legacy: If set to true ``True``, the legacy format
``1 as foo`` will be accepted. Otherwise, only the standard ``foo=1``
format is allowed.
:returns: A dictionary of the arguments retrieved from the ``bits`` token
list.
There is no requirement for all remaining token ``bits`` to be keyword
arguments, so the dictionary will be returned as soon as an invalid
argument format is reached.
"""
if not bits:
return {}
match = kwarg_re.match(bits[0])
kwarg_format = match and match.group(1)
if not kwarg_format:
if not support_legacy:
return {}
if len(bits) < 3 or bits[1] != 'as':
return {}
kwargs = {}
while bits:
if kwarg_format:
match = kwarg_re.match(bits[0])
if not match or not match.group(1):
return kwargs
key, value = match.groups()
del bits[:1]
else:
if len(bits) < 3 or bits[1] != 'as':
return kwargs
key, value = bits[2], bits[0]
del bits[:3]
kwargs[key] = parser.compile_filter(value)
if bits and not kwarg_format:
if bits[0] != 'and':
return kwargs
del bits[:1]
return kwargs
class AutoEscapeControlNode(Node): class AutoEscapeControlNode(Node):
"""Implements the actions of the autoescape tag.""" """Implements the actions of the autoescape tag."""

View File

@ -698,6 +698,29 @@ If you need to rename your tag, you can provide a custom name for it::
def some_function(value): def some_function(value):
return value - 1 return value - 1
.. versionadded:: 1.4
``simple_tag`` functions may accept any number of positional or keyword
arguments. For example:
.. code-block:: python
@register.simple_tag
def my_tag(a, b, *args, **kwargs):
warning = kwargs['warning']
profile = kwargs['profile']
...
return ...
Then in the template any number of arguments, separated by spaces, may be
passed to the template tag. Like in Python, the values for keyword arguments
are set using the equal sign ("``=``") and must be provided after the positional
arguments. For example:
.. code-block:: html+django
{% my_tag 123 "abcd" book.title warning=message|lower profile=user.profile %}
.. _howto-custom-template-tags-assignment-tags: .. _howto-custom-template-tags-assignment-tags:
Assignment tags Assignment tags
@ -761,6 +784,27 @@ Or, using decorator syntax:
For more information on how the ``takes_context`` option works, see the section For more information on how the ``takes_context`` option works, see the section
on :ref:`inclusion tags<howto-custom-template-tags-inclusion-tags>`. on :ref:`inclusion tags<howto-custom-template-tags-inclusion-tags>`.
``assignment_tag`` functions may accept any number of positional or keyword
arguments. For example:
.. code-block:: python
@register.assignment_tag
def my_tag(a, b, *args, **kwargs):
warning = kwargs['warning']
profile = kwargs['profile']
...
return ...
Then in the template any number of arguments, separated by spaces, may be
passed to the template tag. Like in Python, the values for keyword arguments
are set using the equal sign ("``=``") and must be provided after the positional
arguments. For example:
.. code-block:: html+django
{% my_tag 123 "abcd" book.title warning=message|lower profile=user.profile as the_result %}
.. _howto-custom-template-tags-inclusion-tags: .. _howto-custom-template-tags-inclusion-tags:
Inclusion tags Inclusion tags
@ -884,6 +928,29 @@ The ``takes_context`` parameter defaults to ``False``. When it's set to *True*,
the tag is passed the context object, as in this example. That's the only the tag is passed the context object, as in this example. That's the only
difference between this case and the previous ``inclusion_tag`` example. difference between this case and the previous ``inclusion_tag`` example.
.. versionadded:: 1.4
``inclusion_tag`` functions may accept any number of positional or keyword
arguments. For example:
.. code-block:: python
@register.inclusion_tag('my_template.html')
def my_tag(a, b, *args, **kwargs):
warning = kwargs['warning']
profile = kwargs['profile']
...
return ...
Then in the template any number of arguments, separated by spaces, may be
passed to the template tag. Like in Python, the values for keyword arguments
are set using the equal sign ("``=``") and must be provided after the positional
arguments. For example:
.. code-block:: html+django
{% my_tag 123 "abcd" book.title warning=message|lower profile=user.profile %}
Setting a variable in the context Setting a variable in the context
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -162,6 +162,31 @@ A new helper function,
``template.Library`` to ease the creation of template tags that store some ``template.Library`` to ease the creation of template tags that store some
data in a specified context variable. data in a specified context variable.
``*args`` and ``**kwargs`` support for template tag helper functions
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
:ref:`simple_tag<howto-custom-template-tags-simple-tags>`, :ref:`inclusion_tag
<howto-custom-template-tags-inclusion-tags>` and the newly introduced
:ref:`assignment_tag<howto-custom-template-tags-assignment-tags>` template
helper functions may now accept any number of positional or keyword arguments.
For example:
.. code-block:: python
@register.simple_tag
def my_tag(a, b, *args, **kwargs):
warning = kwargs['warning']
profile = kwargs['profile']
...
return ...
Then in the template any number of arguments may be passed to the template tag.
For example:
.. code-block:: html+django
{% my_tag 123 "abcd" book.title warning=message|lower profile=user.profile %}
``truncatechars`` template filter ``truncatechars`` template filter
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -35,6 +35,56 @@ class CustomTagTests(TestCase):
t = template.Template('{% load custom %}{% params_and_context 37 %}') t = template.Template('{% load custom %}{% params_and_context 37 %}')
self.assertEqual(t.render(c), u'params_and_context - Expected result (context value: 42): 37') self.assertEqual(t.render(c), u'params_and_context - Expected result (context value: 42): 37')
t = template.Template('{% load custom %}{% simple_two_params 37 42 %}')
self.assertEqual(t.render(c), u'simple_two_params - Expected result: 37, 42')
t = template.Template('{% load custom %}{% simple_one_default 37 %}')
self.assertEqual(t.render(c), u'simple_one_default - Expected result: 37, hi')
t = template.Template('{% load custom %}{% simple_one_default 37 two="hello" %}')
self.assertEqual(t.render(c), u'simple_one_default - Expected result: 37, hello')
t = template.Template('{% load custom %}{% simple_one_default one=99 two="hello" %}')
self.assertEqual(t.render(c), u'simple_one_default - Expected result: 99, hello')
self.assertRaisesRegexp(template.TemplateSyntaxError,
"'simple_one_default' received unexpected keyword argument 'three'",
template.Template, '{% load custom %}{% simple_one_default 99 two="hello" three="foo" %}')
t = template.Template('{% load custom %}{% simple_one_default 37 42 %}')
self.assertEqual(t.render(c), u'simple_one_default - Expected result: 37, 42')
t = template.Template('{% load custom %}{% simple_unlimited_args 37 %}')
self.assertEqual(t.render(c), u'simple_unlimited_args - Expected result: 37, hi')
t = template.Template('{% load custom %}{% simple_unlimited_args 37 42 56 89 %}')
self.assertEqual(t.render(c), u'simple_unlimited_args - Expected result: 37, 42, 56, 89')
t = template.Template('{% load custom %}{% simple_only_unlimited_args %}')
self.assertEqual(t.render(c), u'simple_only_unlimited_args - Expected result: ')
t = template.Template('{% load custom %}{% simple_only_unlimited_args 37 42 56 89 %}')
self.assertEqual(t.render(c), u'simple_only_unlimited_args - Expected result: 37, 42, 56, 89')
self.assertRaisesRegexp(template.TemplateSyntaxError,
"'simple_two_params' received too many positional arguments",
template.Template, '{% load custom %}{% simple_two_params 37 42 56 %}')
self.assertRaisesRegexp(template.TemplateSyntaxError,
"'simple_one_default' received too many positional arguments",
template.Template, '{% load custom %}{% simple_one_default 37 42 56 %}')
t = template.Template('{% load custom %}{% simple_unlimited_args_kwargs 37 40|add:2 56 eggs="scrambled" four=1|add:3 %}')
self.assertEqual(t.render(c), u'simple_unlimited_args_kwargs - Expected result: 37, 42, 56 / eggs=scrambled, four=4')
self.assertRaisesRegexp(template.TemplateSyntaxError,
"'simple_unlimited_args_kwargs' received some positional argument\(s\) after some keyword argument\(s\)",
template.Template, '{% load custom %}{% simple_unlimited_args_kwargs 37 40|add:2 eggs="scrambled" 56 four=1|add:3 %}')
self.assertRaisesRegexp(template.TemplateSyntaxError,
"'simple_unlimited_args_kwargs' received multiple values for keyword argument 'eggs'",
template.Template, '{% load custom %}{% simple_unlimited_args_kwargs 37 eggs="scrambled" eggs="scrambled" %}')
def test_simple_tag_registration(self): def test_simple_tag_registration(self):
# Test that the decorators preserve the decorated function's docstring, name and attributes. # Test that the decorators preserve the decorated function's docstring, name and attributes.
self.verify_tag(custom.no_params, 'no_params') self.verify_tag(custom.no_params, 'no_params')
@ -42,16 +92,14 @@ class CustomTagTests(TestCase):
self.verify_tag(custom.explicit_no_context, 'explicit_no_context') self.verify_tag(custom.explicit_no_context, 'explicit_no_context')
self.verify_tag(custom.no_params_with_context, 'no_params_with_context') self.verify_tag(custom.no_params_with_context, 'no_params_with_context')
self.verify_tag(custom.params_and_context, 'params_and_context') self.verify_tag(custom.params_and_context, 'params_and_context')
self.verify_tag(custom.simple_unlimited_args_kwargs, 'simple_unlimited_args_kwargs')
self.verify_tag(custom.simple_tag_without_context_parameter, 'simple_tag_without_context_parameter')
def test_simple_tag_missing_context(self): def test_simple_tag_missing_context(self):
# That the 'context' parameter must be present when takes_context is True # The 'context' parameter must be present when takes_context is True
def a_simple_tag_without_parameters(arg): self.assertRaisesRegexp(template.TemplateSyntaxError,
"""Expected __doc__""" "'simple_tag_without_context_parameter' is decorated with takes_context=True so it must have a first argument of 'context'",
return "Expected result" template.Template, '{% load custom %}{% simple_tag_without_context_parameter 123 %}')
register = template.Library()
decorator = register.simple_tag(takes_context=True)
self.assertRaises(template.TemplateSyntaxError, decorator, a_simple_tag_without_parameters)
def test_inclusion_tags(self): def test_inclusion_tags(self):
c = template.Context({'value': 42}) c = template.Context({'value': 42})
@ -71,6 +119,70 @@ class CustomTagTests(TestCase):
t = template.Template('{% load custom %}{% inclusion_params_and_context 37 %}') t = template.Template('{% load custom %}{% inclusion_params_and_context 37 %}')
self.assertEqual(t.render(c), u'inclusion_params_and_context - Expected result (context value: 42): 37\n') self.assertEqual(t.render(c), u'inclusion_params_and_context - Expected result (context value: 42): 37\n')
t = template.Template('{% load custom %}{% inclusion_two_params 37 42 %}')
self.assertEqual(t.render(c), u'inclusion_two_params - Expected result: 37, 42\n')
t = template.Template('{% load custom %}{% inclusion_one_default 37 %}')
self.assertEqual(t.render(c), u'inclusion_one_default - Expected result: 37, hi\n')
t = template.Template('{% load custom %}{% inclusion_one_default 37 two="hello" %}')
self.assertEqual(t.render(c), u'inclusion_one_default - Expected result: 37, hello\n')
t = template.Template('{% load custom %}{% inclusion_one_default one=99 two="hello" %}')
self.assertEqual(t.render(c), u'inclusion_one_default - Expected result: 99, hello\n')
self.assertRaisesRegexp(template.TemplateSyntaxError,
"'inclusion_one_default' received unexpected keyword argument 'three'",
template.Template, '{% load custom %}{% inclusion_one_default 99 two="hello" three="foo" %}')
t = template.Template('{% load custom %}{% inclusion_one_default 37 42 %}')
self.assertEqual(t.render(c), u'inclusion_one_default - Expected result: 37, 42\n')
t = template.Template('{% load custom %}{% inclusion_unlimited_args 37 %}')
self.assertEqual(t.render(c), u'inclusion_unlimited_args - Expected result: 37, hi\n')
t = template.Template('{% load custom %}{% inclusion_unlimited_args 37 42 56 89 %}')
self.assertEqual(t.render(c), u'inclusion_unlimited_args - Expected result: 37, 42, 56, 89\n')
t = template.Template('{% load custom %}{% inclusion_only_unlimited_args %}')
self.assertEqual(t.render(c), u'inclusion_only_unlimited_args - Expected result: \n')
t = template.Template('{% load custom %}{% inclusion_only_unlimited_args 37 42 56 89 %}')
self.assertEqual(t.render(c), u'inclusion_only_unlimited_args - Expected result: 37, 42, 56, 89\n')
self.assertRaisesRegexp(template.TemplateSyntaxError,
"'inclusion_two_params' received too many positional arguments",
template.Template, '{% load custom %}{% inclusion_two_params 37 42 56 %}')
self.assertRaisesRegexp(template.TemplateSyntaxError,
"'inclusion_one_default' received too many positional arguments",
template.Template, '{% load custom %}{% inclusion_one_default 37 42 56 %}')
self.assertRaisesRegexp(template.TemplateSyntaxError,
"'inclusion_one_default' did not receive value\(s\) for the argument\(s\): 'one'",
template.Template, '{% load custom %}{% inclusion_one_default %}')
self.assertRaisesRegexp(template.TemplateSyntaxError,
"'inclusion_unlimited_args' did not receive value\(s\) for the argument\(s\): 'one'",
template.Template, '{% load custom %}{% inclusion_unlimited_args %}')
t = template.Template('{% load custom %}{% inclusion_unlimited_args_kwargs 37 40|add:2 56 eggs="scrambled" four=1|add:3 %}')
self.assertEqual(t.render(c), u'inclusion_unlimited_args_kwargs - Expected result: 37, 42, 56 / eggs=scrambled, four=4\n')
self.assertRaisesRegexp(template.TemplateSyntaxError,
"'inclusion_unlimited_args_kwargs' received some positional argument\(s\) after some keyword argument\(s\)",
template.Template, '{% load custom %}{% inclusion_unlimited_args_kwargs 37 40|add:2 eggs="scrambled" 56 four=1|add:3 %}')
self.assertRaisesRegexp(template.TemplateSyntaxError,
"'inclusion_unlimited_args_kwargs' received multiple values for keyword argument 'eggs'",
template.Template, '{% load custom %}{% inclusion_unlimited_args_kwargs 37 eggs="scrambled" eggs="scrambled" %}')
def test_include_tag_missing_context(self):
# The 'context' parameter must be present when takes_context is True
self.assertRaisesRegexp(template.TemplateSyntaxError,
"'inclusion_tag_without_context_parameter' is decorated with takes_context=True so it must have a first argument of 'context'",
template.Template, '{% load custom %}{% inclusion_tag_without_context_parameter 123 %}')
def test_inclusion_tags_from_template(self): def test_inclusion_tags_from_template(self):
c = template.Context({'value': 42}) c = template.Context({'value': 42})
@ -89,6 +201,27 @@ class CustomTagTests(TestCase):
t = template.Template('{% load custom %}{% inclusion_params_and_context_from_template 37 %}') t = template.Template('{% load custom %}{% inclusion_params_and_context_from_template 37 %}')
self.assertEqual(t.render(c), u'inclusion_params_and_context_from_template - Expected result (context value: 42): 37\n') self.assertEqual(t.render(c), u'inclusion_params_and_context_from_template - Expected result (context value: 42): 37\n')
t = template.Template('{% load custom %}{% inclusion_two_params_from_template 37 42 %}')
self.assertEqual(t.render(c), u'inclusion_two_params_from_template - Expected result: 37, 42\n')
t = template.Template('{% load custom %}{% inclusion_one_default_from_template 37 %}')
self.assertEqual(t.render(c), u'inclusion_one_default_from_template - Expected result: 37, hi\n')
t = template.Template('{% load custom %}{% inclusion_one_default_from_template 37 42 %}')
self.assertEqual(t.render(c), u'inclusion_one_default_from_template - Expected result: 37, 42\n')
t = template.Template('{% load custom %}{% inclusion_unlimited_args_from_template 37 %}')
self.assertEqual(t.render(c), u'inclusion_unlimited_args_from_template - Expected result: 37, hi\n')
t = template.Template('{% load custom %}{% inclusion_unlimited_args_from_template 37 42 56 89 %}')
self.assertEqual(t.render(c), u'inclusion_unlimited_args_from_template - Expected result: 37, 42, 56, 89\n')
t = template.Template('{% load custom %}{% inclusion_only_unlimited_args_from_template %}')
self.assertEqual(t.render(c), u'inclusion_only_unlimited_args_from_template - Expected result: \n')
t = template.Template('{% load custom %}{% inclusion_only_unlimited_args_from_template 37 42 56 89 %}')
self.assertEqual(t.render(c), u'inclusion_only_unlimited_args_from_template - Expected result: 37, 42, 56, 89\n')
def test_inclusion_tag_registration(self): def test_inclusion_tag_registration(self):
# Test that the decorators preserve the decorated function's docstring, name and attributes. # Test that the decorators preserve the decorated function's docstring, name and attributes.
self.verify_tag(custom.inclusion_no_params, 'inclusion_no_params') self.verify_tag(custom.inclusion_no_params, 'inclusion_no_params')
@ -96,6 +229,14 @@ class CustomTagTests(TestCase):
self.verify_tag(custom.inclusion_explicit_no_context, 'inclusion_explicit_no_context') self.verify_tag(custom.inclusion_explicit_no_context, 'inclusion_explicit_no_context')
self.verify_tag(custom.inclusion_no_params_with_context, 'inclusion_no_params_with_context') self.verify_tag(custom.inclusion_no_params_with_context, 'inclusion_no_params_with_context')
self.verify_tag(custom.inclusion_params_and_context, 'inclusion_params_and_context') self.verify_tag(custom.inclusion_params_and_context, 'inclusion_params_and_context')
self.verify_tag(custom.inclusion_two_params, 'inclusion_two_params')
self.verify_tag(custom.inclusion_one_default, 'inclusion_one_default')
self.verify_tag(custom.inclusion_unlimited_args, 'inclusion_unlimited_args')
self.verify_tag(custom.inclusion_only_unlimited_args, 'inclusion_only_unlimited_args')
self.verify_tag(custom.inclusion_tag_without_context_parameter, 'inclusion_tag_without_context_parameter')
self.verify_tag(custom.inclusion_tag_use_l10n, 'inclusion_tag_use_l10n')
self.verify_tag(custom.inclusion_tag_current_app, 'inclusion_tag_current_app')
self.verify_tag(custom.inclusion_unlimited_args_kwargs, 'inclusion_unlimited_args_kwargs')
def test_15070_current_app(self): def test_15070_current_app(self):
""" """
@ -139,6 +280,37 @@ class CustomTagTests(TestCase):
t = template.Template('{% load custom %}{% assignment_params_and_context 37 as var %}The result is: {{ var }}') t = template.Template('{% load custom %}{% assignment_params_and_context 37 as var %}The result is: {{ var }}')
self.assertEqual(t.render(c), u'The result is: assignment_params_and_context - Expected result (context value: 42): 37') self.assertEqual(t.render(c), u'The result is: assignment_params_and_context - Expected result (context value: 42): 37')
t = template.Template('{% load custom %}{% assignment_two_params 37 42 as var %}The result is: {{ var }}')
self.assertEqual(t.render(c), u'The result is: assignment_two_params - Expected result: 37, 42')
t = template.Template('{% load custom %}{% assignment_one_default 37 as var %}The result is: {{ var }}')
self.assertEqual(t.render(c), u'The result is: assignment_one_default - Expected result: 37, hi')
t = template.Template('{% load custom %}{% assignment_one_default 37 two="hello" as var %}The result is: {{ var }}')
self.assertEqual(t.render(c), u'The result is: assignment_one_default - Expected result: 37, hello')
t = template.Template('{% load custom %}{% assignment_one_default one=99 two="hello" as var %}The result is: {{ var }}')
self.assertEqual(t.render(c), u'The result is: assignment_one_default - Expected result: 99, hello')
self.assertRaisesRegexp(template.TemplateSyntaxError,
"'assignment_one_default' received unexpected keyword argument 'three'",
template.Template, '{% load custom %}{% assignment_one_default 99 two="hello" three="foo" as var %}')
t = template.Template('{% load custom %}{% assignment_one_default 37 42 as var %}The result is: {{ var }}')
self.assertEqual(t.render(c), u'The result is: assignment_one_default - Expected result: 37, 42')
t = template.Template('{% load custom %}{% assignment_unlimited_args 37 as var %}The result is: {{ var }}')
self.assertEqual(t.render(c), u'The result is: assignment_unlimited_args - Expected result: 37, hi')
t = template.Template('{% load custom %}{% assignment_unlimited_args 37 42 56 89 as var %}The result is: {{ var }}')
self.assertEqual(t.render(c), u'The result is: assignment_unlimited_args - Expected result: 37, 42, 56, 89')
t = template.Template('{% load custom %}{% assignment_only_unlimited_args as var %}The result is: {{ var }}')
self.assertEqual(t.render(c), u'The result is: assignment_only_unlimited_args - Expected result: ')
t = template.Template('{% load custom %}{% assignment_only_unlimited_args 37 42 56 89 as var %}The result is: {{ var }}')
self.assertEqual(t.render(c), u'The result is: assignment_only_unlimited_args - Expected result: 37, 42, 56, 89')
self.assertRaisesRegexp(template.TemplateSyntaxError, self.assertRaisesRegexp(template.TemplateSyntaxError,
"'assignment_one_param' tag takes at least 2 arguments and the second last argument must be 'as'", "'assignment_one_param' tag takes at least 2 arguments and the second last argument must be 'as'",
template.Template, '{% load custom %}{% assignment_one_param 37 %}The result is: {{ var }}') template.Template, '{% load custom %}{% assignment_one_param 37 %}The result is: {{ var }}')
@ -151,6 +323,33 @@ class CustomTagTests(TestCase):
"'assignment_one_param' tag takes at least 2 arguments and the second last argument must be 'as'", "'assignment_one_param' tag takes at least 2 arguments and the second last argument must be 'as'",
template.Template, '{% load custom %}{% assignment_one_param 37 ass var %}The result is: {{ var }}') template.Template, '{% load custom %}{% assignment_one_param 37 ass var %}The result is: {{ var }}')
self.assertRaisesRegexp(template.TemplateSyntaxError,
"'assignment_two_params' received too many positional arguments",
template.Template, '{% load custom %}{% assignment_two_params 37 42 56 as var %}The result is: {{ var }}')
self.assertRaisesRegexp(template.TemplateSyntaxError,
"'assignment_one_default' received too many positional arguments",
template.Template, '{% load custom %}{% assignment_one_default 37 42 56 as var %}The result is: {{ var }}')
self.assertRaisesRegexp(template.TemplateSyntaxError,
"'assignment_one_default' did not receive value\(s\) for the argument\(s\): 'one'",
template.Template, '{% load custom %}{% assignment_one_default as var %}The result is: {{ var }}')
self.assertRaisesRegexp(template.TemplateSyntaxError,
"'assignment_unlimited_args' did not receive value\(s\) for the argument\(s\): 'one'",
template.Template, '{% load custom %}{% assignment_unlimited_args as var %}The result is: {{ var }}')
t = template.Template('{% load custom %}{% assignment_unlimited_args_kwargs 37 40|add:2 56 eggs="scrambled" four=1|add:3 as var %}The result is: {{ var }}')
self.assertEqual(t.render(c), u'The result is: assignment_unlimited_args_kwargs - Expected result: 37, 42, 56 / eggs=scrambled, four=4')
self.assertRaisesRegexp(template.TemplateSyntaxError,
"'assignment_unlimited_args_kwargs' received some positional argument\(s\) after some keyword argument\(s\)",
template.Template, '{% load custom %}{% assignment_unlimited_args_kwargs 37 40|add:2 eggs="scrambled" 56 four=1|add:3 as var %}The result is: {{ var }}')
self.assertRaisesRegexp(template.TemplateSyntaxError,
"'assignment_unlimited_args_kwargs' received multiple values for keyword argument 'eggs'",
template.Template, '{% load custom %}{% assignment_unlimited_args_kwargs 37 eggs="scrambled" eggs="scrambled" as var %}The result is: {{ var }}')
def test_assignment_tag_registration(self): def test_assignment_tag_registration(self):
# Test that the decorators preserve the decorated function's docstring, name and attributes. # Test that the decorators preserve the decorated function's docstring, name and attributes.
self.verify_tag(custom.assignment_no_params, 'assignment_no_params') self.verify_tag(custom.assignment_no_params, 'assignment_no_params')
@ -158,16 +357,16 @@ class CustomTagTests(TestCase):
self.verify_tag(custom.assignment_explicit_no_context, 'assignment_explicit_no_context') self.verify_tag(custom.assignment_explicit_no_context, 'assignment_explicit_no_context')
self.verify_tag(custom.assignment_no_params_with_context, 'assignment_no_params_with_context') self.verify_tag(custom.assignment_no_params_with_context, 'assignment_no_params_with_context')
self.verify_tag(custom.assignment_params_and_context, 'assignment_params_and_context') self.verify_tag(custom.assignment_params_and_context, 'assignment_params_and_context')
self.verify_tag(custom.assignment_one_default, 'assignment_one_default')
self.verify_tag(custom.assignment_two_params, 'assignment_two_params')
self.verify_tag(custom.assignment_unlimited_args, 'assignment_unlimited_args')
self.verify_tag(custom.assignment_only_unlimited_args, 'assignment_only_unlimited_args')
self.verify_tag(custom.assignment_unlimited_args, 'assignment_unlimited_args')
self.verify_tag(custom.assignment_unlimited_args_kwargs, 'assignment_unlimited_args_kwargs')
self.verify_tag(custom.assignment_tag_without_context_parameter, 'assignment_tag_without_context_parameter')
def test_assignment_tag_missing_context(self): def test_assignment_tag_missing_context(self):
# That the 'context' parameter must be present when takes_context is True # The 'context' parameter must be present when takes_context is True
def an_assignment_tag_without_parameters(arg):
"""Expected __doc__"""
return "Expected result"
register = template.Library()
decorator = register.assignment_tag(takes_context=True)
self.assertRaisesRegexp(template.TemplateSyntaxError, self.assertRaisesRegexp(template.TemplateSyntaxError,
"Any tag function decorated with takes_context=True must have a first argument of 'context'", "'assignment_tag_without_context_parameter' is decorated with takes_context=True so it must have a first argument of 'context'",
decorator, an_assignment_tag_without_parameters) template.Template, '{% load custom %}{% assignment_tag_without_context_parameter 123 as var %}')

View File

@ -1,3 +1,5 @@
import operator
from django import template from django import template
from django.template.defaultfilters import stringfilter from django.template.defaultfilters import stringfilter
from django.template.loader import get_template from django.template.loader import get_template
@ -40,6 +42,61 @@ def params_and_context(context, arg):
return "params_and_context - Expected result (context value: %s): %s" % (context['value'], arg) return "params_and_context - Expected result (context value: %s): %s" % (context['value'], arg)
params_and_context.anything = "Expected params_and_context __dict__" params_and_context.anything = "Expected params_and_context __dict__"
@register.simple_tag
def simple_two_params(one, two):
"""Expected simple_two_params __doc__"""
return "simple_two_params - Expected result: %s, %s" % (one, two)
simple_two_params.anything = "Expected simple_two_params __dict__"
@register.simple_tag
def simple_one_default(one, two='hi'):
"""Expected simple_one_default __doc__"""
return "simple_one_default - Expected result: %s, %s" % (one, two)
simple_one_default.anything = "Expected simple_one_default __dict__"
@register.simple_tag
def simple_unlimited_args(one, two='hi', *args):
"""Expected simple_unlimited_args __doc__"""
return "simple_unlimited_args - Expected result: %s" % (', '.join([unicode(arg) for arg in [one, two] + list(args)]))
simple_unlimited_args.anything = "Expected simple_unlimited_args __dict__"
@register.simple_tag
def simple_only_unlimited_args(*args):
"""Expected simple_only_unlimited_args __doc__"""
return "simple_only_unlimited_args - Expected result: %s" % ', '.join([unicode(arg) for arg in args])
simple_only_unlimited_args.anything = "Expected simple_only_unlimited_args __dict__"
@register.simple_tag
def simple_unlimited_args_kwargs(one, two='hi', *args, **kwargs):
"""Expected simple_unlimited_args_kwargs __doc__"""
# Sort the dictionary by key to guarantee the order for testing.
sorted_kwarg = sorted(kwargs.iteritems(), key=operator.itemgetter(0))
return "simple_unlimited_args_kwargs - Expected result: %s / %s" % (
', '.join([unicode(arg) for arg in [one, two] + list(args)]),
', '.join(['%s=%s' % (k, v) for (k, v) in sorted_kwarg])
)
simple_unlimited_args_kwargs.anything = "Expected simple_unlimited_args_kwargs __dict__"
@register.simple_tag(takes_context=True)
def simple_tag_without_context_parameter(arg):
"""Expected simple_tag_without_context_parameter __doc__"""
return "Expected result"
simple_tag_without_context_parameter.anything = "Expected simple_tag_without_context_parameter __dict__"
@register.simple_tag(takes_context=True)
def current_app(context):
return "%s" % context.current_app
@register.simple_tag(takes_context=True)
def use_l10n(context):
return "%s" % context.use_l10n
@register.simple_tag(name='minustwo')
def minustwo_overridden_name(value):
return value - 2
register.simple_tag(lambda x: x - 1, name='minusone')
@register.inclusion_tag('inclusion.html') @register.inclusion_tag('inclusion.html')
def inclusion_no_params(): def inclusion_no_params():
"""Expected inclusion_no_params __doc__""" """Expected inclusion_no_params __doc__"""
@ -100,21 +157,82 @@ def inclusion_params_and_context_from_template(context, arg):
return {"result" : "inclusion_params_and_context_from_template - Expected result (context value: %s): %s" % (context['value'], arg)} return {"result" : "inclusion_params_and_context_from_template - Expected result (context value: %s): %s" % (context['value'], arg)}
inclusion_params_and_context_from_template.anything = "Expected inclusion_params_and_context_from_template __dict__" inclusion_params_and_context_from_template.anything = "Expected inclusion_params_and_context_from_template __dict__"
@register.simple_tag(takes_context=True) @register.inclusion_tag('inclusion.html')
def current_app(context): def inclusion_two_params(one, two):
return "%s" % context.current_app """Expected inclusion_two_params __doc__"""
return {"result": "inclusion_two_params - Expected result: %s, %s" % (one, two)}
inclusion_two_params.anything = "Expected inclusion_two_params __dict__"
@register.inclusion_tag(get_template('inclusion.html'))
def inclusion_two_params_from_template(one, two):
"""Expected inclusion_two_params_from_template __doc__"""
return {"result": "inclusion_two_params_from_template - Expected result: %s, %s" % (one, two)}
inclusion_two_params_from_template.anything = "Expected inclusion_two_params_from_template __dict__"
@register.inclusion_tag('inclusion.html')
def inclusion_one_default(one, two='hi'):
"""Expected inclusion_one_default __doc__"""
return {"result": "inclusion_one_default - Expected result: %s, %s" % (one, two)}
inclusion_one_default.anything = "Expected inclusion_one_default __dict__"
@register.inclusion_tag(get_template('inclusion.html'))
def inclusion_one_default_from_template(one, two='hi'):
"""Expected inclusion_one_default_from_template __doc__"""
return {"result": "inclusion_one_default_from_template - Expected result: %s, %s" % (one, two)}
inclusion_one_default_from_template.anything = "Expected inclusion_one_default_from_template __dict__"
@register.inclusion_tag('inclusion.html')
def inclusion_unlimited_args(one, two='hi', *args):
"""Expected inclusion_unlimited_args __doc__"""
return {"result": "inclusion_unlimited_args - Expected result: %s" % (', '.join([unicode(arg) for arg in [one, two] + list(args)]))}
inclusion_unlimited_args.anything = "Expected inclusion_unlimited_args __dict__"
@register.inclusion_tag(get_template('inclusion.html'))
def inclusion_unlimited_args_from_template(one, two='hi', *args):
"""Expected inclusion_unlimited_args_from_template __doc__"""
return {"result": "inclusion_unlimited_args_from_template - Expected result: %s" % (', '.join([unicode(arg) for arg in [one, two] + list(args)]))}
inclusion_unlimited_args_from_template.anything = "Expected inclusion_unlimited_args_from_template __dict__"
@register.inclusion_tag('inclusion.html')
def inclusion_only_unlimited_args(*args):
"""Expected inclusion_only_unlimited_args __doc__"""
return {"result": "inclusion_only_unlimited_args - Expected result: %s" % (', '.join([unicode(arg) for arg in args]))}
inclusion_only_unlimited_args.anything = "Expected inclusion_only_unlimited_args __dict__"
@register.inclusion_tag(get_template('inclusion.html'))
def inclusion_only_unlimited_args_from_template(*args):
"""Expected inclusion_only_unlimited_args_from_template __doc__"""
return {"result": "inclusion_only_unlimited_args_from_template - Expected result: %s" % (', '.join([unicode(arg) for arg in args]))}
inclusion_only_unlimited_args_from_template.anything = "Expected inclusion_only_unlimited_args_from_template __dict__"
@register.inclusion_tag('test_incl_tag_current_app.html', takes_context=True) @register.inclusion_tag('test_incl_tag_current_app.html', takes_context=True)
def inclusion_tag_current_app(context): def inclusion_tag_current_app(context):
"""Expected inclusion_tag_current_app __doc__"""
return {} return {}
inclusion_tag_current_app.anything = "Expected inclusion_tag_current_app __dict__"
@register.simple_tag(takes_context=True)
def use_l10n(context):
return "%s" % context.use_l10n
@register.inclusion_tag('test_incl_tag_use_l10n.html', takes_context=True) @register.inclusion_tag('test_incl_tag_use_l10n.html', takes_context=True)
def inclusion_tag_use_l10n(context): def inclusion_tag_use_l10n(context):
"""Expected inclusion_tag_use_l10n __doc__"""
return {} return {}
inclusion_tag_use_l10n.anything = "Expected inclusion_tag_use_l10n __dict__"
@register.inclusion_tag('inclusion.html')
def inclusion_unlimited_args_kwargs(one, two='hi', *args, **kwargs):
"""Expected inclusion_unlimited_args_kwargs __doc__"""
# Sort the dictionary by key to guarantee the order for testing.
sorted_kwarg = sorted(kwargs.iteritems(), key=operator.itemgetter(0))
return {"result": "inclusion_unlimited_args_kwargs - Expected result: %s / %s" % (
', '.join([unicode(arg) for arg in [one, two] + list(args)]),
', '.join(['%s=%s' % (k, v) for (k, v) in sorted_kwarg])
)}
inclusion_unlimited_args_kwargs.anything = "Expected inclusion_unlimited_args_kwargs __dict__"
@register.inclusion_tag('inclusion.html', takes_context=True)
def inclusion_tag_without_context_parameter(arg):
"""Expected inclusion_tag_without_context_parameter __doc__"""
return {}
inclusion_tag_without_context_parameter.anything = "Expected inclusion_tag_without_context_parameter __dict__"
@register.assignment_tag @register.assignment_tag
def assignment_no_params(): def assignment_no_params():
@ -146,8 +264,43 @@ def assignment_params_and_context(context, arg):
return "assignment_params_and_context - Expected result (context value: %s): %s" % (context['value'], arg) return "assignment_params_and_context - Expected result (context value: %s): %s" % (context['value'], arg)
assignment_params_and_context.anything = "Expected assignment_params_and_context __dict__" assignment_params_and_context.anything = "Expected assignment_params_and_context __dict__"
register.simple_tag(lambda x: x - 1, name='minusone') @register.assignment_tag
def assignment_two_params(one, two):
"""Expected assignment_two_params __doc__"""
return "assignment_two_params - Expected result: %s, %s" % (one, two)
assignment_two_params.anything = "Expected assignment_two_params __dict__"
@register.simple_tag(name='minustwo') @register.assignment_tag
def minustwo_overridden_name(value): def assignment_one_default(one, two='hi'):
return value - 2 """Expected assignment_one_default __doc__"""
return "assignment_one_default - Expected result: %s, %s" % (one, two)
assignment_one_default.anything = "Expected assignment_one_default __dict__"
@register.assignment_tag
def assignment_unlimited_args(one, two='hi', *args):
"""Expected assignment_unlimited_args __doc__"""
return "assignment_unlimited_args - Expected result: %s" % (', '.join([unicode(arg) for arg in [one, two] + list(args)]))
assignment_unlimited_args.anything = "Expected assignment_unlimited_args __dict__"
@register.assignment_tag
def assignment_only_unlimited_args(*args):
"""Expected assignment_only_unlimited_args __doc__"""
return "assignment_only_unlimited_args - Expected result: %s" % ', '.join([unicode(arg) for arg in args])
assignment_only_unlimited_args.anything = "Expected assignment_only_unlimited_args __dict__"
@register.assignment_tag
def assignment_unlimited_args_kwargs(one, two='hi', *args, **kwargs):
"""Expected assignment_unlimited_args_kwargs __doc__"""
# Sort the dictionary by key to guarantee the order for testing.
sorted_kwarg = sorted(kwargs.iteritems(), key=operator.itemgetter(0))
return "assignment_unlimited_args_kwargs - Expected result: %s / %s" % (
', '.join([unicode(arg) for arg in [one, two] + list(args)]),
', '.join(['%s=%s' % (k, v) for (k, v) in sorted_kwarg])
)
assignment_unlimited_args_kwargs.anything = "Expected assignment_unlimited_args_kwargs __dict__"
@register.assignment_tag(takes_context=True)
def assignment_tag_without_context_parameter(arg):
"""Expected assignment_tag_without_context_parameter __doc__"""
return "Expected result"
assignment_tag_without_context_parameter.anything = "Expected assignment_tag_without_context_parameter __dict__"