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 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
|
||||||
re.escape(VARIABLE_TAG_START), re.escape(VARIABLE_TAG_END),
|
tag_re = (re.compile('(%s.*?%s|%s.*?%s|%s.*?%s)' %
|
||||||
re.escape(COMMENT_TAG_START), re.escape(COMMENT_TAG_END)))
|
(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
|
# 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()
|
||||||
|
@ -218,17 +245,19 @@ class Parser(object):
|
||||||
self.empty_variable(token)
|
self.empty_variable(token)
|
||||||
filter_expression = self.compile_filter(token.contents)
|
filter_expression = self.compile_filter(token.contents)
|
||||||
var_node = self.create_variable_node(filter_expression)
|
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:
|
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,11 +316,12 @@ 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):
|
||||||
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):
|
def compile_function_error(self, token, e):
|
||||||
pass
|
pass
|
||||||
|
@ -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]
|
||||||
|
@ -419,10 +476,10 @@ constant_string = r"""
|
||||||
%(strdq)s|
|
%(strdq)s|
|
||||||
%(strsq)s)
|
%(strsq)s)
|
||||||
""" % {
|
""" % {
|
||||||
'strdq': r'"[^"\\]*(?:\\.[^"\\]*)*"', # double-quoted string
|
'strdq': r'"[^"\\]*(?:\\.[^"\\]*)*"', # double-quoted string
|
||||||
'strsq': r"'[^'\\]*(?:\\.[^'\\]*)*'", # single-quoted string
|
'strsq': r"'[^'\\]*(?:\\.[^'\\]*)*'", # single-quoted string
|
||||||
'i18n_open' : re.escape("_("),
|
'i18n_open': re.escape("_("),
|
||||||
'i18n_close' : re.escape(")"),
|
'i18n_close': re.escape(")"),
|
||||||
}
|
}
|
||||||
constant_string = constant_string.replace("\n", "")
|
constant_string = constant_string.replace("\n", "")
|
||||||
|
|
||||||
|
@ -440,18 +497,19 @@ filter_raw_string = r"""
|
||||||
)""" % {
|
)""" % {
|
||||||
'constant': constant_string,
|
'constant': constant_string,
|
||||||
'num': r'[-+\.]?\d[\d\.e]*',
|
'num': r'[-+\.]?\d[\d\.e]*',
|
||||||
'var_chars': "\w\." ,
|
'var_chars': "\w\.",
|
||||||
'filter_sep': re.escape(FILTER_SEPARATOR),
|
'filter_sep': re.escape(FILTER_SEPARATOR),
|
||||||
'arg_sep': re.escape(FILTER_ARGUMENT_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):
|
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):
|
||||||
|
@ -673,22 +739,23 @@ class Variable(object):
|
||||||
instead.
|
instead.
|
||||||
"""
|
"""
|
||||||
current = context
|
current = context
|
||||||
try: # catch-all for silent variable failures
|
try: # catch-all for silent variable failures
|
||||||
for bit in self.lookups:
|
for bit in self.lookups:
|
||||||
try: # dictionary lookup
|
try: # dictionary lookup
|
||||||
current = current[bit]
|
current = current[bit]
|
||||||
except (TypeError, AttributeError, KeyError):
|
except (TypeError, AttributeError, KeyError):
|
||||||
try: # attribute lookup
|
try: # attribute lookup
|
||||||
current = getattr(current, bit)
|
current = getattr(current, bit)
|
||||||
except (TypeError, AttributeError):
|
except (TypeError, AttributeError):
|
||||||
try: # list-index lookup
|
try: # list-index lookup
|
||||||
current = current[int(bit)]
|
current = current[int(bit)]
|
||||||
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
|
||||||
|
@ -700,7 +767,7 @@ class Variable(object):
|
||||||
except TypeError: # arguments *were* required
|
except TypeError: # arguments *were* required
|
||||||
# GOTCHA: This will also catch any TypeError
|
# GOTCHA: This will also catch any TypeError
|
||||||
# raised in the function itself.
|
# 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:
|
except Exception, e:
|
||||||
if getattr(e, 'silent_variable_failure', False):
|
if getattr(e, 'silent_variable_failure', False):
|
||||||
current = settings.TEMPLATE_STRING_IF_INVALID
|
current = settings.TEMPLATE_STRING_IF_INVALID
|
||||||
|
@ -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,62 +1165,73 @@ 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
|
||||||
"""
|
"""
|
||||||
app_path, taglib = taglib_module.rsplit('.',1)
|
app_path, taglib = taglib_module.rsplit('.', 1)
|
||||||
app_module = import_module(app_path)
|
app_module = import_module(app_path)
|
||||||
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')
|
||||||
|
|
|
@ -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."""
|
||||||
|
|
|
@ -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
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
|
|
@ -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 %}')
|
||||||
|
|
|
@ -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__"
|
Loading…
Reference in New Issue