mirror of https://github.com/django/django.git
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:
parent
29b8e34dfb
commit
8137027fd7
|
@ -3,13 +3,16 @@ from functools import partial
|
|||
from inspect import getargspec
|
||||
|
||||
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.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.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.html import escape
|
||||
from django.utils.module_loading import module_has_submodule
|
||||
|
@ -19,6 +22,12 @@ TOKEN_TEXT = 0
|
|||
TOKEN_VAR = 1
|
||||
TOKEN_BLOCK = 2
|
||||
TOKEN_COMMENT = 3
|
||||
TOKEN_MAPPING = {
|
||||
TOKEN_TEXT: 'Text',
|
||||
TOKEN_VAR: 'Var',
|
||||
TOKEN_BLOCK: 'Block',
|
||||
TOKEN_COMMENT: 'Comment',
|
||||
}
|
||||
|
||||
# template syntax constants
|
||||
FILTER_SEPARATOR = '|'
|
||||
|
@ -34,16 +43,19 @@ TRANSLATOR_COMMENT_MARK = 'Translators'
|
|||
SINGLE_BRACE_START = '{'
|
||||
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
|
||||
# (e.g. strings)
|
||||
UNKNOWN_SOURCE = '<unknown source>'
|
||||
|
||||
# match a variable or block tag and capture the entire tag, including start/end delimiters
|
||||
tag_re = re.compile('(%s.*?%s|%s.*?%s|%s.*?%s)' % (re.escape(BLOCK_TAG_START), re.escape(BLOCK_TAG_END),
|
||||
re.escape(VARIABLE_TAG_START), re.escape(VARIABLE_TAG_END),
|
||||
re.escape(COMMENT_TAG_START), re.escape(COMMENT_TAG_END)))
|
||||
# match a variable or block tag and capture the entire tag, including start/end
|
||||
# delimiters
|
||||
tag_re = (re.compile('(%s.*?%s|%s.*?%s|%s.*?%s)' %
|
||||
(re.escape(BLOCK_TAG_START), re.escape(BLOCK_TAG_END),
|
||||
re.escape(VARIABLE_TAG_START), re.escape(VARIABLE_TAG_END),
|
||||
re.escape(COMMENT_TAG_START), re.escape(COMMENT_TAG_END))))
|
||||
|
||||
# global dictionary of libraries that have been loaded using get_library
|
||||
libraries = {}
|
||||
|
@ -73,7 +85,8 @@ class VariableDoesNotExist(Exception):
|
|||
return unicode(self).encode('utf-8')
|
||||
|
||||
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):
|
||||
pass
|
||||
|
@ -97,11 +110,13 @@ class StringOrigin(Origin):
|
|||
return self.source
|
||||
|
||||
class Template(object):
|
||||
def __init__(self, template_string, origin=None, name='<Unknown Template>'):
|
||||
def __init__(self, template_string, origin=None,
|
||||
name='<Unknown Template>'):
|
||||
try:
|
||||
template_string = smart_unicode(template_string)
|
||||
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:
|
||||
origin = StringOrigin(template_string)
|
||||
self.nodelist = compile_string(template_string, origin)
|
||||
|
@ -136,14 +151,15 @@ def compile_string(template_string, origin):
|
|||
|
||||
class Token(object):
|
||||
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.lineno = None
|
||||
|
||||
def __str__(self):
|
||||
return '<%s token: "%s...">' % \
|
||||
({TOKEN_TEXT: 'Text', TOKEN_VAR: 'Var', TOKEN_BLOCK: 'Block', TOKEN_COMMENT: 'Comment'}[self.token_type],
|
||||
self.contents[:20].replace('\n', ''))
|
||||
token_name = TOKEN_MAPPING[self.token_type]
|
||||
return ('<%s token: "%s...">' %
|
||||
(token_name, self.contents[:20].replace('\n', '')))
|
||||
|
||||
def split_contents(self):
|
||||
split = []
|
||||
|
@ -167,7 +183,9 @@ class Lexer(object):
|
|||
self.lineno = 1
|
||||
|
||||
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
|
||||
result = []
|
||||
for bit in tag_re.split(self.template_string):
|
||||
|
@ -184,13 +202,21 @@ class Lexer(object):
|
|||
"""
|
||||
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())
|
||||
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())
|
||||
token = Token(TOKEN_BLOCK,
|
||||
token_string[
|
||||
len(BLOCK_TAG_START):-len(BLOCK_TAG_END)
|
||||
].strip())
|
||||
elif token_string.startswith(COMMENT_TAG_START):
|
||||
content = ''
|
||||
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)
|
||||
else:
|
||||
token = Token(TOKEN_TEXT, token_string)
|
||||
|
@ -207,7 +233,8 @@ class Parser(object):
|
|||
self.add_library(lib)
|
||||
|
||||
def parse(self, parse_until=None):
|
||||
if parse_until is None: parse_until = []
|
||||
if parse_until is None:
|
||||
parse_until = []
|
||||
nodelist = self.create_nodelist()
|
||||
while self.tokens:
|
||||
token = self.next_token()
|
||||
|
@ -218,17 +245,19 @@ class Parser(object):
|
|||
self.empty_variable(token)
|
||||
filter_expression = self.compile_filter(token.contents)
|
||||
var_node = self.create_variable_node(filter_expression)
|
||||
self.extend_nodelist(nodelist, var_node,token)
|
||||
self.extend_nodelist(nodelist, var_node, token)
|
||||
elif token.token_type == TOKEN_BLOCK:
|
||||
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)
|
||||
return nodelist
|
||||
try:
|
||||
command = token.contents.split()[0]
|
||||
except IndexError:
|
||||
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)
|
||||
try:
|
||||
compile_func = self.tags[command]
|
||||
|
@ -264,7 +293,8 @@ class Parser(object):
|
|||
if nodelist.contains_nontext:
|
||||
raise 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):
|
||||
nodelist.contains_nontext = True
|
||||
nodelist.append(node)
|
||||
|
@ -286,11 +316,12 @@ class Parser(object):
|
|||
|
||||
def invalid_block_tag(self, token, command, parse_until=None):
|
||||
if parse_until:
|
||||
raise self.error(token, "Invalid block tag: '%s', expected %s" % (command, get_text_list(["'%s'" % p for p in parse_until])))
|
||||
raise self.error(token, "Invalid block tag: '%s', expected %s" %
|
||||
(command, get_text_list(["'%s'" % p for p in parse_until])))
|
||||
raise self.error(token, "Invalid block tag: '%s'" % command)
|
||||
|
||||
def unclosed_block_tag(self, parse_until):
|
||||
raise self.error(None, "Unclosed tags: %s " % ', '.join(parse_until))
|
||||
raise self.error(None, "Unclosed tags: %s " % ', '.join(parse_until))
|
||||
|
||||
def compile_function_error(self, token, e):
|
||||
pass
|
||||
|
@ -309,7 +340,9 @@ class Parser(object):
|
|||
self.filters.update(lib.filters)
|
||||
|
||||
def compile_filter(self, token):
|
||||
"Convenient wrapper for FilterExpression"
|
||||
"""
|
||||
Convenient wrapper for FilterExpression
|
||||
"""
|
||||
return FilterExpression(token, self)
|
||||
|
||||
def find_filter(self, filter_name):
|
||||
|
@ -320,8 +353,9 @@ class Parser(object):
|
|||
|
||||
class TokenParser(object):
|
||||
"""
|
||||
Subclass this and implement the top() method to parse a template line. When
|
||||
instantiating the parser, pass in the line from the Django template parser.
|
||||
Subclass this and implement the top() method to parse a template line.
|
||||
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 filter was called with.
|
||||
|
@ -333,25 +367,35 @@ class TokenParser(object):
|
|||
self.tagname = self.tag()
|
||||
|
||||
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()
|
||||
|
||||
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)
|
||||
|
||||
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):
|
||||
raise TemplateSyntaxError("back called without some previous parsing")
|
||||
raise TemplateSyntaxError("back called without some previous "
|
||||
"parsing")
|
||||
self.pointer = self.backout.pop()
|
||||
|
||||
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
|
||||
i = self.pointer
|
||||
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
|
||||
while i < len(subject) and subject[i] not in (' ', '\t'):
|
||||
i += 1
|
||||
|
@ -363,12 +407,18 @@ class TokenParser(object):
|
|||
return s
|
||||
|
||||
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
|
||||
i = self.pointer
|
||||
|
||||
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'):
|
||||
if subject[i] in ('"', "'"):
|
||||
c = subject[i]
|
||||
|
@ -376,22 +426,29 @@ 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" % (i, subject))
|
||||
raise TemplateSyntaxError("Searching for value. "
|
||||
"Unexpected end of string in column %d: %s" %
|
||||
(i, subject))
|
||||
i += 1
|
||||
return i
|
||||
|
||||
if i >= len(subject):
|
||||
raise TemplateSyntaxError("Searching for value. Expected another value but found end of string: %s" % subject)
|
||||
raise TemplateSyntaxError("Searching for value. Expected another "
|
||||
"value but found end of string: %s" %
|
||||
subject)
|
||||
if subject[i] in ('"', "'"):
|
||||
p = i
|
||||
i += 1
|
||||
while i < len(subject) and subject[i] != subject[p]:
|
||||
i += 1
|
||||
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
|
||||
|
||||
# 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)
|
||||
|
||||
res = subject[p:i]
|
||||
|
@ -419,10 +476,10 @@ constant_string = r"""
|
|||
%(strdq)s|
|
||||
%(strsq)s)
|
||||
""" % {
|
||||
'strdq': r'"[^"\\]*(?:\\.[^"\\]*)*"', # double-quoted string
|
||||
'strsq': r"'[^'\\]*(?:\\.[^'\\]*)*'", # single-quoted string
|
||||
'i18n_open' : re.escape("_("),
|
||||
'i18n_close' : re.escape(")"),
|
||||
'strdq': r'"[^"\\]*(?:\\.[^"\\]*)*"', # double-quoted string
|
||||
'strsq': r"'[^'\\]*(?:\\.[^'\\]*)*'", # single-quoted string
|
||||
'i18n_open': re.escape("_("),
|
||||
'i18n_close': re.escape(")"),
|
||||
}
|
||||
constant_string = constant_string.replace("\n", "")
|
||||
|
||||
|
@ -440,18 +497,19 @@ filter_raw_string = r"""
|
|||
)""" % {
|
||||
'constant': constant_string,
|
||||
'num': r'[-+\.]?\d[\d\.e]*',
|
||||
'var_chars': "\w\." ,
|
||||
'var_chars': "\w\.",
|
||||
'filter_sep': re.escape(FILTER_SEPARATOR),
|
||||
'arg_sep': re.escape(FILTER_ARGUMENT_SEPARATOR),
|
||||
}
|
||||
|
||||
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):
|
||||
r"""
|
||||
"""
|
||||
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.
|
||||
Sample:
|
||||
Sample::
|
||||
|
||||
>>> token = 'variable|default:"Default value"|date:"Y-m-d"'
|
||||
>>> p = Parser('')
|
||||
>>> fe = FilterExpression(token, p)
|
||||
|
@ -472,8 +530,10 @@ class FilterExpression(object):
|
|||
for match in matches:
|
||||
start = match.start()
|
||||
if upto != start:
|
||||
raise TemplateSyntaxError("Could not parse some characters: %s|%s|%s" % \
|
||||
(token[:upto], token[upto:start], token[start:]))
|
||||
raise TemplateSyntaxError("Could not parse some characters: "
|
||||
"%s|%s|%s" %
|
||||
(token[:upto], token[upto:start],
|
||||
token[start:]))
|
||||
if var_obj is None:
|
||||
var, constant = match.group("var", "constant")
|
||||
if constant:
|
||||
|
@ -482,7 +542,8 @@ class FilterExpression(object):
|
|||
except VariableDoesNotExist:
|
||||
var_obj = 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:
|
||||
var_obj = Variable(var)
|
||||
else:
|
||||
|
@ -498,7 +559,8 @@ class FilterExpression(object):
|
|||
filters.append((filter_func, args))
|
||||
upto = match.end()
|
||||
if upto != len(token):
|
||||
raise TemplateSyntaxError("Could not parse the remainder: '%s' from '%s'" % (token[upto:], token))
|
||||
raise TemplateSyntaxError("Could not parse the remainder: '%s' "
|
||||
"from '%s'" % (token[upto:], token))
|
||||
|
||||
self.filters = filters
|
||||
self.var = var_obj
|
||||
|
@ -559,7 +621,8 @@ class FilterExpression(object):
|
|||
provided.pop(0)
|
||||
except IndexError:
|
||||
# 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 = defaults and list(defaults) or []
|
||||
|
@ -568,7 +631,8 @@ class FilterExpression(object):
|
|||
defaults.pop(0)
|
||||
except IndexError:
|
||||
# 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
|
||||
args_check = staticmethod(args_check)
|
||||
|
@ -586,9 +650,9 @@ def resolve_variable(path, context):
|
|||
return Variable(path).resolve(context)
|
||||
|
||||
class Variable(object):
|
||||
r"""
|
||||
A template variable, resolvable against a given context. The variable may be
|
||||
a hard-coded string (if it begins and ends with single or double quote
|
||||
"""
|
||||
A template variable, resolvable against a given context. The variable may
|
||||
be a hard-coded string (if it begins and ends with single or double quote
|
||||
marks)::
|
||||
|
||||
>>> c = {'article': {'section':u'News'}}
|
||||
|
@ -642,7 +706,9 @@ class Variable(object):
|
|||
# Otherwise we'll set self.lookups so that resolve() knows we're
|
||||
# dealing with a bonafide variable
|
||||
if var.find(VARIABLE_ATTRIBUTE_SEPARATOR + '_') > -1 or var[0] == '_':
|
||||
raise TemplateSyntaxError("Variables and attributes may not begin with underscores: '%s'" % var)
|
||||
raise TemplateSyntaxError("Variables and attributes may "
|
||||
"not begin with underscores: '%s'" %
|
||||
var)
|
||||
self.lookups = tuple(var.split(VARIABLE_ATTRIBUTE_SEPARATOR))
|
||||
|
||||
def resolve(self, context):
|
||||
|
@ -673,22 +739,23 @@ class Variable(object):
|
|||
instead.
|
||||
"""
|
||||
current = context
|
||||
try: # catch-all for silent variable failures
|
||||
try: # catch-all for silent variable failures
|
||||
for bit in self.lookups:
|
||||
try: # dictionary lookup
|
||||
try: # dictionary lookup
|
||||
current = current[bit]
|
||||
except (TypeError, AttributeError, KeyError):
|
||||
try: # attribute lookup
|
||||
try: # attribute lookup
|
||||
current = getattr(current, bit)
|
||||
except (TypeError, AttributeError):
|
||||
try: # list-index lookup
|
||||
try: # list-index lookup
|
||||
current = current[int(bit)]
|
||||
except (IndexError, # list index out of range
|
||||
ValueError, # invalid literal for int()
|
||||
KeyError, # current is a dict without `int(bit)` key
|
||||
TypeError, # unsubscriptable object
|
||||
):
|
||||
raise VariableDoesNotExist("Failed lookup for key [%s] in %r", (bit, current)) # missing attribute
|
||||
except (IndexError, # list index out of range
|
||||
ValueError, # invalid literal for int()
|
||||
KeyError, # current is a dict without `int(bit)` key
|
||||
TypeError): # unsubscriptable object
|
||||
raise VariableDoesNotExist("Failed lookup for key "
|
||||
"[%s] in %r",
|
||||
(bit, current)) # missing attribute
|
||||
if callable(current):
|
||||
if getattr(current, 'do_not_call_in_templates', False):
|
||||
pass
|
||||
|
@ -700,7 +767,7 @@ class Variable(object):
|
|||
except TypeError: # arguments *were* required
|
||||
# GOTCHA: This will also catch any TypeError
|
||||
# raised in the function itself.
|
||||
current = settings.TEMPLATE_STRING_IF_INVALID # invalid method call
|
||||
current = settings.TEMPLATE_STRING_IF_INVALID # invalid method call
|
||||
except Exception, e:
|
||||
if getattr(e, 'silent_variable_failure', False):
|
||||
current = settings.TEMPLATE_STRING_IF_INVALID
|
||||
|
@ -716,14 +783,19 @@ class Node(object):
|
|||
child_nodelists = ('nodelist',)
|
||||
|
||||
def render(self, context):
|
||||
"Return the node rendered as a string"
|
||||
"""
|
||||
Return the node rendered as a string.
|
||||
"""
|
||||
pass
|
||||
|
||||
def __iter__(self):
|
||||
yield self
|
||||
|
||||
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 = []
|
||||
if isinstance(self, nodetype):
|
||||
nodes.append(self)
|
||||
|
@ -776,7 +848,8 @@ def _render_value_in_context(value, context):
|
|||
"""
|
||||
value = localize(value, use_l10n=context.use_l10n)
|
||||
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)
|
||||
else:
|
||||
return value
|
||||
|
@ -793,23 +866,159 @@ class VariableNode(Node):
|
|||
output = self.filter_expression.resolve(context)
|
||||
except UnicodeDecodeError:
|
||||
# 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 _render_value_in_context(output, context)
|
||||
|
||||
def generic_tag_compiler(params, defaults, name, node_class, parser, token):
|
||||
"Returns a template.Node subclass."
|
||||
bits = token.split_contents()[1:]
|
||||
bmax = len(params)
|
||||
def_len = defaults and len(defaults) or 0
|
||||
bmin = bmax - def_len
|
||||
if(len(bits) < bmin or len(bits) > bmax):
|
||||
if bmin == bmax:
|
||||
message = "%s takes %s arguments" % (name, bmin)
|
||||
# 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:
|
||||
message = "%s takes between %s and %s arguments" % (name, bmin, bmax)
|
||||
raise TemplateSyntaxError(message)
|
||||
return node_class(bits)
|
||||
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
|
||||
|
||||
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):
|
||||
def __init__(self):
|
||||
|
@ -817,10 +1026,10 @@ class Library(object):
|
|||
self.tags = {}
|
||||
|
||||
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()
|
||||
return self.tag_function
|
||||
elif name != None and compile_function == None:
|
||||
elif name is not None and compile_function is None:
|
||||
if callable(name):
|
||||
# @register.tag
|
||||
return self.tag_function(name)
|
||||
|
@ -829,22 +1038,23 @@ class Library(object):
|
|||
def dec(func):
|
||||
return self.tag(name, func)
|
||||
return dec
|
||||
elif name != None and compile_function != None:
|
||||
elif name is not None and compile_function is not None:
|
||||
# register.tag('somename', somefunc)
|
||||
self.tags[name] = compile_function
|
||||
return compile_function
|
||||
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
|
||||
return func
|
||||
|
||||
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()
|
||||
return self.filter_function
|
||||
elif filter_func == None:
|
||||
elif filter_func is None:
|
||||
if callable(name):
|
||||
# @register.filter
|
||||
return self.filter_function(name)
|
||||
|
@ -853,12 +1063,13 @@ class Library(object):
|
|||
def dec(func):
|
||||
return self.filter(name, func)
|
||||
return dec
|
||||
elif name != None and filter_func != None:
|
||||
elif name is not None and filter_func is not None:
|
||||
# register.filter('somename', somefunc)
|
||||
self.filters[name] = filter_func
|
||||
return filter_func
|
||||
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):
|
||||
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 dec(func):
|
||||
params, xx, xxx, 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'")
|
||||
params, varargs, varkw, defaults = getargspec(func)
|
||||
|
||||
class SimpleNode(Node):
|
||||
def __init__(self, vars_to_resolve):
|
||||
self.vars_to_resolve = map(Variable, vars_to_resolve)
|
||||
class SimpleNode(TagHelperNode):
|
||||
|
||||
def render(self, context):
|
||||
resolved_vars = [var.resolve(context) for var in self.vars_to_resolve]
|
||||
if takes_context:
|
||||
func_args = [context] + resolved_vars
|
||||
else:
|
||||
func_args = resolved_vars
|
||||
return func(*func_args)
|
||||
resolved_args, resolved_kwargs = self.get_resolved_arguments(context)
|
||||
return func(*resolved_args, **resolved_kwargs)
|
||||
|
||||
function_name = name or getattr(func, '_decorated_function', func).__name__
|
||||
compile_func = partial(generic_tag_compiler, params, defaults, function_name, SimpleNode)
|
||||
function_name = (name or
|
||||
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__
|
||||
self.tag(function_name, compile_func)
|
||||
return func
|
||||
|
@ -902,52 +1106,33 @@ class Library(object):
|
|||
|
||||
def assignment_tag(self, func=None, takes_context=None, name=None):
|
||||
def dec(func):
|
||||
params, xx, xxx, 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'")
|
||||
params, varargs, varkw, defaults = getargspec(func)
|
||||
|
||||
class AssignmentNode(Node):
|
||||
def __init__(self, params_vars, target_var):
|
||||
self.params_vars = map(Variable, params_vars)
|
||||
class AssignmentNode(TagHelperNode):
|
||||
def __init__(self, takes_context, args, kwargs, target_var):
|
||||
super(AssignmentNode, self).__init__(takes_context, args, kwargs)
|
||||
self.target_var = target_var
|
||||
|
||||
def render(self, context):
|
||||
resolved_vars = [var.resolve(context) for var in self.params_vars]
|
||||
if takes_context:
|
||||
func_args = [context] + resolved_vars
|
||||
else:
|
||||
func_args = resolved_vars
|
||||
context[self.target_var] = func(*func_args)
|
||||
resolved_args, resolved_kwargs = self.get_resolved_arguments(context)
|
||||
context[self.target_var] = func(*resolved_args, **resolved_kwargs)
|
||||
return ''
|
||||
|
||||
function_name = (name or
|
||||
getattr(func, '_decorated_function', func).__name__)
|
||||
|
||||
def compile_func(parser, token):
|
||||
bits = token.split_contents()
|
||||
tag_name = bits[0]
|
||||
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'):
|
||||
bits = token.split_contents()[1:]
|
||||
if len(bits) < 2 or bits[-2] != 'as':
|
||||
raise TemplateSyntaxError(
|
||||
"'%s' tag takes at least 2 arguments and the "
|
||||
"second last argument must be 'as'" % tag_name)
|
||||
params_vars = bits[:-2]
|
||||
"second last argument must be 'as'" % function_name)
|
||||
target_var = bits[-1]
|
||||
if (len(params_vars) < params_min or
|
||||
len(params_vars) > params_max):
|
||||
if params_min == params_max:
|
||||
raise TemplateSyntaxError(
|
||||
"%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)
|
||||
bits = bits[:-2]
|
||||
args, kwargs = parse_bits(parser, bits, params,
|
||||
varargs, varkw, defaults, takes_context, function_name)
|
||||
return AssignmentNode(takes_context, args, kwargs, target_var)
|
||||
|
||||
function_name = name or getattr(func, '_decorated_function', func).__name__
|
||||
compile_func.__doc__ = func.__doc__
|
||||
self.tag(function_name, compile_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 dec(func):
|
||||
params, xx, xxx, 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'")
|
||||
params, varargs, varkw, defaults = getargspec(func)
|
||||
|
||||
class InclusionNode(Node):
|
||||
def __init__(self, vars_to_resolve):
|
||||
self.vars_to_resolve = map(Variable, vars_to_resolve)
|
||||
class InclusionNode(TagHelperNode):
|
||||
|
||||
def render(self, context):
|
||||
resolved_vars = [var.resolve(context) for var in self.vars_to_resolve]
|
||||
if takes_context:
|
||||
args = [context] + resolved_vars
|
||||
else:
|
||||
args = resolved_vars
|
||||
|
||||
dict = func(*args)
|
||||
resolved_args, resolved_kwargs = self.get_resolved_arguments(context)
|
||||
_dict = func(*resolved_args, **resolved_kwargs)
|
||||
|
||||
if not getattr(self, 'nodelist', False):
|
||||
from django.template.loader import get_template, select_template
|
||||
|
@ -992,62 +1165,73 @@ class Library(object):
|
|||
else:
|
||||
t = get_template(file_name)
|
||||
self.nodelist = t.nodelist
|
||||
new_context = context_class(dict, **{
|
||||
new_context = context_class(_dict, **{
|
||||
'autoescape': context.autoescape,
|
||||
'current_app': context.current_app,
|
||||
'use_l10n': context.use_l10n,
|
||||
})
|
||||
# Copy across the CSRF token, if present, because inclusion
|
||||
# tags are often used for forms, and we need instructions
|
||||
# for using CSRF protection to be as simple as possible.
|
||||
# Copy across the CSRF token, if present, because
|
||||
# inclusion tags are often used for forms, and we need
|
||||
# instructions for using CSRF protection to be as simple
|
||||
# as possible.
|
||||
csrf_token = context.get('csrf_token', None)
|
||||
if csrf_token is not None:
|
||||
new_context['csrf_token'] = csrf_token
|
||||
return self.nodelist.render(new_context)
|
||||
|
||||
function_name = name or getattr(func, '_decorated_function', func).__name__
|
||||
compile_func = partial(generic_tag_compiler, params, defaults, function_name, InclusionNode)
|
||||
function_name = (name or
|
||||
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__
|
||||
self.tag(function_name, compile_func)
|
||||
return func
|
||||
return dec
|
||||
|
||||
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
|
||||
returns that attribute as the representation of the library
|
||||
"""
|
||||
app_path, taglib = taglib_module.rsplit('.',1)
|
||||
app_path, taglib = taglib_module.rsplit('.', 1)
|
||||
app_module = import_module(app_path)
|
||||
try:
|
||||
mod = import_module(taglib_module)
|
||||
except ImportError, e:
|
||||
# If the ImportError is because the taglib submodule does not exist, that's not
|
||||
# an error that should be raised. If the submodule exists and raised an ImportError
|
||||
# on the attempt to load it, that we want to raise.
|
||||
# If the ImportError is because the taglib submodule does not exist,
|
||||
# that's not an error that should be raised. If the submodule exists
|
||||
# and raised an ImportError on the attempt to load it, that we want
|
||||
# to raise.
|
||||
if not module_has_submodule(app_module, taglib):
|
||||
return None
|
||||
else:
|
||||
raise InvalidTemplateLibrary("ImportError raised loading %s: %s" % (taglib_module, e))
|
||||
raise InvalidTemplateLibrary("ImportError raised loading %s: %s" %
|
||||
(taglib_module, e))
|
||||
try:
|
||||
return mod.register
|
||||
except AttributeError:
|
||||
raise InvalidTemplateLibrary("Template library %s does not have a variable named 'register'" % taglib_module)
|
||||
raise InvalidTemplateLibrary("Template library %s does not have "
|
||||
"a variable named 'register'" %
|
||||
taglib_module)
|
||||
|
||||
templatetags_modules = []
|
||||
|
||||
def get_templatetags_modules():
|
||||
"""Return the list of all available template tag modules.
|
||||
"""
|
||||
Return the list of all available template tag modules.
|
||||
|
||||
Caches the result for faster access.
|
||||
"""
|
||||
global templatetags_modules
|
||||
if not templatetags_modules:
|
||||
_templatetags_modules = []
|
||||
# Populate list once per process. Mutate the local list first, and then
|
||||
# assign it to the global name to ensure there are no cases where two
|
||||
# threads try to populate it simultaneously.
|
||||
# Populate list once per process. Mutate the local list first, and
|
||||
# then assign it to the global name to ensure there are no cases where
|
||||
# two threads try to populate it simultaneously.
|
||||
for app_module in ['django'] + list(settings.INSTALLED_APPS):
|
||||
try:
|
||||
templatetag_module = '%s.templatetags' % app_module
|
||||
|
@ -1062,12 +1246,13 @@ def get_library(library_name):
|
|||
"""
|
||||
Load the template library module with the given name.
|
||||
|
||||
If library is not already loaded loop over all templatetags modules to locate it.
|
||||
If library is not already loaded loop over all templatetags modules
|
||||
to locate it.
|
||||
|
||||
{% load somelib %} and {% load someotherlib %} loops twice.
|
||||
|
||||
Subsequent loads eg. {% load somelib %} in the same process will grab the cached
|
||||
module from libraries.
|
||||
Subsequent loads eg. {% load somelib %} in the same process will grab
|
||||
the cached module from libraries.
|
||||
"""
|
||||
lib = libraries.get(library_name, None)
|
||||
if not lib:
|
||||
|
@ -1081,11 +1266,16 @@ def get_library(library_name):
|
|||
libraries[library_name] = lib
|
||||
break
|
||||
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
|
||||
|
||||
|
||||
def add_to_builtins(module):
|
||||
builtins.append(import_library(module))
|
||||
|
||||
|
||||
add_to_builtins('django.template.defaulttags')
|
||||
add_to_builtins('django.template.defaultfilters')
|
||||
|
|
|
@ -10,64 +10,13 @@ from django.template.base import (Node, NodeList, Template, Library,
|
|||
TemplateSyntaxError, VariableDoesNotExist, InvalidTemplateLibrary,
|
||||
BLOCK_TAG_START, BLOCK_TAG_END, VARIABLE_TAG_START, VARIABLE_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.defaultfilters import date
|
||||
from django.utils.encoding import smart_str, smart_unicode
|
||||
from django.utils.safestring import mark_safe
|
||||
|
||||
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):
|
||||
"""Implements the actions of the autoescape tag."""
|
||||
|
|
|
@ -698,6 +698,29 @@ If you need to rename your tag, you can provide a custom name for it::
|
|||
def some_function(value):
|
||||
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:
|
||||
|
||||
Assignment tags
|
||||
|
@ -761,6 +784,27 @@ Or, using decorator syntax:
|
|||
For more information on how the ``takes_context`` option works, see the section
|
||||
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:
|
||||
|
||||
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
|
||||
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
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
|
|
@ -162,6 +162,31 @@ A new helper function,
|
|||
``template.Library`` to ease the creation of template tags that store some
|
||||
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
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
|
|
@ -35,6 +35,56 @@ class CustomTagTests(TestCase):
|
|||
t = template.Template('{% load custom %}{% params_and_context 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):
|
||||
# Test that the decorators preserve the decorated function's docstring, name and attributes.
|
||||
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.no_params_with_context, 'no_params_with_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):
|
||||
# That the 'context' parameter must be present when takes_context is True
|
||||
def a_simple_tag_without_parameters(arg):
|
||||
"""Expected __doc__"""
|
||||
return "Expected result"
|
||||
|
||||
register = template.Library()
|
||||
decorator = register.simple_tag(takes_context=True)
|
||||
self.assertRaises(template.TemplateSyntaxError, decorator, a_simple_tag_without_parameters)
|
||||
# The 'context' parameter must be present when takes_context is True
|
||||
self.assertRaisesRegexp(template.TemplateSyntaxError,
|
||||
"'simple_tag_without_context_parameter' is decorated with takes_context=True so it must have a first argument of 'context'",
|
||||
template.Template, '{% load custom %}{% simple_tag_without_context_parameter 123 %}')
|
||||
|
||||
def test_inclusion_tags(self):
|
||||
c = template.Context({'value': 42})
|
||||
|
@ -71,6 +119,70 @@ class CustomTagTests(TestCase):
|
|||
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')
|
||||
|
||||
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):
|
||||
c = template.Context({'value': 42})
|
||||
|
||||
|
@ -89,6 +201,27 @@ class CustomTagTests(TestCase):
|
|||
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')
|
||||
|
||||
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):
|
||||
# Test that the decorators preserve the decorated function's docstring, name and attributes.
|
||||
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_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_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):
|
||||
"""
|
||||
|
@ -139,6 +280,37 @@ class CustomTagTests(TestCase):
|
|||
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')
|
||||
|
||||
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,
|
||||
"'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 }}')
|
||||
|
@ -151,6 +323,33 @@ class CustomTagTests(TestCase):
|
|||
"'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 }}')
|
||||
|
||||
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):
|
||||
# Test that the decorators preserve the decorated function's docstring, name and attributes.
|
||||
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_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_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):
|
||||
# That 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)
|
||||
|
||||
# The 'context' parameter must be present when takes_context is True
|
||||
self.assertRaisesRegexp(template.TemplateSyntaxError,
|
||||
"Any tag function decorated with takes_context=True must have a first argument of 'context'",
|
||||
decorator, an_assignment_tag_without_parameters)
|
||||
"'assignment_tag_without_context_parameter' is decorated with takes_context=True so it must have a first argument of 'context'",
|
||||
template.Template, '{% load custom %}{% assignment_tag_without_context_parameter 123 as var %}')
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import operator
|
||||
|
||||
from django import template
|
||||
from django.template.defaultfilters import stringfilter
|
||||
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)
|
||||
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')
|
||||
def inclusion_no_params():
|
||||
"""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)}
|
||||
inclusion_params_and_context_from_template.anything = "Expected inclusion_params_and_context_from_template __dict__"
|
||||
|
||||
@register.simple_tag(takes_context=True)
|
||||
def current_app(context):
|
||||
return "%s" % context.current_app
|
||||
@register.inclusion_tag('inclusion.html')
|
||||
def inclusion_two_params(one, two):
|
||||
"""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)
|
||||
def inclusion_tag_current_app(context):
|
||||
"""Expected inclusion_tag_current_app __doc__"""
|
||||
return {}
|
||||
|
||||
@register.simple_tag(takes_context=True)
|
||||
def use_l10n(context):
|
||||
return "%s" % context.use_l10n
|
||||
inclusion_tag_current_app.anything = "Expected inclusion_tag_current_app __dict__"
|
||||
|
||||
@register.inclusion_tag('test_incl_tag_use_l10n.html', takes_context=True)
|
||||
def inclusion_tag_use_l10n(context):
|
||||
"""Expected inclusion_tag_use_l10n __doc__"""
|
||||
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
|
||||
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)
|
||||
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')
|
||||
def minustwo_overridden_name(value):
|
||||
return value - 2
|
||||
@register.assignment_tag
|
||||
def assignment_one_default(one, two='hi'):
|
||||
"""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__"
|
Loading…
Reference in New Issue