Ticket #13956: 13956.ttag_helpers_args_kwargs_support.3.diff

File 13956.ttag_helpers_args_kwargs_support.3.diff, 82.5 KB (added by Julien Phalip, 13 years ago)

Final patch. Will commit shortly.

  • django/template/base.py

    diff --git a/django/template/base.py b/django/template/base.py
    index c94eeb5..8642f4d 100644
    a b from functools import partial  
    33from inspect import getargspec
    44
    55from django.conf import settings
    6 from django.template.context import Context, RequestContext, ContextPopException
     6from django.template.context import (Context, RequestContext,
     7    ContextPopException)
    78from django.utils.importlib import import_module
    89from django.utils.itercompat import is_iterable
    9 from django.utils.text import smart_split, unescape_string_literal, get_text_list
     10from django.utils.text import (smart_split, unescape_string_literal,
     11    get_text_list)
    1012from django.utils.encoding import smart_unicode, force_unicode, smart_str
    1113from django.utils.translation import ugettext_lazy
    12 from django.utils.safestring import SafeData, EscapeData, mark_safe, mark_for_escaping
     14from django.utils.safestring import (SafeData, EscapeData, mark_safe,
     15    mark_for_escaping)
    1316from django.utils.formats import localize
    1417from django.utils.html import escape
    1518from django.utils.module_loading import module_has_submodule
    TOKEN_TEXT = 0  
    1922TOKEN_VAR = 1
    2023TOKEN_BLOCK = 2
    2124TOKEN_COMMENT = 3
     25TOKEN_MAPPING = {
     26    TOKEN_TEXT: 'Text',
     27    TOKEN_VAR: 'Var',
     28    TOKEN_BLOCK: 'Block',
     29    TOKEN_COMMENT: 'Comment',
     30}
    2231
    2332# template syntax constants
    2433FILTER_SEPARATOR = '|'
    TRANSLATOR_COMMENT_MARK = 'Translators'  
    3443SINGLE_BRACE_START = '{'
    3544SINGLE_BRACE_END = '}'
    3645
    37 ALLOWED_VARIABLE_CHARS = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_.'
     46ALLOWED_VARIABLE_CHARS = ('abcdefghijklmnopqrstuvwxyz'
     47                         'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_.')
    3848
    3949# what to report as the origin for templates that come from non-loader sources
    4050# (e.g. strings)
    4151UNKNOWN_SOURCE = '<unknown source>'
    4252
    43 # match a variable or block tag and capture the entire tag, including start/end delimiters
    44 tag_re = re.compile('(%s.*?%s|%s.*?%s|%s.*?%s)' % (re.escape(BLOCK_TAG_START), re.escape(BLOCK_TAG_END),
    45                                           re.escape(VARIABLE_TAG_START), re.escape(VARIABLE_TAG_END),
    46                                           re.escape(COMMENT_TAG_START), re.escape(COMMENT_TAG_END)))
     53# match a variable or block tag and capture the entire tag, including start/end
     54# delimiters
     55tag_re = (re.compile('(%s.*?%s|%s.*?%s|%s.*?%s)' %
     56          (re.escape(BLOCK_TAG_START), re.escape(BLOCK_TAG_END),
     57           re.escape(VARIABLE_TAG_START), re.escape(VARIABLE_TAG_END),
     58           re.escape(COMMENT_TAG_START), re.escape(COMMENT_TAG_END))))
    4759
    4860# global dictionary of libraries that have been loaded using get_library
    4961libraries = {}
    class VariableDoesNotExist(Exception):  
    7385        return unicode(self).encode('utf-8')
    7486
    7587    def __unicode__(self):
    76         return self.msg % tuple([force_unicode(p, errors='replace') for p in self.params])
     88        return self.msg % tuple([force_unicode(p, errors='replace')
     89                                 for p in self.params])
    7790
    7891class InvalidTemplateLibrary(Exception):
    7992    pass
    class StringOrigin(Origin):  
    97110        return self.source
    98111
    99112class Template(object):
    100     def __init__(self, template_string, origin=None, name='<Unknown Template>'):
     113    def __init__(self, template_string, origin=None,
     114                 name='<Unknown Template>'):
    101115        try:
    102116            template_string = smart_unicode(template_string)
    103117        except UnicodeDecodeError:
    104             raise TemplateEncodingError("Templates can only be constructed from unicode or UTF-8 strings.")
     118            raise TemplateEncodingError("Templates can only be constructed "
     119                                        "from unicode or UTF-8 strings.")
    105120        if settings.TEMPLATE_DEBUG and origin is None:
    106121            origin = StringOrigin(template_string)
    107122        self.nodelist = compile_string(template_string, origin)
    def compile_string(template_string, origin):  
    136151
    137152class Token(object):
    138153    def __init__(self, token_type, contents):
    139         # token_type must be TOKEN_TEXT, TOKEN_VAR, TOKEN_BLOCK or TOKEN_COMMENT.
     154        # token_type must be TOKEN_TEXT, TOKEN_VAR, TOKEN_BLOCK or
     155        # TOKEN_COMMENT.
    140156        self.token_type, self.contents = token_type, contents
    141157        self.lineno = None
    142158
    143159    def __str__(self):
    144         return '<%s token: "%s...">' % \
    145             ({TOKEN_TEXT: 'Text', TOKEN_VAR: 'Var', TOKEN_BLOCK: 'Block', TOKEN_COMMENT: 'Comment'}[self.token_type],
    146             self.contents[:20].replace('\n', ''))
     160        token_name = TOKEN_MAPPING[self.token_type]
     161        return ('<%s token: "%s...">' %
     162                (token_name, self.contents[:20].replace('\n', '')))
    147163
    148164    def split_contents(self):
    149165        split = []
    class Lexer(object):  
    167183        self.lineno = 1
    168184
    169185    def tokenize(self):
    170         "Return a list of tokens from a given template_string."
     186        """
     187        Return a list of tokens from a given template_string.
     188        """
    171189        in_tag = False
    172190        result = []
    173191        for bit in tag_re.split(self.template_string):
    class Lexer(object):  
    184202        """
    185203        if in_tag:
    186204            if token_string.startswith(VARIABLE_TAG_START):
    187                 token = Token(TOKEN_VAR, token_string[len(VARIABLE_TAG_START):-len(VARIABLE_TAG_END)].strip())
     205                token = Token(TOKEN_VAR,
     206                              token_string[
     207                                len(VARIABLE_TAG_START):-len(VARIABLE_TAG_END)
     208                              ].strip())
    188209            elif token_string.startswith(BLOCK_TAG_START):
    189                 token = Token(TOKEN_BLOCK, token_string[len(BLOCK_TAG_START):-len(BLOCK_TAG_END)].strip())
     210                token = Token(TOKEN_BLOCK,
     211                              token_string[
     212                                len(BLOCK_TAG_START):-len(BLOCK_TAG_END)
     213                              ].strip())
    190214            elif token_string.startswith(COMMENT_TAG_START):
    191215                content = ''
    192216                if token_string.find(TRANSLATOR_COMMENT_MARK):
    193                     content = token_string[len(COMMENT_TAG_START):-len(COMMENT_TAG_END)].strip()
     217                    content = token_string[
     218                                len(COMMENT_TAG_START):-len(COMMENT_TAG_END)
     219                              ].strip()
    194220                token = Token(TOKEN_COMMENT, content)
    195221        else:
    196222            token = Token(TOKEN_TEXT, token_string)
    class Parser(object):  
    207233            self.add_library(lib)
    208234
    209235    def parse(self, parse_until=None):
    210         if parse_until is None: parse_until = []
     236        if parse_until is None:
     237            parse_until = []
    211238        nodelist = self.create_nodelist()
    212239        while self.tokens:
    213240            token = self.next_token()
    class Parser(object):  
    218245                    self.empty_variable(token)
    219246                filter_expression = self.compile_filter(token.contents)
    220247                var_node = self.create_variable_node(filter_expression)
    221                 self.extend_nodelist(nodelist, var_node,token)
     248                self.extend_nodelist(nodelist, var_node, token)
    222249            elif token.token_type == TOKEN_BLOCK:
    223250                if token.contents in parse_until:
    224                     # put token back on token list so calling code knows why it terminated
     251                    # put token back on token list so calling
     252                    # code knows why it terminated
    225253                    self.prepend_token(token)
    226254                    return nodelist
    227255                try:
    228256                    command = token.contents.split()[0]
    229257                except IndexError:
    230258                    self.empty_block_tag(token)
    231                 # execute callback function for this tag and append resulting node
     259                # execute callback function for this tag and append
     260                # resulting node
    232261                self.enter_command(command, token)
    233262                try:
    234263                    compile_func = self.tags[command]
    class Parser(object):  
    264293                if nodelist.contains_nontext:
    265294                    raise AttributeError
    266295            except AttributeError:
    267                 raise TemplateSyntaxError("%r must be the first tag in the template." % node)
     296                raise TemplateSyntaxError("%r must be the first tag "
     297                                          "in the template." % node)
    268298        if isinstance(nodelist, NodeList) and not isinstance(node, TextNode):
    269299            nodelist.contains_nontext = True
    270300        nodelist.append(node)
    class Parser(object):  
    286316
    287317    def invalid_block_tag(self, token, command, parse_until=None):
    288318        if parse_until:
    289             raise self.error(token, "Invalid block tag: '%s', expected %s" % (command, get_text_list(["'%s'" % p for p in parse_until])))
     319            raise self.error(token, "Invalid block tag: '%s', expected %s" %
     320                (command, get_text_list(["'%s'" % p for p in parse_until])))
    290321        raise self.error(token, "Invalid block tag: '%s'" % command)
    291322
    292323    def unclosed_block_tag(self, parse_until):
    293         raise self.error(None, "Unclosed tags: %s " %  ', '.join(parse_until))
     324        raise self.error(None, "Unclosed tags: %s " % ', '.join(parse_until))
    294325
    295326    def compile_function_error(self, token, e):
    296327        pass
    class Parser(object):  
    309340        self.filters.update(lib.filters)
    310341
    311342    def compile_filter(self, token):
    312         "Convenient wrapper for FilterExpression"
     343        """
     344        Convenient wrapper for FilterExpression
     345        """
    313346        return FilterExpression(token, self)
    314347
    315348    def find_filter(self, filter_name):
    class Parser(object):  
    320353
    321354class TokenParser(object):
    322355    """
    323     Subclass this and implement the top() method to parse a template line. When
    324     instantiating the parser, pass in the line from the Django template parser.
     356    Subclass this and implement the top() method to parse a template line.
     357    When instantiating the parser, pass in the line from the Django template
     358    parser.
    325359
    326360    The parser's "tagname" instance-variable stores the name of the tag that
    327361    the filter was called with.
    class TokenParser(object):  
    333367        self.tagname = self.tag()
    334368
    335369    def top(self):
    336         "Overload this method to do the actual parsing and return the result."
     370        """
     371        Overload this method to do the actual parsing and return the result.
     372        """
    337373        raise NotImplementedError()
    338374
    339375    def more(self):
    340         "Returns True if there is more stuff in the tag."
     376        """
     377        Returns True if there is more stuff in the tag.
     378        """
    341379        return self.pointer < len(self.subject)
    342380
    343381    def back(self):
    344         "Undoes the last microparser. Use this for lookahead and backtracking."
     382        """
     383        Undoes the last microparser. Use this for lookahead and backtracking.
     384        """
    345385        if not len(self.backout):
    346             raise TemplateSyntaxError("back called without some previous parsing")
     386            raise TemplateSyntaxError("back called without some previous "
     387                                      "parsing")
    347388        self.pointer = self.backout.pop()
    348389
    349390    def tag(self):
    350         "A microparser that just returns the next tag from the line."
     391        """
     392        A microparser that just returns the next tag from the line.
     393        """
    351394        subject = self.subject
    352395        i = self.pointer
    353396        if i >= len(subject):
    354             raise TemplateSyntaxError("expected another tag, found end of string: %s" % subject)
     397            raise TemplateSyntaxError("expected another tag, found "
     398                                      "end of string: %s" % subject)
    355399        p = i
    356400        while i < len(subject) and subject[i] not in (' ', '\t'):
    357401            i += 1
    class TokenParser(object):  
    363407        return s
    364408
    365409    def value(self):
    366         "A microparser that parses for a value: some string constant or variable name."
     410        """
     411        A microparser that parses for a value: some string constant or
     412        variable name.
     413        """
    367414        subject = self.subject
    368415        i = self.pointer
    369416
    370417        def next_space_index(subject, i):
    371             "Increment pointer until a real space (i.e. a space not within quotes) is encountered"
     418            """
     419            Increment pointer until a real space (i.e. a space not within
     420            quotes) is encountered
     421            """
    372422            while i < len(subject) and subject[i] not in (' ', '\t'):
    373423                if subject[i] in ('"', "'"):
    374424                    c = subject[i]
    class TokenParser(object):  
    376426                    while i < len(subject) and subject[i] != c:
    377427                        i += 1
    378428                    if i >= len(subject):
    379                         raise TemplateSyntaxError("Searching for value. Unexpected end of string in column %d: %s" % (i, subject))
     429                        raise TemplateSyntaxError("Searching for value. "
     430                            "Unexpected end of string in column %d: %s" %
     431                            (i, subject))
    380432                i += 1
    381433            return i
    382434
    383435        if i >= len(subject):
    384             raise TemplateSyntaxError("Searching for value. Expected another value but found end of string: %s" % subject)
     436            raise TemplateSyntaxError("Searching for value. Expected another "
     437                                      "value but found end of string: %s" %
     438                                      subject)
    385439        if subject[i] in ('"', "'"):
    386440            p = i
    387441            i += 1
    388442            while i < len(subject) and subject[i] != subject[p]:
    389443                i += 1
    390444            if i >= len(subject):
    391                 raise TemplateSyntaxError("Searching for value. Unexpected end of string in column %d: %s" % (i, subject))
     445                raise TemplateSyntaxError("Searching for value. Unexpected "
     446                                          "end of string in column %d: %s" %
     447                                          (i, subject))
    392448            i += 1
    393449
    394             # Continue parsing until next "real" space, so that filters are also included
     450            # Continue parsing until next "real" space,
     451            # so that filters are also included
    395452            i = next_space_index(subject, i)
    396453
    397454            res = subject[p:i]
    constant_string = r"""  
    419476%(strdq)s|
    420477%(strsq)s)
    421478""" % {
    422     'strdq': r'"[^"\\]*(?:\\.[^"\\]*)*"', # double-quoted string
    423     'strsq': r"'[^'\\]*(?:\\.[^'\\]*)*'", # single-quoted string
    424     'i18n_open' : re.escape("_("),
    425     'i18n_close' : re.escape(")"),
     479    'strdq': r'"[^"\\]*(?:\\.[^"\\]*)*"',  # double-quoted string
     480    'strsq': r"'[^'\\]*(?:\\.[^'\\]*)*'",  # single-quoted string
     481    'i18n_open': re.escape("_("),
     482    'i18n_close': re.escape(")"),
    426483    }
    427484constant_string = constant_string.replace("\n", "")
    428485
    filter_raw_string = r"""  
    440497 )""" % {
    441498    'constant': constant_string,
    442499    'num': r'[-+\.]?\d[\d\.e]*',
    443     'var_chars': "\w\." ,
     500    'var_chars': "\w\.",
    444501    'filter_sep': re.escape(FILTER_SEPARATOR),
    445502    'arg_sep': re.escape(FILTER_ARGUMENT_SEPARATOR),
    446503  }
    447504
    448 filter_re = re.compile(filter_raw_string, re.UNICODE|re.VERBOSE)
     505filter_re = re.compile(filter_raw_string, re.UNICODE | re.VERBOSE)
    449506
    450507class FilterExpression(object):
    451     r"""
     508    """
    452509    Parses a variable token and its optional filters (all as a single string),
    453510    and return a list of tuples of the filter name and arguments.
    454     Sample:
     511    Sample::
     512
    455513        >>> token = 'variable|default:"Default value"|date:"Y-m-d"'
    456514        >>> p = Parser('')
    457515        >>> fe = FilterExpression(token, p)
    class FilterExpression(object):  
    472530        for match in matches:
    473531            start = match.start()
    474532            if upto != start:
    475                 raise TemplateSyntaxError("Could not parse some characters: %s|%s|%s"  % \
    476                         (token[:upto], token[upto:start], token[start:]))
     533                raise TemplateSyntaxError("Could not parse some characters: "
     534                                          "%s|%s|%s" %
     535                                          (token[:upto], token[upto:start],
     536                                           token[start:]))
    477537            if var_obj is None:
    478538                var, constant = match.group("var", "constant")
    479539                if constant:
    class FilterExpression(object):  
    482542                    except VariableDoesNotExist:
    483543                        var_obj = None
    484544                elif var is None:
    485                     raise TemplateSyntaxError("Could not find variable at start of %s." % token)
     545                    raise TemplateSyntaxError("Could not find variable at "
     546                                              "start of %s." % token)
    486547                else:
    487548                    var_obj = Variable(var)
    488549            else:
    class FilterExpression(object):  
    498559                filters.append((filter_func, args))
    499560            upto = match.end()
    500561        if upto != len(token):
    501             raise TemplateSyntaxError("Could not parse the remainder: '%s' from '%s'" % (token[upto:], token))
     562            raise TemplateSyntaxError("Could not parse the remainder: '%s' "
     563                                      "from '%s'" % (token[upto:], token))
    502564
    503565        self.filters = filters
    504566        self.var = var_obj
    class FilterExpression(object):  
    559621                provided.pop(0)
    560622        except IndexError:
    561623            # Not enough
    562             raise TemplateSyntaxError("%s requires %d arguments, %d provided" % (name, len(nondefs), plen))
     624            raise TemplateSyntaxError("%s requires %d arguments, %d provided" %
     625                                      (name, len(nondefs), plen))
    563626
    564627        # Defaults can be overridden.
    565628        defaults = defaults and list(defaults) or []
    class FilterExpression(object):  
    568631                defaults.pop(0)
    569632        except IndexError:
    570633            # Too many.
    571             raise TemplateSyntaxError("%s requires %d arguments, %d provided" % (name, len(nondefs), plen))
     634            raise TemplateSyntaxError("%s requires %d arguments, %d provided" %
     635                                      (name, len(nondefs), plen))
    572636
    573637        return True
    574638    args_check = staticmethod(args_check)
    def resolve_variable(path, context):  
    586650    return Variable(path).resolve(context)
    587651
    588652class Variable(object):
    589     r"""
    590     A template variable, resolvable against a given context. The variable may be
    591     a hard-coded string (if it begins and ends with single or double quote
     653    """
     654    A template variable, resolvable against a given context. The variable may
     655    be a hard-coded string (if it begins and ends with single or double quote
    592656    marks)::
    593657
    594658        >>> c = {'article': {'section':u'News'}}
    class Variable(object):  
    642706                # Otherwise we'll set self.lookups so that resolve() knows we're
    643707                # dealing with a bonafide variable
    644708                if var.find(VARIABLE_ATTRIBUTE_SEPARATOR + '_') > -1 or var[0] == '_':
    645                     raise TemplateSyntaxError("Variables and attributes may not begin with underscores: '%s'" % var)
     709                    raise TemplateSyntaxError("Variables and attributes may "
     710                                              "not begin with underscores: '%s'" %
     711                                              var)
    646712                self.lookups = tuple(var.split(VARIABLE_ATTRIBUTE_SEPARATOR))
    647713
    648714    def resolve(self, context):
    class Variable(object):  
    673739        instead.
    674740        """
    675741        current = context
    676         try: # catch-all for silent variable failures
     742        try:  # catch-all for silent variable failures
    677743            for bit in self.lookups:
    678                 try: # dictionary lookup
     744                try:  # dictionary lookup
    679745                    current = current[bit]
    680746                except (TypeError, AttributeError, KeyError):
    681                     try: # attribute lookup
     747                    try:  # attribute lookup
    682748                        current = getattr(current, bit)
    683749                    except (TypeError, AttributeError):
    684                         try: # list-index lookup
     750                        try:  # list-index lookup
    685751                            current = current[int(bit)]
    686                         except (IndexError, # list index out of range
    687                                 ValueError, # invalid literal for int()
    688                                 KeyError,   # current is a dict without `int(bit)` key
    689                                 TypeError,  # unsubscriptable object
    690                                 ):
    691                             raise VariableDoesNotExist("Failed lookup for key [%s] in %r", (bit, current)) # missing attribute
     752                        except (IndexError,  # list index out of range
     753                                ValueError,  # invalid literal for int()
     754                                KeyError,    # current is a dict without `int(bit)` key
     755                                TypeError):  # unsubscriptable object
     756                            raise VariableDoesNotExist("Failed lookup for key "
     757                                                       "[%s] in %r",
     758                                                       (bit, current))  # missing attribute
    692759                if callable(current):
    693760                    if getattr(current, 'do_not_call_in_templates', False):
    694761                        pass
    class Variable(object):  
    700767                        except TypeError: # arguments *were* required
    701768                            # GOTCHA: This will also catch any TypeError
    702769                            # raised in the function itself.
    703                             current = settings.TEMPLATE_STRING_IF_INVALID # invalid method call
     770                            current = settings.TEMPLATE_STRING_IF_INVALID  # invalid method call
    704771        except Exception, e:
    705772            if getattr(e, 'silent_variable_failure', False):
    706773                current = settings.TEMPLATE_STRING_IF_INVALID
    class Node(object):  
    716783    child_nodelists = ('nodelist',)
    717784
    718785    def render(self, context):
    719         "Return the node rendered as a string"
     786        """
     787        Return the node rendered as a string.
     788        """
    720789        pass
    721790
    722791    def __iter__(self):
    723792        yield self
    724793
    725794    def get_nodes_by_type(self, nodetype):
    726         "Return a list of all nodes (within this node and its nodelist) of the given type"
     795        """
     796        Return a list of all nodes (within this node and its nodelist)
     797        of the given type
     798        """
    727799        nodes = []
    728800        if isinstance(self, nodetype):
    729801            nodes.append(self)
    def _render_value_in_context(value, context):  
    776848    """
    777849    value = localize(value, use_l10n=context.use_l10n)
    778850    value = force_unicode(value)
    779     if (context.autoescape and not isinstance(value, SafeData)) or isinstance(value, EscapeData):
     851    if ((context.autoescape and not isinstance(value, SafeData)) or
     852            isinstance(value, EscapeData)):
    780853        return escape(value)
    781854    else:
    782855        return value
    class VariableNode(Node):  
    793866            output = self.filter_expression.resolve(context)
    794867        except UnicodeDecodeError:
    795868            # Unicode conversion can fail sometimes for reasons out of our
    796             # control (e.g. exception rendering). In that case, we fail quietly.
     869            # control (e.g. exception rendering). In that case, we fail
     870            # quietly.
    797871            return ''
    798872        return _render_value_in_context(output, context)
    799873
    800 def generic_tag_compiler(params, defaults, name, node_class, parser, token):
    801     "Returns a template.Node subclass."
    802     bits = token.split_contents()[1:]
    803     bmax = len(params)
    804     def_len = defaults and len(defaults) or 0
    805     bmin = bmax - def_len
    806     if(len(bits) < bmin or len(bits) > bmax):
    807         if bmin == bmax:
    808             message = "%s takes %s arguments" % (name, bmin)
     874# Regex for token keyword arguments
     875kwarg_re = re.compile(r"(?:(\w+)=)?(.+)")
     876
     877def token_kwargs(bits, parser, support_legacy=False):
     878    """
     879    A utility method for parsing token keyword arguments.
     880
     881    :param bits: A list containing remainder of the token (split by spaces)
     882        that is to be checked for arguments. Valid arguments will be removed
     883        from this list.
     884
     885    :param support_legacy: If set to true ``True``, the legacy format
     886        ``1 as foo`` will be accepted. Otherwise, only the standard ``foo=1``
     887        format is allowed.
     888
     889    :returns: A dictionary of the arguments retrieved from the ``bits`` token
     890        list.
     891
     892    There is no requirement for all remaining token ``bits`` to be keyword
     893    arguments, so the dictionary will be returned as soon as an invalid
     894    argument format is reached.
     895    """
     896    if not bits:
     897        return {}
     898    match = kwarg_re.match(bits[0])
     899    kwarg_format = match and match.group(1)
     900    if not kwarg_format:
     901        if not support_legacy:
     902            return {}
     903        if len(bits) < 3 or bits[1] != 'as':
     904            return {}
     905
     906    kwargs = {}
     907    while bits:
     908        if kwarg_format:
     909            match = kwarg_re.match(bits[0])
     910            if not match or not match.group(1):
     911                return kwargs
     912            key, value = match.groups()
     913            del bits[:1]
     914        else:
     915            if len(bits) < 3 or bits[1] != 'as':
     916                return kwargs
     917            key, value = bits[2], bits[0]
     918            del bits[:3]
     919        kwargs[key] = parser.compile_filter(value)
     920        if bits and not kwarg_format:
     921            if bits[0] != 'and':
     922                return kwargs
     923            del bits[:1]
     924    return kwargs
     925
     926def parse_bits(parser, bits, params, varargs, varkw, defaults,
     927               takes_context, name):
     928    """
     929    Parses bits for template tag helpers (simple_tag, include_tag and
     930    assignment_tag), in particular by detecting syntax errors and by
     931    extracting positional and keyword arguments.
     932    """
     933    if takes_context:
     934        if params[0] == 'context':
     935            params = params[1:]
     936        else:
     937            raise TemplateSyntaxError(
     938                "'%s' is decorated with takes_context=True so it must "
     939                "have a first argument of 'context'" % name)
     940    args = []
     941    kwargs = {}
     942    unhandled_params = list(params)
     943    for bit in bits:
     944        # First we try to extract a potential kwarg from the bit
     945        kwarg = token_kwargs([bit], parser)
     946        if kwarg:
     947            # The kwarg was successfully extracted
     948            param, value = kwarg.items()[0]
     949            if param not in params and varkw is None:
     950                # An unexpected keyword argument was supplied
     951                raise TemplateSyntaxError(
     952                    "'%s' received unexpected keyword argument '%s'" %
     953                    (name, param))
     954            elif param in kwargs:
     955                # The keyword argument has already been supplied once
     956                raise TemplateSyntaxError(
     957                    "'%s' received multiple values for keyword argument '%s'" %
     958                    (name, param))
     959            else:
     960                # All good, record the keyword argument
     961                kwargs[str(param)] = value
     962                if param in unhandled_params:
     963                    # If using the keyword syntax for a positional arg, then
     964                    # consume it.
     965                    unhandled_params.remove(param)
    809966        else:
    810             message = "%s takes between %s and %s arguments" % (name, bmin, bmax)
    811         raise TemplateSyntaxError(message)
    812     return node_class(bits)
     967            if kwargs:
     968                raise TemplateSyntaxError(
     969                    "'%s' received some positional argument(s) after some "
     970                    "keyword argument(s)" % name)
     971            else:
     972                # Record the positional argument
     973                args.append(parser.compile_filter(bit))
     974                try:
     975                    # Consume from the list of expected positional arguments
     976                    unhandled_params.pop(0)
     977                except IndexError:
     978                    if varargs is None:
     979                        raise TemplateSyntaxError(
     980                            "'%s' received too many positional arguments" %
     981                            name)
     982    if defaults is not None:
     983        # Consider the last n params handled, where n is the
     984        # number of defaults.
     985        unhandled_params = unhandled_params[:-len(defaults)]
     986    if unhandled_params:
     987        # Some positional arguments were not supplied
     988        raise TemplateSyntaxError(
     989            u"'%s' did not receive value(s) for the argument(s): %s" %
     990            (name, u", ".join([u"'%s'" % p for p in unhandled_params])))
     991    return args, kwargs
     992
     993def generic_tag_compiler(parser, token, params, varargs, varkw, defaults,
     994                         name, takes_context, node_class):
     995    """
     996    Returns a template.Node subclass.
     997    """
     998    bits = token.split_contents()[1:]
     999    args, kwargs = parse_bits(parser, bits, params, varargs, varkw,
     1000                              defaults, takes_context, name)
     1001    return node_class(takes_context, args, kwargs)
     1002
     1003class TagHelperNode(Node):
     1004    """
     1005    Base class for tag helper nodes such as SimpleNode, InclusionNode and
     1006    AssignmentNode. Manages the positional and keyword arguments to be passed
     1007    to the decorated function.
     1008    """
     1009
     1010    def __init__(self, takes_context, args, kwargs):
     1011        self.takes_context = takes_context
     1012        self.args = args
     1013        self.kwargs = kwargs
     1014
     1015    def get_resolved_arguments(self, context):
     1016        resolved_args = [var.resolve(context) for var in self.args]
     1017        if self.takes_context:
     1018            resolved_args = [context] + resolved_args
     1019        resolved_kwargs = dict((k, v.resolve(context))
     1020                                for k, v in self.kwargs.items())
     1021        return resolved_args, resolved_kwargs
    8131022
    8141023class Library(object):
    8151024    def __init__(self):
    class Library(object):  
    8171026        self.tags = {}
    8181027
    8191028    def tag(self, name=None, compile_function=None):
    820         if name == None and compile_function == None:
     1029        if name is None and compile_function is None:
    8211030            # @register.tag()
    8221031            return self.tag_function
    823         elif name != None and compile_function == None:
     1032        elif name is not None and compile_function is None:
    8241033            if callable(name):
    8251034                # @register.tag
    8261035                return self.tag_function(name)
    class Library(object):  
    8291038                def dec(func):
    8301039                    return self.tag(name, func)
    8311040                return dec
    832         elif name != None and compile_function != None:
     1041        elif name is not None and compile_function is not None:
    8331042            # register.tag('somename', somefunc)
    8341043            self.tags[name] = compile_function
    8351044            return compile_function
    8361045        else:
    837             raise InvalidTemplateLibrary("Unsupported arguments to Library.tag: (%r, %r)", (name, compile_function))
     1046            raise InvalidTemplateLibrary("Unsupported arguments to "
     1047                "Library.tag: (%r, %r)", (name, compile_function))
    8381048
    839     def tag_function(self,func):
     1049    def tag_function(self, func):
    8401050        self.tags[getattr(func, "_decorated_function", func).__name__] = func
    8411051        return func
    8421052
    8431053    def filter(self, name=None, filter_func=None):
    844         if name == None and filter_func == None:
     1054        if name is None and filter_func is None:
    8451055            # @register.filter()
    8461056            return self.filter_function
    847         elif filter_func == None:
     1057        elif filter_func is None:
    8481058            if callable(name):
    8491059                # @register.filter
    8501060                return self.filter_function(name)
    class Library(object):  
    8531063                def dec(func):
    8541064                    return self.filter(name, func)
    8551065                return dec
    856         elif name != None and filter_func != None:
     1066        elif name is not None and filter_func is not None:
    8571067            # register.filter('somename', somefunc)
    8581068            self.filters[name] = filter_func
    8591069            return filter_func
    8601070        else:
    861             raise InvalidTemplateLibrary("Unsupported arguments to Library.filter: (%r, %r)", (name, filter_func))
     1071            raise InvalidTemplateLibrary("Unsupported arguments to "
     1072                "Library.filter: (%r, %r)", (name, filter_func))
    8621073
    8631074    def filter_function(self, func):
    8641075        self.filters[getattr(func, "_decorated_function", func).__name__] = func
    class Library(object):  
    8661077
    8671078    def simple_tag(self, func=None, takes_context=None, name=None):
    8681079        def dec(func):
    869             params, xx, xxx, defaults = getargspec(func)
    870             if takes_context:
    871                 if params[0] == 'context':
    872                     params = params[1:]
    873                 else:
    874                     raise TemplateSyntaxError("Any tag function decorated with takes_context=True must have a first argument of 'context'")
     1080            params, varargs, varkw, defaults = getargspec(func)
    8751081
    876             class SimpleNode(Node):
    877                 def __init__(self, vars_to_resolve):
    878                     self.vars_to_resolve = map(Variable, vars_to_resolve)
     1082            class SimpleNode(TagHelperNode):
    8791083
    8801084                def render(self, context):
    881                     resolved_vars = [var.resolve(context) for var in self.vars_to_resolve]
    882                     if takes_context:
    883                         func_args = [context] + resolved_vars
    884                     else:
    885                         func_args = resolved_vars
    886                     return func(*func_args)
    887 
    888             function_name = name or getattr(func, '_decorated_function', func).__name__
    889             compile_func = partial(generic_tag_compiler, params, defaults, function_name, SimpleNode)
     1085                    resolved_args, resolved_kwargs = self.get_resolved_arguments(context)
     1086                    return func(*resolved_args, **resolved_kwargs)
     1087
     1088            function_name = (name or
     1089                getattr(func, '_decorated_function', func).__name__)
     1090            compile_func = partial(generic_tag_compiler,
     1091                params=params, varargs=varargs, varkw=varkw,
     1092                defaults=defaults, name=function_name,
     1093                takes_context=takes_context, node_class=SimpleNode)
    8901094            compile_func.__doc__ = func.__doc__
    8911095            self.tag(function_name, compile_func)
    8921096            return func
    class Library(object):  
    9021106
    9031107    def assignment_tag(self, func=None, takes_context=None, name=None):
    9041108        def dec(func):
    905             params, xx, xxx, defaults = getargspec(func)
    906             if takes_context:
    907                 if params[0] == 'context':
    908                     params = params[1:]
    909                 else:
    910                     raise TemplateSyntaxError("Any tag function decorated with takes_context=True must have a first argument of 'context'")
     1109            params, varargs, varkw, defaults = getargspec(func)
    9111110
    912             class AssignmentNode(Node):
    913                 def __init__(self, params_vars, target_var):
    914                     self.params_vars = map(Variable, params_vars)
     1111            class AssignmentNode(TagHelperNode):
     1112                def __init__(self, takes_context, args, kwargs, target_var):
     1113                    super(AssignmentNode, self).__init__(takes_context, args, kwargs)
    9151114                    self.target_var = target_var
    9161115
    9171116                def render(self, context):
    918                     resolved_vars = [var.resolve(context) for var in self.params_vars]
    919                     if takes_context:
    920                         func_args = [context] + resolved_vars
    921                     else:
    922                         func_args = resolved_vars
    923                     context[self.target_var] = func(*func_args)
     1117                    resolved_args, resolved_kwargs = self.get_resolved_arguments(context)
     1118                    context[self.target_var] = func(*resolved_args, **resolved_kwargs)
    9241119                    return ''
    9251120
     1121            function_name = (name or
     1122                getattr(func, '_decorated_function', func).__name__)
     1123
    9261124            def compile_func(parser, token):
    927                 bits = token.split_contents()
    928                 tag_name = bits[0]
    929                 bits = bits[1:]
    930                 params_max = len(params)
    931                 defaults_length = defaults and len(defaults) or 0
    932                 params_min = params_max - defaults_length
    933                 if (len(bits) < 2 or bits[-2] != 'as'):
     1125                bits = token.split_contents()[1:]
     1126                if len(bits) < 2 or bits[-2] != 'as':
    9341127                    raise TemplateSyntaxError(
    9351128                        "'%s' tag takes at least 2 arguments and the "
    936                         "second last argument must be 'as'" % tag_name)
    937                 params_vars = bits[:-2]
     1129                        "second last argument must be 'as'" % function_name)
    9381130                target_var = bits[-1]
    939                 if (len(params_vars) < params_min or
    940                         len(params_vars) > params_max):
    941                     if params_min == params_max:
    942                         raise TemplateSyntaxError(
    943                             "%s takes %s arguments" % (tag_name, params_min))
    944                     else:
    945                         raise TemplateSyntaxError(
    946                             "%s takes between %s and %s arguments"
    947                             % (tag_name, params_min, params_max))
    948                 return AssignmentNode(params_vars, target_var)
     1131                bits = bits[:-2]
     1132                args, kwargs = parse_bits(parser, bits, params,
     1133                    varargs, varkw, defaults, takes_context, function_name)
     1134                return AssignmentNode(takes_context, args, kwargs, target_var)
    9491135
    950             function_name = name or getattr(func, '_decorated_function', func).__name__
    9511136            compile_func.__doc__ = func.__doc__
    9521137            self.tag(function_name, compile_func)
    9531138            return func
    class Library(object):  
    9631148
    9641149    def inclusion_tag(self, file_name, context_class=Context, takes_context=False, name=None):
    9651150        def dec(func):
    966             params, xx, xxx, defaults = getargspec(func)
    967             if takes_context:
    968                 if params[0] == 'context':
    969                     params = params[1:]
    970                 else:
    971                     raise TemplateSyntaxError("Any tag function decorated with takes_context=True must have a first argument of 'context'")
     1151            params, varargs, varkw, defaults = getargspec(func)
    9721152
    973             class InclusionNode(Node):
    974                 def __init__(self, vars_to_resolve):
    975                     self.vars_to_resolve = map(Variable, vars_to_resolve)
     1153            class InclusionNode(TagHelperNode):
    9761154
    9771155                def render(self, context):
    978                     resolved_vars = [var.resolve(context) for var in self.vars_to_resolve]
    979                     if takes_context:
    980                         args = [context] + resolved_vars
    981                     else:
    982                         args = resolved_vars
    983 
    984                     dict = func(*args)
     1156                    resolved_args, resolved_kwargs = self.get_resolved_arguments(context)
     1157                    _dict = func(*resolved_args, **resolved_kwargs)
    9851158
    9861159                    if not getattr(self, 'nodelist', False):
    9871160                        from django.template.loader import get_template, select_template
    class Library(object):  
    9921165                        else:
    9931166                            t = get_template(file_name)
    9941167                        self.nodelist = t.nodelist
    995                     new_context = context_class(dict, **{
     1168                    new_context = context_class(_dict, **{
    9961169                        'autoescape': context.autoescape,
    9971170                        'current_app': context.current_app,
    9981171                        'use_l10n': context.use_l10n,
    9991172                    })
    1000                     # Copy across the CSRF token, if present, because inclusion
    1001                     # tags are often used for forms, and we need instructions
    1002                     # for using CSRF protection to be as simple as possible.
     1173                    # Copy across the CSRF token, if present, because
     1174                    # inclusion tags are often used for forms, and we need
     1175                    # instructions for using CSRF protection to be as simple
     1176                    # as possible.
    10031177                    csrf_token = context.get('csrf_token', None)
    10041178                    if csrf_token is not None:
    10051179                        new_context['csrf_token'] = csrf_token
    10061180                    return self.nodelist.render(new_context)
    10071181
    1008             function_name = name or getattr(func, '_decorated_function', func).__name__
    1009             compile_func = partial(generic_tag_compiler, params, defaults, function_name, InclusionNode)
     1182            function_name = (name or
     1183                getattr(func, '_decorated_function', func).__name__)
     1184            compile_func = partial(generic_tag_compiler,
     1185                params=params, varargs=varargs, varkw=varkw,
     1186                defaults=defaults, name=function_name,
     1187                takes_context=takes_context, node_class=InclusionNode)
    10101188            compile_func.__doc__ = func.__doc__
    10111189            self.tag(function_name, compile_func)
    10121190            return func
    10131191        return dec
    10141192
    10151193def import_library(taglib_module):
    1016     """Load a template tag library module.
     1194    """
     1195    Load a template tag library module.
    10171196
    10181197    Verifies that the library contains a 'register' attribute, and
    10191198    returns that attribute as the representation of the library
    10201199    """
    1021     app_path, taglib = taglib_module.rsplit('.',1)
     1200    app_path, taglib = taglib_module.rsplit('.', 1)
    10221201    app_module = import_module(app_path)
    10231202    try:
    10241203        mod = import_module(taglib_module)
    10251204    except ImportError, e:
    1026         # If the ImportError is because the taglib submodule does not exist, that's not
    1027         # an error that should be raised. If the submodule exists and raised an ImportError
    1028         # on the attempt to load it, that we want to raise.
     1205        # If the ImportError is because the taglib submodule does not exist,
     1206        # that's not an error that should be raised. If the submodule exists
     1207        # and raised an ImportError on the attempt to load it, that we want
     1208        # to raise.
    10291209        if not module_has_submodule(app_module, taglib):
    10301210            return None
    10311211        else:
    1032             raise InvalidTemplateLibrary("ImportError raised loading %s: %s" % (taglib_module, e))
     1212            raise InvalidTemplateLibrary("ImportError raised loading %s: %s" %
     1213                                         (taglib_module, e))
    10331214    try:
    10341215        return mod.register
    10351216    except AttributeError:
    1036         raise InvalidTemplateLibrary("Template library %s does not have a variable named 'register'" % taglib_module)
     1217        raise InvalidTemplateLibrary("Template library %s does not have "
     1218                                     "a variable named 'register'" %
     1219                                     taglib_module)
    10371220
    10381221templatetags_modules = []
    10391222
    10401223def get_templatetags_modules():
    1041     """Return the list of all available template tag modules.
     1224    """
     1225    Return the list of all available template tag modules.
    10421226
    10431227    Caches the result for faster access.
    10441228    """
    10451229    global templatetags_modules
    10461230    if not templatetags_modules:
    10471231        _templatetags_modules = []
    1048         # Populate list once per process. Mutate the local list first, and then
    1049         # assign it to the global name to ensure there are no cases where two
    1050         # threads try to populate it simultaneously.
     1232        # Populate list once per process. Mutate the local list first, and
     1233        # then assign it to the global name to ensure there are no cases where
     1234        # two threads try to populate it simultaneously.
    10511235        for app_module in ['django'] + list(settings.INSTALLED_APPS):
    10521236            try:
    10531237                templatetag_module = '%s.templatetags' % app_module
    def get_library(library_name):  
    10621246    """
    10631247    Load the template library module with the given name.
    10641248
    1065     If library is not already loaded loop over all templatetags modules to locate it.
     1249    If library is not already loaded loop over all templatetags modules
     1250    to locate it.
    10661251
    10671252    {% load somelib %} and {% load someotherlib %} loops twice.
    10681253
    1069     Subsequent loads eg. {% load somelib %} in the same process will grab the cached
    1070     module from libraries.
     1254    Subsequent loads eg. {% load somelib %} in the same process will grab
     1255    the cached module from libraries.
    10711256    """
    10721257    lib = libraries.get(library_name, None)
    10731258    if not lib:
    def get_library(library_name):  
    10811266                libraries[library_name] = lib
    10821267                break
    10831268        if not lib:
    1084             raise InvalidTemplateLibrary("Template library %s not found, tried %s" % (library_name, ','.join(tried_modules)))
     1269            raise InvalidTemplateLibrary("Template library %s not found, "
     1270                                         "tried %s" %
     1271                                         (library_name,
     1272                                          ','.join(tried_modules)))
    10851273    return lib
    10861274
     1275
    10871276def add_to_builtins(module):
    10881277    builtins.append(import_library(module))
    10891278
     1279
    10901280add_to_builtins('django.template.defaulttags')
    10911281add_to_builtins('django.template.defaultfilters')
  • django/template/defaulttags.py

    diff --git a/django/template/defaulttags.py b/django/template/defaulttags.py
    index 0b039a5..9620d1c 100644
    a b from django.template.base import (Node, NodeList, Template, Library,  
    1010    TemplateSyntaxError, VariableDoesNotExist, InvalidTemplateLibrary,
    1111    BLOCK_TAG_START, BLOCK_TAG_END, VARIABLE_TAG_START, VARIABLE_TAG_END,
    1212    SINGLE_BRACE_START, SINGLE_BRACE_END, COMMENT_TAG_START, COMMENT_TAG_END,
    13     get_library)
     13    get_library, token_kwargs, kwarg_re)
    1414from django.template.smartif import IfParser, Literal
    1515from django.template.defaultfilters import date
    1616from django.utils.encoding import smart_str, smart_unicode
    1717from django.utils.safestring import mark_safe
    1818
    1919register = Library()
    20 # Regex for token keyword arguments
    21 kwarg_re = re.compile(r"(?:(\w+)=)?(.+)")
    22 
    23 def token_kwargs(bits, parser, support_legacy=False):
    24     """
    25     A utility method for parsing token keyword arguments.
    26 
    27     :param bits: A list containing remainder of the token (split by spaces)
    28         that is to be checked for arguments. Valid arguments will be removed
    29         from this list.
    30 
    31     :param support_legacy: If set to true ``True``, the legacy format
    32         ``1 as foo`` will be accepted. Otherwise, only the standard ``foo=1``
    33         format is allowed.
    34 
    35     :returns: A dictionary of the arguments retrieved from the ``bits`` token
    36         list.
    37 
    38     There is no requirement for all remaining token ``bits`` to be keyword
    39     arguments, so the dictionary will be returned as soon as an invalid
    40     argument format is reached.
    41     """
    42     if not bits:
    43         return {}
    44     match = kwarg_re.match(bits[0])
    45     kwarg_format = match and match.group(1)
    46     if not kwarg_format:
    47         if not support_legacy:
    48             return {}
    49         if len(bits) < 3 or bits[1] != 'as':
    50             return {}
    51 
    52     kwargs = {}
    53     while bits:
    54         if kwarg_format:
    55             match = kwarg_re.match(bits[0])
    56             if not match or not match.group(1):
    57                 return kwargs
    58             key, value = match.groups()
    59             del bits[:1]
    60         else:
    61             if len(bits) < 3 or bits[1] != 'as':
    62                 return kwargs
    63             key, value = bits[2], bits[0]
    64             del bits[:3]
    65         kwargs[key] = parser.compile_filter(value)
    66         if bits and not kwarg_format:
    67             if bits[0] != 'and':
    68                 return kwargs
    69             del bits[:1]
    70     return kwargs
    7120
    7221class AutoEscapeControlNode(Node):
    7322    """Implements the actions of the autoescape tag."""
  • docs/howto/custom-template-tags.txt

    diff --git a/docs/howto/custom-template-tags.txt b/docs/howto/custom-template-tags.txt
    index eaab36c..4001794 100644
    a b If you need to rename your tag, you can provide a custom name for it::  
    698698    def some_function(value):
    699699        return value - 1
    700700
     701.. versionadded:: 1.4
     702
     703``simple_tag`` functions may accept any number of positional or keyword
     704arguments. For example:
     705
     706.. code-block:: python
     707
     708    @register.simple_tag
     709    def my_tag(a, b, *args, **kwargs):
     710        warning = kwargs['warning']
     711        profile = kwargs['profile']
     712        ...
     713        return ...
     714
     715Then in the template any number of arguments, separated by spaces, may be
     716passed to the template tag. Like in Python, the values for keyword arguments
     717are set using the equal sign ("``=``") and must be provided after the positional
     718arguments. For example:
     719
     720.. code-block:: html+django
     721
     722    {% my_tag 123 "abcd" book.title warning=message|lower profile=user.profile %}
     723
    701724.. _howto-custom-template-tags-assignment-tags:
    702725
    703726Assignment tags
    Or, using decorator syntax:  
    761784For more information on how the ``takes_context`` option works, see the section
    762785on :ref:`inclusion tags<howto-custom-template-tags-inclusion-tags>`.
    763786
     787``assignment_tag`` functions may accept any number of positional or keyword
     788arguments. For example:
     789
     790.. code-block:: python
     791
     792    @register.assignment_tag
     793    def my_tag(a, b, *args, **kwargs):
     794        warning = kwargs['warning']
     795        profile = kwargs['profile']
     796        ...
     797        return ...
     798
     799Then in the template any number of arguments, separated by spaces, may be
     800passed to the template tag. Like in Python, the values for keyword arguments
     801are set using the equal sign ("``=``") and must be provided after the positional
     802arguments. For example:
     803
     804.. code-block:: html+django
     805
     806    {% my_tag 123 "abcd" book.title warning=message|lower profile=user.profile as the_result %}
     807
    764808.. _howto-custom-template-tags-inclusion-tags:
    765809
    766810Inclusion tags
    The ``takes_context`` parameter defaults to ``False``. When it's set to *True*,  
    884928the tag is passed the context object, as in this example. That's the only
    885929difference between this case and the previous ``inclusion_tag`` example.
    886930
     931.. versionadded:: 1.4
     932
     933``inclusion_tag`` functions may accept any number of positional or keyword
     934arguments. For example:
     935
     936.. code-block:: python
     937
     938    @register.inclusion_tag('my_template.html')
     939    def my_tag(a, b, *args, **kwargs):
     940        warning = kwargs['warning']
     941        profile = kwargs['profile']
     942        ...
     943        return ...
     944
     945Then in the template any number of arguments, separated by spaces, may be
     946passed to the template tag. Like in Python, the values for keyword arguments
     947are set using the equal sign ("``=``") and must be provided after the positional
     948arguments. For example:
     949
     950.. code-block:: html+django
     951
     952    {% my_tag 123 "abcd" book.title warning=message|lower profile=user.profile %}
     953
    887954Setting a variable in the context
    888955~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    889956
  • docs/releases/1.4.txt

    diff --git a/docs/releases/1.4.txt b/docs/releases/1.4.txt
    index 9cf268a..29fd5ef 100644
    a b A new helper function,  
    162162``template.Library`` to ease the creation of template tags that store some
    163163data in a specified context variable.
    164164
     165``*args`` and ``**kwargs`` support for template tag helper functions
     166~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     167
     168:ref:`simple_tag<howto-custom-template-tags-simple-tags>`, :ref:`inclusion_tag
     169<howto-custom-template-tags-inclusion-tags>` and the newly introduced
     170:ref:`assignment_tag<howto-custom-template-tags-assignment-tags>` template
     171helper functions may now accept any number of positional or keyword arguments.
     172For example:
     173
     174.. code-block:: python
     175
     176    @register.simple_tag
     177    def my_tag(a, b, *args, **kwargs):
     178        warning = kwargs['warning']
     179        profile = kwargs['profile']
     180        ...
     181        return ...
     182
     183Then in the template any number of arguments may be passed to the template tag.
     184For example:
     185
     186.. code-block:: html+django
     187
     188    {% my_tag 123 "abcd" book.title warning=message|lower profile=user.profile %}
     189
    165190``truncatechars`` template filter
    166191~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    167192
  • tests/regressiontests/templates/custom.py

    diff --git a/tests/regressiontests/templates/custom.py b/tests/regressiontests/templates/custom.py
    index d781874..c05229e 100644
    a b class CustomTagTests(TestCase):  
    3535        t = template.Template('{% load custom %}{% params_and_context 37 %}')
    3636        self.assertEqual(t.render(c), u'params_and_context - Expected result (context value: 42): 37')
    3737
     38        t = template.Template('{% load custom %}{% simple_two_params 37 42 %}')
     39        self.assertEqual(t.render(c), u'simple_two_params - Expected result: 37, 42')
     40
     41        t = template.Template('{% load custom %}{% simple_one_default 37 %}')
     42        self.assertEqual(t.render(c), u'simple_one_default - Expected result: 37, hi')
     43
     44        t = template.Template('{% load custom %}{% simple_one_default 37 two="hello" %}')
     45        self.assertEqual(t.render(c), u'simple_one_default - Expected result: 37, hello')
     46
     47        t = template.Template('{% load custom %}{% simple_one_default one=99 two="hello" %}')
     48        self.assertEqual(t.render(c), u'simple_one_default - Expected result: 99, hello')
     49
     50        self.assertRaisesRegexp(template.TemplateSyntaxError,
     51            "'simple_one_default' received unexpected keyword argument 'three'",
     52            template.Template, '{% load custom %}{% simple_one_default 99 two="hello" three="foo" %}')
     53
     54        t = template.Template('{% load custom %}{% simple_one_default 37 42 %}')
     55        self.assertEqual(t.render(c), u'simple_one_default - Expected result: 37, 42')
     56
     57        t = template.Template('{% load custom %}{% simple_unlimited_args 37 %}')
     58        self.assertEqual(t.render(c), u'simple_unlimited_args - Expected result: 37, hi')
     59
     60        t = template.Template('{% load custom %}{% simple_unlimited_args 37 42 56 89 %}')
     61        self.assertEqual(t.render(c), u'simple_unlimited_args - Expected result: 37, 42, 56, 89')
     62
     63        t = template.Template('{% load custom %}{% simple_only_unlimited_args %}')
     64        self.assertEqual(t.render(c), u'simple_only_unlimited_args - Expected result: ')
     65
     66        t = template.Template('{% load custom %}{% simple_only_unlimited_args 37 42 56 89 %}')
     67        self.assertEqual(t.render(c), u'simple_only_unlimited_args - Expected result: 37, 42, 56, 89')
     68
     69        self.assertRaisesRegexp(template.TemplateSyntaxError,
     70            "'simple_two_params' received too many positional arguments",
     71            template.Template, '{% load custom %}{% simple_two_params 37 42 56 %}')
     72
     73        self.assertRaisesRegexp(template.TemplateSyntaxError,
     74            "'simple_one_default' received too many positional arguments",
     75            template.Template, '{% load custom %}{% simple_one_default 37 42 56 %}')
     76
     77        t = template.Template('{% load custom %}{% simple_unlimited_args_kwargs 37 40|add:2 56 eggs="scrambled" four=1|add:3 %}')
     78        self.assertEqual(t.render(c), u'simple_unlimited_args_kwargs - Expected result: 37, 42, 56 / eggs=scrambled, four=4')
     79
     80        self.assertRaisesRegexp(template.TemplateSyntaxError,
     81            "'simple_unlimited_args_kwargs' received some positional argument\(s\) after some keyword argument\(s\)",
     82            template.Template, '{% load custom %}{% simple_unlimited_args_kwargs 37 40|add:2 eggs="scrambled" 56 four=1|add:3 %}')
     83
     84        self.assertRaisesRegexp(template.TemplateSyntaxError,
     85            "'simple_unlimited_args_kwargs' received multiple values for keyword argument 'eggs'",
     86            template.Template, '{% load custom %}{% simple_unlimited_args_kwargs 37 eggs="scrambled" eggs="scrambled" %}')
     87
    3888    def test_simple_tag_registration(self):
    3989        # Test that the decorators preserve the decorated function's docstring, name and attributes.
    4090        self.verify_tag(custom.no_params, 'no_params')
    class CustomTagTests(TestCase):  
    4292        self.verify_tag(custom.explicit_no_context, 'explicit_no_context')
    4393        self.verify_tag(custom.no_params_with_context, 'no_params_with_context')
    4494        self.verify_tag(custom.params_and_context, 'params_and_context')
     95        self.verify_tag(custom.simple_unlimited_args_kwargs, 'simple_unlimited_args_kwargs')
     96        self.verify_tag(custom.simple_tag_without_context_parameter, 'simple_tag_without_context_parameter')
    4597
    4698    def test_simple_tag_missing_context(self):
    47         # That the 'context' parameter must be present when takes_context is True
    48         def a_simple_tag_without_parameters(arg):
    49             """Expected __doc__"""
    50             return "Expected result"
    51 
    52         register = template.Library()
    53         decorator = register.simple_tag(takes_context=True)
    54         self.assertRaises(template.TemplateSyntaxError, decorator, a_simple_tag_without_parameters)
     99        # The 'context' parameter must be present when takes_context is True
     100        self.assertRaisesRegexp(template.TemplateSyntaxError,
     101            "'simple_tag_without_context_parameter' is decorated with takes_context=True so it must have a first argument of 'context'",
     102            template.Template, '{% load custom %}{% simple_tag_without_context_parameter 123 %}')
    55103
    56104    def test_inclusion_tags(self):
    57105        c = template.Context({'value': 42})
    class CustomTagTests(TestCase):  
    71119        t = template.Template('{% load custom %}{% inclusion_params_and_context 37 %}')
    72120        self.assertEqual(t.render(c), u'inclusion_params_and_context - Expected result (context value: 42): 37\n')
    73121
     122        t = template.Template('{% load custom %}{% inclusion_two_params 37 42 %}')
     123        self.assertEqual(t.render(c), u'inclusion_two_params - Expected result: 37, 42\n')
     124
     125        t = template.Template('{% load custom %}{% inclusion_one_default 37 %}')
     126        self.assertEqual(t.render(c), u'inclusion_one_default - Expected result: 37, hi\n')
     127
     128        t = template.Template('{% load custom %}{% inclusion_one_default 37 two="hello" %}')
     129        self.assertEqual(t.render(c), u'inclusion_one_default - Expected result: 37, hello\n')
     130
     131        t = template.Template('{% load custom %}{% inclusion_one_default one=99 two="hello" %}')
     132        self.assertEqual(t.render(c), u'inclusion_one_default - Expected result: 99, hello\n')
     133
     134        self.assertRaisesRegexp(template.TemplateSyntaxError,
     135            "'inclusion_one_default' received unexpected keyword argument 'three'",
     136            template.Template, '{% load custom %}{% inclusion_one_default 99 two="hello" three="foo" %}')
     137
     138        t = template.Template('{% load custom %}{% inclusion_one_default 37 42 %}')
     139        self.assertEqual(t.render(c), u'inclusion_one_default - Expected result: 37, 42\n')
     140
     141        t = template.Template('{% load custom %}{% inclusion_unlimited_args 37 %}')
     142        self.assertEqual(t.render(c), u'inclusion_unlimited_args - Expected result: 37, hi\n')
     143
     144        t = template.Template('{% load custom %}{% inclusion_unlimited_args 37 42 56 89 %}')
     145        self.assertEqual(t.render(c), u'inclusion_unlimited_args - Expected result: 37, 42, 56, 89\n')
     146
     147        t = template.Template('{% load custom %}{% inclusion_only_unlimited_args %}')
     148        self.assertEqual(t.render(c), u'inclusion_only_unlimited_args - Expected result: \n')
     149
     150        t = template.Template('{% load custom %}{% inclusion_only_unlimited_args 37 42 56 89 %}')
     151        self.assertEqual(t.render(c), u'inclusion_only_unlimited_args - Expected result: 37, 42, 56, 89\n')
     152
     153        self.assertRaisesRegexp(template.TemplateSyntaxError,
     154            "'inclusion_two_params' received too many positional arguments",
     155            template.Template, '{% load custom %}{% inclusion_two_params 37 42 56 %}')
     156
     157        self.assertRaisesRegexp(template.TemplateSyntaxError,
     158            "'inclusion_one_default' received too many positional arguments",
     159            template.Template, '{% load custom %}{% inclusion_one_default 37 42 56 %}')
     160
     161        self.assertRaisesRegexp(template.TemplateSyntaxError,
     162            "'inclusion_one_default' did not receive value\(s\) for the argument\(s\): 'one'",
     163            template.Template, '{% load custom %}{% inclusion_one_default %}')
     164
     165        self.assertRaisesRegexp(template.TemplateSyntaxError,
     166            "'inclusion_unlimited_args' did not receive value\(s\) for the argument\(s\): 'one'",
     167            template.Template, '{% load custom %}{% inclusion_unlimited_args %}')
     168
     169        t = template.Template('{% load custom %}{% inclusion_unlimited_args_kwargs 37 40|add:2 56 eggs="scrambled" four=1|add:3 %}')
     170        self.assertEqual(t.render(c), u'inclusion_unlimited_args_kwargs - Expected result: 37, 42, 56 / eggs=scrambled, four=4\n')
     171
     172        self.assertRaisesRegexp(template.TemplateSyntaxError,
     173            "'inclusion_unlimited_args_kwargs' received some positional argument\(s\) after some keyword argument\(s\)",
     174            template.Template, '{% load custom %}{% inclusion_unlimited_args_kwargs 37 40|add:2 eggs="scrambled" 56 four=1|add:3 %}')
     175
     176        self.assertRaisesRegexp(template.TemplateSyntaxError,
     177            "'inclusion_unlimited_args_kwargs' received multiple values for keyword argument 'eggs'",
     178            template.Template, '{% load custom %}{% inclusion_unlimited_args_kwargs 37 eggs="scrambled" eggs="scrambled" %}')
     179
     180    def test_include_tag_missing_context(self):
     181        # The 'context' parameter must be present when takes_context is True
     182        self.assertRaisesRegexp(template.TemplateSyntaxError,
     183            "'inclusion_tag_without_context_parameter' is decorated with takes_context=True so it must have a first argument of 'context'",
     184            template.Template, '{% load custom %}{% inclusion_tag_without_context_parameter 123 %}')
     185
    74186    def test_inclusion_tags_from_template(self):
    75187        c = template.Context({'value': 42})
    76188
    class CustomTagTests(TestCase):  
    89201        t = template.Template('{% load custom %}{% inclusion_params_and_context_from_template 37 %}')
    90202        self.assertEqual(t.render(c), u'inclusion_params_and_context_from_template - Expected result (context value: 42): 37\n')
    91203
     204        t = template.Template('{% load custom %}{% inclusion_two_params_from_template 37 42 %}')
     205        self.assertEqual(t.render(c), u'inclusion_two_params_from_template - Expected result: 37, 42\n')
     206
     207        t = template.Template('{% load custom %}{% inclusion_one_default_from_template 37 %}')
     208        self.assertEqual(t.render(c), u'inclusion_one_default_from_template - Expected result: 37, hi\n')
     209
     210        t = template.Template('{% load custom %}{% inclusion_one_default_from_template 37 42 %}')
     211        self.assertEqual(t.render(c), u'inclusion_one_default_from_template - Expected result: 37, 42\n')
     212
     213        t = template.Template('{% load custom %}{% inclusion_unlimited_args_from_template 37 %}')
     214        self.assertEqual(t.render(c), u'inclusion_unlimited_args_from_template - Expected result: 37, hi\n')
     215
     216        t = template.Template('{% load custom %}{% inclusion_unlimited_args_from_template 37 42 56 89 %}')
     217        self.assertEqual(t.render(c), u'inclusion_unlimited_args_from_template - Expected result: 37, 42, 56, 89\n')
     218
     219        t = template.Template('{% load custom %}{% inclusion_only_unlimited_args_from_template %}')
     220        self.assertEqual(t.render(c), u'inclusion_only_unlimited_args_from_template - Expected result: \n')
     221
     222        t = template.Template('{% load custom %}{% inclusion_only_unlimited_args_from_template 37 42 56 89 %}')
     223        self.assertEqual(t.render(c), u'inclusion_only_unlimited_args_from_template - Expected result: 37, 42, 56, 89\n')
     224
    92225    def test_inclusion_tag_registration(self):
    93226        # Test that the decorators preserve the decorated function's docstring, name and attributes.
    94227        self.verify_tag(custom.inclusion_no_params, 'inclusion_no_params')
    class CustomTagTests(TestCase):  
    96229        self.verify_tag(custom.inclusion_explicit_no_context, 'inclusion_explicit_no_context')
    97230        self.verify_tag(custom.inclusion_no_params_with_context, 'inclusion_no_params_with_context')
    98231        self.verify_tag(custom.inclusion_params_and_context, 'inclusion_params_and_context')
     232        self.verify_tag(custom.inclusion_two_params, 'inclusion_two_params')
     233        self.verify_tag(custom.inclusion_one_default, 'inclusion_one_default')
     234        self.verify_tag(custom.inclusion_unlimited_args, 'inclusion_unlimited_args')
     235        self.verify_tag(custom.inclusion_only_unlimited_args, 'inclusion_only_unlimited_args')
     236        self.verify_tag(custom.inclusion_tag_without_context_parameter, 'inclusion_tag_without_context_parameter')
     237        self.verify_tag(custom.inclusion_tag_use_l10n, 'inclusion_tag_use_l10n')
     238        self.verify_tag(custom.inclusion_tag_current_app, 'inclusion_tag_current_app')
     239        self.verify_tag(custom.inclusion_unlimited_args_kwargs, 'inclusion_unlimited_args_kwargs')
    99240
    100241    def test_15070_current_app(self):
    101242        """
    class CustomTagTests(TestCase):  
    139280        t = template.Template('{% load custom %}{% assignment_params_and_context 37 as var %}The result is: {{ var }}')
    140281        self.assertEqual(t.render(c), u'The result is: assignment_params_and_context - Expected result (context value: 42): 37')
    141282
     283        t = template.Template('{% load custom %}{% assignment_two_params 37 42 as var %}The result is: {{ var }}')
     284        self.assertEqual(t.render(c), u'The result is: assignment_two_params - Expected result: 37, 42')
     285
     286        t = template.Template('{% load custom %}{% assignment_one_default 37 as var %}The result is: {{ var }}')
     287        self.assertEqual(t.render(c), u'The result is: assignment_one_default - Expected result: 37, hi')
     288
     289        t = template.Template('{% load custom %}{% assignment_one_default 37 two="hello" as var %}The result is: {{ var }}')
     290        self.assertEqual(t.render(c), u'The result is: assignment_one_default - Expected result: 37, hello')
     291
     292        t = template.Template('{% load custom %}{% assignment_one_default one=99 two="hello" as var %}The result is: {{ var }}')
     293        self.assertEqual(t.render(c), u'The result is: assignment_one_default - Expected result: 99, hello')
     294
     295        self.assertRaisesRegexp(template.TemplateSyntaxError,
     296            "'assignment_one_default' received unexpected keyword argument 'three'",
     297            template.Template, '{% load custom %}{% assignment_one_default 99 two="hello" three="foo" as var %}')
     298
     299        t = template.Template('{% load custom %}{% assignment_one_default 37 42 as var %}The result is: {{ var }}')
     300        self.assertEqual(t.render(c), u'The result is: assignment_one_default - Expected result: 37, 42')
     301
     302        t = template.Template('{% load custom %}{% assignment_unlimited_args 37 as var %}The result is: {{ var }}')
     303        self.assertEqual(t.render(c), u'The result is: assignment_unlimited_args - Expected result: 37, hi')
     304
     305        t = template.Template('{% load custom %}{% assignment_unlimited_args 37 42 56 89 as var %}The result is: {{ var }}')
     306        self.assertEqual(t.render(c), u'The result is: assignment_unlimited_args - Expected result: 37, 42, 56, 89')
     307
     308        t = template.Template('{% load custom %}{% assignment_only_unlimited_args as var %}The result is: {{ var }}')
     309        self.assertEqual(t.render(c), u'The result is: assignment_only_unlimited_args - Expected result: ')
     310
     311        t = template.Template('{% load custom %}{% assignment_only_unlimited_args 37 42 56 89 as var %}The result is: {{ var }}')
     312        self.assertEqual(t.render(c), u'The result is: assignment_only_unlimited_args - Expected result: 37, 42, 56, 89')
     313
    142314        self.assertRaisesRegexp(template.TemplateSyntaxError,
    143315            "'assignment_one_param' tag takes at least 2 arguments and the second last argument must be 'as'",
    144316            template.Template, '{% load custom %}{% assignment_one_param 37 %}The result is: {{ var }}')
    class CustomTagTests(TestCase):  
    151323            "'assignment_one_param' tag takes at least 2 arguments and the second last argument must be 'as'",
    152324            template.Template, '{% load custom %}{% assignment_one_param 37 ass var %}The result is: {{ var }}')
    153325
     326        self.assertRaisesRegexp(template.TemplateSyntaxError,
     327            "'assignment_two_params' received too many positional arguments",
     328            template.Template, '{% load custom %}{% assignment_two_params 37 42 56 as var %}The result is: {{ var }}')
     329
     330        self.assertRaisesRegexp(template.TemplateSyntaxError,
     331            "'assignment_one_default' received too many positional arguments",
     332            template.Template, '{% load custom %}{% assignment_one_default 37 42 56 as var %}The result is: {{ var }}')
     333
     334        self.assertRaisesRegexp(template.TemplateSyntaxError,
     335            "'assignment_one_default' did not receive value\(s\) for the argument\(s\): 'one'",
     336            template.Template, '{% load custom %}{% assignment_one_default as var %}The result is: {{ var }}')
     337
     338        self.assertRaisesRegexp(template.TemplateSyntaxError,
     339            "'assignment_unlimited_args' did not receive value\(s\) for the argument\(s\): 'one'",
     340            template.Template, '{% load custom %}{% assignment_unlimited_args as var %}The result is: {{ var }}')
     341
     342        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 }}')
     343        self.assertEqual(t.render(c), u'The result is: assignment_unlimited_args_kwargs - Expected result: 37, 42, 56 / eggs=scrambled, four=4')
     344
     345        self.assertRaisesRegexp(template.TemplateSyntaxError,
     346            "'assignment_unlimited_args_kwargs' received some positional argument\(s\) after some keyword argument\(s\)",
     347            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 }}')
     348
     349        self.assertRaisesRegexp(template.TemplateSyntaxError,
     350            "'assignment_unlimited_args_kwargs' received multiple values for keyword argument 'eggs'",
     351            template.Template, '{% load custom %}{% assignment_unlimited_args_kwargs 37 eggs="scrambled" eggs="scrambled" as var %}The result is: {{ var }}')
     352
    154353    def test_assignment_tag_registration(self):
    155354        # Test that the decorators preserve the decorated function's docstring, name and attributes.
    156355        self.verify_tag(custom.assignment_no_params, 'assignment_no_params')
    class CustomTagTests(TestCase):  
    158357        self.verify_tag(custom.assignment_explicit_no_context, 'assignment_explicit_no_context')
    159358        self.verify_tag(custom.assignment_no_params_with_context, 'assignment_no_params_with_context')
    160359        self.verify_tag(custom.assignment_params_and_context, 'assignment_params_and_context')
     360        self.verify_tag(custom.assignment_one_default, 'assignment_one_default')
     361        self.verify_tag(custom.assignment_two_params, 'assignment_two_params')
     362        self.verify_tag(custom.assignment_unlimited_args, 'assignment_unlimited_args')
     363        self.verify_tag(custom.assignment_only_unlimited_args, 'assignment_only_unlimited_args')
     364        self.verify_tag(custom.assignment_unlimited_args, 'assignment_unlimited_args')
     365        self.verify_tag(custom.assignment_unlimited_args_kwargs, 'assignment_unlimited_args_kwargs')
     366        self.verify_tag(custom.assignment_tag_without_context_parameter, 'assignment_tag_without_context_parameter')
    161367
    162368    def test_assignment_tag_missing_context(self):
    163         # That the 'context' parameter must be present when takes_context is True
    164         def an_assignment_tag_without_parameters(arg):
    165             """Expected __doc__"""
    166             return "Expected result"
    167 
    168         register = template.Library()
    169         decorator = register.assignment_tag(takes_context=True)
    170 
     369        # The 'context' parameter must be present when takes_context is True
    171370        self.assertRaisesRegexp(template.TemplateSyntaxError,
    172             "Any tag function decorated with takes_context=True must have a first argument of 'context'",
    173             decorator, an_assignment_tag_without_parameters)
     371            "'assignment_tag_without_context_parameter' is decorated with takes_context=True so it must have a first argument of 'context'",
     372            template.Template, '{% load custom %}{% assignment_tag_without_context_parameter 123 as var %}')
  • tests/regressiontests/templates/templatetags/custom.py

    diff --git a/tests/regressiontests/templates/templatetags/custom.py b/tests/regressiontests/templates/templatetags/custom.py
    index dfa4171..8620661 100644
    a b  
     1import operator
     2
    13from django import template
    24from django.template.defaultfilters import stringfilter
    35from django.template.loader import get_template
    def params_and_context(context, arg):  
    4042    return "params_and_context - Expected result (context value: %s): %s" % (context['value'], arg)
    4143params_and_context.anything = "Expected params_and_context __dict__"
    4244
     45@register.simple_tag
     46def simple_two_params(one, two):
     47    """Expected simple_two_params __doc__"""
     48    return "simple_two_params - Expected result: %s, %s" % (one, two)
     49simple_two_params.anything = "Expected simple_two_params __dict__"
     50
     51@register.simple_tag
     52def simple_one_default(one, two='hi'):
     53    """Expected simple_one_default __doc__"""
     54    return "simple_one_default - Expected result: %s, %s" % (one, two)
     55simple_one_default.anything = "Expected simple_one_default __dict__"
     56
     57@register.simple_tag
     58def simple_unlimited_args(one, two='hi', *args):
     59    """Expected simple_unlimited_args __doc__"""
     60    return "simple_unlimited_args - Expected result: %s" % (', '.join([unicode(arg) for arg in [one, two] + list(args)]))
     61simple_unlimited_args.anything = "Expected simple_unlimited_args __dict__"
     62
     63@register.simple_tag
     64def simple_only_unlimited_args(*args):
     65    """Expected simple_only_unlimited_args __doc__"""
     66    return "simple_only_unlimited_args - Expected result: %s" % ', '.join([unicode(arg) for arg in args])
     67simple_only_unlimited_args.anything = "Expected simple_only_unlimited_args __dict__"
     68
     69@register.simple_tag
     70def simple_unlimited_args_kwargs(one, two='hi', *args, **kwargs):
     71    """Expected simple_unlimited_args_kwargs __doc__"""
     72    # Sort the dictionary by key to guarantee the order for testing.
     73    sorted_kwarg = sorted(kwargs.iteritems(), key=operator.itemgetter(0))
     74    return "simple_unlimited_args_kwargs - Expected result: %s / %s" % (
     75        ', '.join([unicode(arg) for arg in [one, two] + list(args)]),
     76        ', '.join(['%s=%s' % (k, v) for (k, v) in sorted_kwarg])
     77        )
     78simple_unlimited_args_kwargs.anything = "Expected simple_unlimited_args_kwargs __dict__"
     79
     80@register.simple_tag(takes_context=True)
     81def simple_tag_without_context_parameter(arg):
     82    """Expected simple_tag_without_context_parameter __doc__"""
     83    return "Expected result"
     84simple_tag_without_context_parameter.anything = "Expected simple_tag_without_context_parameter __dict__"
     85
     86@register.simple_tag(takes_context=True)
     87def current_app(context):
     88    return "%s" % context.current_app
     89
     90@register.simple_tag(takes_context=True)
     91def use_l10n(context):
     92    return "%s" % context.use_l10n
     93
     94@register.simple_tag(name='minustwo')
     95def minustwo_overridden_name(value):
     96    return value - 2
     97
     98register.simple_tag(lambda x: x - 1, name='minusone')
     99
    43100@register.inclusion_tag('inclusion.html')
    44101def inclusion_no_params():
    45102    """Expected inclusion_no_params __doc__"""
    def inclusion_params_and_context_from_template(context, arg):  
    100157    return {"result" : "inclusion_params_and_context_from_template - Expected result (context value: %s): %s" % (context['value'], arg)}
    101158inclusion_params_and_context_from_template.anything = "Expected inclusion_params_and_context_from_template __dict__"
    102159
    103 @register.simple_tag(takes_context=True)
    104 def current_app(context):
    105     return "%s" % context.current_app
     160@register.inclusion_tag('inclusion.html')
     161def inclusion_two_params(one, two):
     162    """Expected inclusion_two_params __doc__"""
     163    return {"result": "inclusion_two_params - Expected result: %s, %s" % (one, two)}
     164inclusion_two_params.anything = "Expected inclusion_two_params __dict__"
     165
     166@register.inclusion_tag(get_template('inclusion.html'))
     167def inclusion_two_params_from_template(one, two):
     168    """Expected inclusion_two_params_from_template __doc__"""
     169    return {"result": "inclusion_two_params_from_template - Expected result: %s, %s" % (one, two)}
     170inclusion_two_params_from_template.anything = "Expected inclusion_two_params_from_template __dict__"
     171
     172@register.inclusion_tag('inclusion.html')
     173def inclusion_one_default(one, two='hi'):
     174    """Expected inclusion_one_default __doc__"""
     175    return {"result": "inclusion_one_default - Expected result: %s, %s" % (one, two)}
     176inclusion_one_default.anything = "Expected inclusion_one_default __dict__"
     177
     178@register.inclusion_tag(get_template('inclusion.html'))
     179def inclusion_one_default_from_template(one, two='hi'):
     180    """Expected inclusion_one_default_from_template __doc__"""
     181    return {"result": "inclusion_one_default_from_template - Expected result: %s, %s" % (one, two)}
     182inclusion_one_default_from_template.anything = "Expected inclusion_one_default_from_template __dict__"
     183
     184@register.inclusion_tag('inclusion.html')
     185def inclusion_unlimited_args(one, two='hi', *args):
     186    """Expected inclusion_unlimited_args __doc__"""
     187    return {"result": "inclusion_unlimited_args - Expected result: %s" % (', '.join([unicode(arg) for arg in [one, two] + list(args)]))}
     188inclusion_unlimited_args.anything = "Expected inclusion_unlimited_args __dict__"
     189
     190@register.inclusion_tag(get_template('inclusion.html'))
     191def inclusion_unlimited_args_from_template(one, two='hi', *args):
     192    """Expected inclusion_unlimited_args_from_template __doc__"""
     193    return {"result": "inclusion_unlimited_args_from_template - Expected result: %s" % (', '.join([unicode(arg) for arg in [one, two] + list(args)]))}
     194inclusion_unlimited_args_from_template.anything = "Expected inclusion_unlimited_args_from_template __dict__"
     195
     196@register.inclusion_tag('inclusion.html')
     197def inclusion_only_unlimited_args(*args):
     198    """Expected inclusion_only_unlimited_args __doc__"""
     199    return {"result": "inclusion_only_unlimited_args - Expected result: %s" % (', '.join([unicode(arg) for arg in args]))}
     200inclusion_only_unlimited_args.anything = "Expected inclusion_only_unlimited_args __dict__"
     201
     202@register.inclusion_tag(get_template('inclusion.html'))
     203def inclusion_only_unlimited_args_from_template(*args):
     204    """Expected inclusion_only_unlimited_args_from_template __doc__"""
     205    return {"result": "inclusion_only_unlimited_args_from_template - Expected result: %s" % (', '.join([unicode(arg) for arg in args]))}
     206inclusion_only_unlimited_args_from_template.anything = "Expected inclusion_only_unlimited_args_from_template __dict__"
    106207
    107208@register.inclusion_tag('test_incl_tag_current_app.html', takes_context=True)
    108209def inclusion_tag_current_app(context):
     210    """Expected inclusion_tag_current_app __doc__"""
    109211    return {}
    110 
    111 @register.simple_tag(takes_context=True)
    112 def use_l10n(context):
    113     return "%s" % context.use_l10n
     212inclusion_tag_current_app.anything = "Expected inclusion_tag_current_app __dict__"
    114213
    115214@register.inclusion_tag('test_incl_tag_use_l10n.html', takes_context=True)
    116215def inclusion_tag_use_l10n(context):
     216    """Expected inclusion_tag_use_l10n __doc__"""
     217    return {}
     218inclusion_tag_use_l10n.anything = "Expected inclusion_tag_use_l10n __dict__"
     219
     220@register.inclusion_tag('inclusion.html')
     221def inclusion_unlimited_args_kwargs(one, two='hi', *args, **kwargs):
     222    """Expected inclusion_unlimited_args_kwargs __doc__"""
     223    # Sort the dictionary by key to guarantee the order for testing.
     224    sorted_kwarg = sorted(kwargs.iteritems(), key=operator.itemgetter(0))
     225    return {"result": "inclusion_unlimited_args_kwargs - Expected result: %s / %s" % (
     226        ', '.join([unicode(arg) for arg in [one, two] + list(args)]),
     227        ', '.join(['%s=%s' % (k, v) for (k, v) in sorted_kwarg])
     228        )}
     229inclusion_unlimited_args_kwargs.anything = "Expected inclusion_unlimited_args_kwargs __dict__"
     230
     231@register.inclusion_tag('inclusion.html', takes_context=True)
     232def inclusion_tag_without_context_parameter(arg):
     233    """Expected inclusion_tag_without_context_parameter __doc__"""
    117234    return {}
     235inclusion_tag_without_context_parameter.anything = "Expected inclusion_tag_without_context_parameter __dict__"
    118236
    119237@register.assignment_tag
    120238def assignment_no_params():
    def assignment_params_and_context(context, arg):  
    146264    return "assignment_params_and_context - Expected result (context value: %s): %s" % (context['value'], arg)
    147265assignment_params_and_context.anything = "Expected assignment_params_and_context __dict__"
    148266
    149 register.simple_tag(lambda x: x - 1, name='minusone')
     267@register.assignment_tag
     268def assignment_two_params(one, two):
     269    """Expected assignment_two_params __doc__"""
     270    return "assignment_two_params - Expected result: %s, %s" % (one, two)
     271assignment_two_params.anything = "Expected assignment_two_params __dict__"
    150272
    151 @register.simple_tag(name='minustwo')
    152 def minustwo_overridden_name(value):
    153     return value - 2
     273@register.assignment_tag
     274def assignment_one_default(one, two='hi'):
     275    """Expected assignment_one_default __doc__"""
     276    return "assignment_one_default - Expected result: %s, %s" % (one, two)
     277assignment_one_default.anything = "Expected assignment_one_default __dict__"
     278
     279@register.assignment_tag
     280def assignment_unlimited_args(one, two='hi', *args):
     281    """Expected assignment_unlimited_args __doc__"""
     282    return "assignment_unlimited_args - Expected result: %s" % (', '.join([unicode(arg) for arg in [one, two] + list(args)]))
     283assignment_unlimited_args.anything = "Expected assignment_unlimited_args __dict__"
     284
     285@register.assignment_tag
     286def assignment_only_unlimited_args(*args):
     287    """Expected assignment_only_unlimited_args __doc__"""
     288    return "assignment_only_unlimited_args - Expected result: %s" % ', '.join([unicode(arg) for arg in args])
     289assignment_only_unlimited_args.anything = "Expected assignment_only_unlimited_args __dict__"
     290
     291@register.assignment_tag
     292def assignment_unlimited_args_kwargs(one, two='hi', *args, **kwargs):
     293    """Expected assignment_unlimited_args_kwargs __doc__"""
     294    # Sort the dictionary by key to guarantee the order for testing.
     295    sorted_kwarg = sorted(kwargs.iteritems(), key=operator.itemgetter(0))
     296    return "assignment_unlimited_args_kwargs - Expected result: %s / %s" % (
     297        ', '.join([unicode(arg) for arg in [one, two] + list(args)]),
     298        ', '.join(['%s=%s' % (k, v) for (k, v) in sorted_kwarg])
     299        )
     300assignment_unlimited_args_kwargs.anything = "Expected assignment_unlimited_args_kwargs __dict__"
     301
     302@register.assignment_tag(takes_context=True)
     303def assignment_tag_without_context_parameter(arg):
     304    """Expected assignment_tag_without_context_parameter __doc__"""
     305    return "Expected result"
     306assignment_tag_without_context_parameter.anything = "Expected assignment_tag_without_context_parameter __dict__"
     307 No newline at end of file
Back to Top