Ticket #13956: 13956.ttag_helpers_args_kwargs_support.2.diff

File 13956.ttag_helpers_args_kwargs_support.2.diff, 73.2 KB (added by Jannis Leidel, 13 years ago)

Updated patch with PEP8 fixes.

  • django/template/base.py

    diff --git a/django/template/base.py b/django/template/base.py
    index c94eeb5..6fad075 100644
    a b TOKEN_TEXT = 0  
    1919TOKEN_VAR = 1
    2020TOKEN_BLOCK = 2
    2121TOKEN_COMMENT = 3
     22TOKEN_MAPPING = {
     23    TOKEN_TEXT: 'Text',
     24    TOKEN_VAR: 'Var',
     25    TOKEN_BLOCK: 'Block',
     26    TOKEN_COMMENT: 'Comment',
     27}
    2228
    2329# template syntax constants
    2430FILTER_SEPARATOR = '|'
    ALLOWED_VARIABLE_CHARS = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01  
    4147UNKNOWN_SOURCE = '<unknown source>'
    4248
    4349# 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)))
     50tag_re = (re.compile('(%s.*?%s|%s.*?%s|%s.*?%s)' %
     51          (re.escape(BLOCK_TAG_START), re.escape(BLOCK_TAG_END),
     52           re.escape(VARIABLE_TAG_START), re.escape(VARIABLE_TAG_END),
     53           re.escape(COMMENT_TAG_START), re.escape(COMMENT_TAG_END))))
    4754
    4855# global dictionary of libraries that have been loaded using get_library
    4956libraries = {}
    class VariableDoesNotExist(Exception):  
    7380        return unicode(self).encode('utf-8')
    7481
    7582    def __unicode__(self):
    76         return self.msg % tuple([force_unicode(p, errors='replace') for p in self.params])
     83        return self.msg % tuple([force_unicode(p, errors='replace')
     84                                 for p in self.params])
    7785
    7886class InvalidTemplateLibrary(Exception):
    7987    pass
    class Template(object):  
    101109        try:
    102110            template_string = smart_unicode(template_string)
    103111        except UnicodeDecodeError:
    104             raise TemplateEncodingError("Templates can only be constructed from unicode or UTF-8 strings.")
     112            raise TemplateEncodingError("Templates can only be constructed "
     113                                        "from unicode or UTF-8 strings.")
    105114        if settings.TEMPLATE_DEBUG and origin is None:
    106115            origin = StringOrigin(template_string)
    107116        self.nodelist = compile_string(template_string, origin)
    class Token(object):  
    141150        self.lineno = None
    142151
    143152    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', ''))
     153        token_name = TOKEN_MAPPING[self.token_type]
     154        return ('<%s token: "%s...">' %
     155                (token_name, self.contents[:20].replace('\n', '')))
    147156
    148157    def split_contents(self):
    149158        split = []
    class Parser(object):  
    207216            self.add_library(lib)
    208217
    209218    def parse(self, parse_until=None):
    210         if parse_until is None: parse_until = []
     219        if parse_until is None:
     220            parse_until = []
    211221        nodelist = self.create_nodelist()
    212222        while self.tokens:
    213223            token = self.next_token()
    class Parser(object):  
    218228                    self.empty_variable(token)
    219229                filter_expression = self.compile_filter(token.contents)
    220230                var_node = self.create_variable_node(filter_expression)
    221                 self.extend_nodelist(nodelist, var_node,token)
     231                self.extend_nodelist(nodelist, var_node, token)
    222232            elif token.token_type == TOKEN_BLOCK:
    223233                if token.contents in parse_until:
    224                     # put token back on token list so calling code knows why it terminated
     234                    # put token back on token list so calling
     235                    # code knows why it terminated
    225236                    self.prepend_token(token)
    226237                    return nodelist
    227238                try:
    228239                    command = token.contents.split()[0]
    229240                except IndexError:
    230241                    self.empty_block_tag(token)
    231                 # execute callback function for this tag and append resulting node
     242                # execute callback function for this tag and append
     243                # resulting node
    232244                self.enter_command(command, token)
    233245                try:
    234246                    compile_func = self.tags[command]
    class Parser(object):  
    264276                if nodelist.contains_nontext:
    265277                    raise AttributeError
    266278            except AttributeError:
    267                 raise TemplateSyntaxError("%r must be the first tag in the template." % node)
     279                raise TemplateSyntaxError("%r must be the first tag "
     280                                          "in the template." % node)
    268281        if isinstance(nodelist, NodeList) and not isinstance(node, TextNode):
    269282            nodelist.contains_nontext = True
    270283        nodelist.append(node)
    class Parser(object):  
    286299
    287300    def invalid_block_tag(self, token, command, parse_until=None):
    288301        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])))
     302            raise self.error(token, "Invalid block tag: '%s', expected %s" %
     303                (command, get_text_list(["'%s'" % p for p in parse_until])))
    290304        raise self.error(token, "Invalid block tag: '%s'" % command)
    291305
    292306    def unclosed_block_tag(self, parse_until):
    293         raise self.error(None, "Unclosed tags: %s " %  ', '.join(parse_until))
     307        raise self.error(None, "Unclosed tags: %s " % ', '.join(parse_until))
    294308
    295309    def compile_function_error(self, token, e):
    296310        pass
    class Parser(object):  
    320334
    321335class TokenParser(object):
    322336    """
    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.
     337    Subclass this and implement the top() method to parse a template line.
     338    When instantiating the parser, pass in the line from the Django template
     339    parser.
    325340
    326341    The parser's "tagname" instance-variable stores the name of the tag that
    327342    the filter was called with.
    class TokenParser(object):  
    351366        subject = self.subject
    352367        i = self.pointer
    353368        if i >= len(subject):
    354             raise TemplateSyntaxError("expected another tag, found end of string: %s" % subject)
     369            raise TemplateSyntaxError("expected another tag, found "
     370                                      "end of string: %s" % subject)
    355371        p = i
    356372        while i < len(subject) and subject[i] not in (' ', '\t'):
    357373            i += 1
    class TokenParser(object):  
    363379        return s
    364380
    365381    def value(self):
    366         "A microparser that parses for a value: some string constant or variable name."
     382        """
     383        A microparser that parses for a value: some string constant or
     384        variable name.
     385        """
    367386        subject = self.subject
    368387        i = self.pointer
    369388
    370389        def next_space_index(subject, i):
    371             "Increment pointer until a real space (i.e. a space not within quotes) is encountered"
     390            """
     391            Increment pointer until a real space (i.e. a space not within
     392            quotes) is encountered
     393            """
    372394            while i < len(subject) and subject[i] not in (' ', '\t'):
    373395                if subject[i] in ('"', "'"):
    374396                    c = subject[i]
    class TokenParser(object):  
    376398                    while i < len(subject) and subject[i] != c:
    377399                        i += 1
    378400                    if i >= len(subject):
    379                         raise TemplateSyntaxError("Searching for value. Unexpected end of string in column %d: %s" % (i, subject))
     401                        raise TemplateSyntaxError("Searching for value. "
     402                            "Unexpected end of string in column %d: %s" %
     403                            (i, subject))
    380404                i += 1
    381405            return i
    382406
    383407        if i >= len(subject):
    384             raise TemplateSyntaxError("Searching for value. Expected another value but found end of string: %s" % subject)
     408            raise TemplateSyntaxError("Searching for value. Expected another "
     409                                      "value but found end of string: %s" %
     410                                      subject)
    385411        if subject[i] in ('"', "'"):
    386412            p = i
    387413            i += 1
    388414            while i < len(subject) and subject[i] != subject[p]:
    389415                i += 1
    390416            if i >= len(subject):
    391                 raise TemplateSyntaxError("Searching for value. Unexpected end of string in column %d: %s" % (i, subject))
     417                raise TemplateSyntaxError("Searching for value. Unexpected "
     418                                          "end of string in column %d: %s" %
     419                                          (i, subject))
    392420            i += 1
    393421
    394             # Continue parsing until next "real" space, so that filters are also included
     422            # Continue parsing until next "real" space,
     423            # so that filters are also included
    395424            i = next_space_index(subject, i)
    396425
    397426            res = subject[p:i]
    constant_string = r"""  
    419448%(strdq)s|
    420449%(strsq)s)
    421450""" % {
    422     'strdq': r'"[^"\\]*(?:\\.[^"\\]*)*"', # double-quoted string
    423     'strsq': r"'[^'\\]*(?:\\.[^'\\]*)*'", # single-quoted string
    424     'i18n_open' : re.escape("_("),
    425     'i18n_close' : re.escape(")"),
     451    'strdq': r'"[^"\\]*(?:\\.[^"\\]*)*"',  # double-quoted string
     452    'strsq': r"'[^'\\]*(?:\\.[^'\\]*)*'",  # single-quoted string
     453    'i18n_open': re.escape("_("),
     454    'i18n_close': re.escape(")"),
    426455    }
    427456constant_string = constant_string.replace("\n", "")
    428457
    filter_raw_string = r"""  
    440469 )""" % {
    441470    'constant': constant_string,
    442471    'num': r'[-+\.]?\d[\d\.e]*',
    443     'var_chars': "\w\." ,
     472    'var_chars': "\w\.",
    444473    'filter_sep': re.escape(FILTER_SEPARATOR),
    445474    'arg_sep': re.escape(FILTER_ARGUMENT_SEPARATOR),
    446475  }
    447476
    448 filter_re = re.compile(filter_raw_string, re.UNICODE|re.VERBOSE)
     477filter_re = re.compile(filter_raw_string, re.UNICODE | re.VERBOSE)
    449478
    450479class FilterExpression(object):
    451     r"""
     480    """
    452481    Parses a variable token and its optional filters (all as a single string),
    453482    and return a list of tuples of the filter name and arguments.
    454     Sample:
     483    Sample::
     484
    455485        >>> token = 'variable|default:"Default value"|date:"Y-m-d"'
    456486        >>> p = Parser('')
    457487        >>> fe = FilterExpression(token, p)
    class FilterExpression(object):  
    472502        for match in matches:
    473503            start = match.start()
    474504            if upto != start:
    475                 raise TemplateSyntaxError("Could not parse some characters: %s|%s|%s"  % \
    476                         (token[:upto], token[upto:start], token[start:]))
     505                raise TemplateSyntaxError("Could not parse some characters: "
     506                                          "%s|%s|%s" %
     507                                          (token[:upto], token[upto:start],
     508                                           token[start:]))
    477509            if var_obj is None:
    478510                var, constant = match.group("var", "constant")
    479511                if constant:
    class FilterExpression(object):  
    482514                    except VariableDoesNotExist:
    483515                        var_obj = None
    484516                elif var is None:
    485                     raise TemplateSyntaxError("Could not find variable at start of %s." % token)
     517                    raise TemplateSyntaxError("Could not find variable at "
     518                                              "start of %s." % token)
    486519                else:
    487520                    var_obj = Variable(var)
    488521            else:
    class FilterExpression(object):  
    498531                filters.append((filter_func, args))
    499532            upto = match.end()
    500533        if upto != len(token):
    501             raise TemplateSyntaxError("Could not parse the remainder: '%s' from '%s'" % (token[upto:], token))
     534            raise TemplateSyntaxError("Could not parse the remainder: '%s' "
     535                                      "from '%s'" % (token[upto:], token))
    502536
    503537        self.filters = filters
    504538        self.var = var_obj
    class FilterExpression(object):  
    559593                provided.pop(0)
    560594        except IndexError:
    561595            # Not enough
    562             raise TemplateSyntaxError("%s requires %d arguments, %d provided" % (name, len(nondefs), plen))
     596            raise TemplateSyntaxError("%s requires %d arguments, %d provided" %
     597                                      (name, len(nondefs), plen))
    563598
    564599        # Defaults can be overridden.
    565600        defaults = defaults and list(defaults) or []
    class FilterExpression(object):  
    568603                defaults.pop(0)
    569604        except IndexError:
    570605            # Too many.
    571             raise TemplateSyntaxError("%s requires %d arguments, %d provided" % (name, len(nondefs), plen))
     606            raise TemplateSyntaxError("%s requires %d arguments, %d provided" %
     607                                      (name, len(nondefs), plen))
    572608
    573609        return True
    574610    args_check = staticmethod(args_check)
    def resolve_variable(path, context):  
    586622    return Variable(path).resolve(context)
    587623
    588624class 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
     625    """
     626    A template variable, resolvable against a given context. The variable may
     627    be a hard-coded string (if it begins and ends with single or double quote
    592628    marks)::
    593629
    594630        >>> c = {'article': {'section':u'News'}}
    class Variable(object):  
    642678                # Otherwise we'll set self.lookups so that resolve() knows we're
    643679                # dealing with a bonafide variable
    644680                if var.find(VARIABLE_ATTRIBUTE_SEPARATOR + '_') > -1 or var[0] == '_':
    645                     raise TemplateSyntaxError("Variables and attributes may not begin with underscores: '%s'" % var)
     681                    raise TemplateSyntaxError("Variables and attributes may "
     682                                              "not begin with underscores: '%s'" %
     683                                              var)
    646684                self.lookups = tuple(var.split(VARIABLE_ATTRIBUTE_SEPARATOR))
    647685
    648686    def resolve(self, context):
    class Variable(object):  
    673711        instead.
    674712        """
    675713        current = context
    676         try: # catch-all for silent variable failures
     714        try:  # catch-all for silent variable failures
    677715            for bit in self.lookups:
    678                 try: # dictionary lookup
     716                try:  # dictionary lookup
    679717                    current = current[bit]
    680718                except (TypeError, AttributeError, KeyError):
    681                     try: # attribute lookup
     719                    try:  # attribute lookup
    682720                        current = getattr(current, bit)
    683721                    except (TypeError, AttributeError):
    684                         try: # list-index lookup
     722                        try:  # list-index lookup
    685723                            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
     724                        except (IndexError,  # list index out of range
     725                                ValueError,  # invalid literal for int()
     726                                KeyError,    # current is a dict without `int(bit)` key
     727                                TypeError):  # unsubscriptable object
     728                            raise VariableDoesNotExist("Failed lookup for key "
     729                                                       "[%s] in %r",
     730                                                       (bit, current))  # missing attribute
    692731                if callable(current):
    693732                    if getattr(current, 'do_not_call_in_templates', False):
    694733                        pass
    class Variable(object):  
    700739                        except TypeError: # arguments *were* required
    701740                            # GOTCHA: This will also catch any TypeError
    702741                            # raised in the function itself.
    703                             current = settings.TEMPLATE_STRING_IF_INVALID # invalid method call
     742                            current = settings.TEMPLATE_STRING_IF_INVALID  # invalid method call
    704743        except Exception, e:
    705744            if getattr(e, 'silent_variable_failure', False):
    706745                current = settings.TEMPLATE_STRING_IF_INVALID
    class Node(object):  
    723762        yield self
    724763
    725764    def get_nodes_by_type(self, nodetype):
    726         "Return a list of all nodes (within this node and its nodelist) of the given type"
     765        """
     766        Return a list of all nodes (within this node and its nodelist)
     767        of the given type
     768        """
    727769        nodes = []
    728770        if isinstance(self, nodetype):
    729771            nodes.append(self)
    def _render_value_in_context(value, context):  
    776818    """
    777819    value = localize(value, use_l10n=context.use_l10n)
    778820    value = force_unicode(value)
    779     if (context.autoescape and not isinstance(value, SafeData)) or isinstance(value, EscapeData):
     821    if ((context.autoescape and not isinstance(value, SafeData)) or
     822            isinstance(value, EscapeData)):
    780823        return escape(value)
    781824    else:
    782825        return value
    class VariableNode(Node):  
    793836            output = self.filter_expression.resolve(context)
    794837        except UnicodeDecodeError:
    795838            # Unicode conversion can fail sometimes for reasons out of our
    796             # control (e.g. exception rendering). In that case, we fail quietly.
     839            # control (e.g. exception rendering). In that case, we fail
     840            # quietly.
    797841            return ''
    798842        return _render_value_in_context(output, context)
    799843
    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)
     844# Regex for token keyword arguments
     845kwarg_re = re.compile(r"(?:(\w+)=)?(.+)")
     846
     847def token_kwargs(bits, parser, support_legacy=False):
     848    """
     849    A utility method for parsing token keyword arguments.
     850
     851    :param bits: A list containing remainder of the token (split by spaces)
     852        that is to be checked for arguments. Valid arguments will be removed
     853        from this list.
     854
     855    :param support_legacy: If set to true ``True``, the legacy format
     856        ``1 as foo`` will be accepted. Otherwise, only the standard ``foo=1``
     857        format is allowed.
     858
     859    :returns: A dictionary of the arguments retrieved from the ``bits`` token
     860        list.
     861
     862    There is no requirement for all remaining token ``bits`` to be keyword
     863    arguments, so the dictionary will be returned as soon as an invalid
     864    argument format is reached.
     865    """
     866    if not bits:
     867        return {}
     868    match = kwarg_re.match(bits[0])
     869    kwarg_format = match and match.group(1)
     870    if not kwarg_format:
     871        if not support_legacy:
     872            return {}
     873        if len(bits) < 3 or bits[1] != 'as':
     874            return {}
     875
     876    kwargs = {}
     877    while bits:
     878        if kwarg_format:
     879            match = kwarg_re.match(bits[0])
     880            if not match or not match.group(1):
     881                return kwargs
     882            key, value = match.groups()
     883            del bits[:1]
    809884        else:
    810             message = "%s takes between %s and %s arguments" % (name, bmin, bmax)
    811         raise TemplateSyntaxError(message)
    812     return node_class(bits)
     885            if len(bits) < 3 or bits[1] != 'as':
     886                return kwargs
     887            key, value = bits[2], bits[0]
     888            del bits[:3]
     889        kwargs[key] = parser.compile_filter(value)
     890        if bits and not kwarg_format:
     891            if bits[0] != 'and':
     892                return kwargs
     893            del bits[:1]
     894    return kwargs
     895
     896def parse_bits(parser, bits, params, varargs, varkw, defaults,
     897               takes_context, name):
     898    """
     899    Parses bits for template tag helpers (simple_tag, include_tag and
     900    assignment_tag), in particular by detecting syntax errors and by
     901    extracting positional and keyword arguments.
     902    """
     903    if takes_context:
     904        if params[0] == 'context':
     905            params = params[1:]
     906        else:
     907            raise TemplateSyntaxError(
     908                "'%s' is decorated with takes_context=True so it must "
     909                "have a first argument of 'context'" % name)
     910    args = []
     911    kwargs = {}
     912    unhandled_params = list(params)
     913    for bit in bits:
     914        # First we try to extract a potential kwarg from the bit
     915        kwarg = token_kwargs([bit], parser)
     916        if kwarg:
     917            # The kwarg was successfully extracted
     918            param, value = kwarg.items()[0]
     919            if param not in params and varkw is None:
     920                # An unexpected keyword argument was supplied
     921                raise TemplateSyntaxError(
     922                    "'%s' received unexpected keyword argument '%s'" %
     923                    (name, param))
     924            elif param in kwargs:
     925                # The keyword argument has already been supplied once
     926                raise TemplateSyntaxError(
     927                    "'%s' received multiple values for keyword argument '%s'" %
     928                    (name, param))
     929            else:
     930                # All good, record the keyword argument
     931                kwargs[str(param)] = value
     932                if param in unhandled_params:
     933                    # If using the keyword syntax for a positional arg, then
     934                    # consume it.
     935                    unhandled_params.remove(param)
     936        else:
     937            if kwargs:
     938                raise TemplateSyntaxError(
     939                    "'%s' received some positional argument(s) after some "
     940                    "keyword argument(s)" % name)
     941            else:
     942                # Record the positional argument
     943                args.append(parser.compile_filter(bit))
     944                try:
     945                    # Consume from the list of expected positional arguments
     946                    unhandled_params.pop(0)
     947                except IndexError:
     948                    if varargs is None:
     949                        raise TemplateSyntaxError(
     950                            "'%s' received too many positional arguments" %
     951                            name)
     952    if defaults is not None:
     953        # Consider the last n params handled, where n is the
     954        # number of defaults.
     955        unhandled_params = unhandled_params[:-len(defaults)]
     956    if unhandled_params:
     957        # Some positional arguments were not supplied
     958        raise TemplateSyntaxError(
     959            u"'%s' did not receive value(s) for the argument(s): %s" %
     960            (name, u", ".join([u"'%s'" % p for p in unhandled_params])))
     961    return args, kwargs
     962
     963def generic_tag_compiler(parser, token, params, varargs, varkw, defaults,
     964                         name, takes_context, node_class):
     965    """Returns a template.Node subclass."""
     966    bits = token.split_contents()[1:]
     967    args, kwargs = parse_bits(parser, bits, params, varargs, varkw,
     968                              defaults, takes_context, name)
     969    return node_class(takes_context, args, kwargs)
     970
     971class TagHelperNode(Node):
     972    """
     973    Base class for tag helper nodes such as SimpleNode, InclusionNode and
     974    AssignmentNode. Manages the positional and keyword arguments to be passed
     975    to the decorated function.
     976    """
     977
     978    def __init__(self, takes_context, args, kwargs):
     979        self.takes_context = takes_context
     980        self.args = args
     981        self.kwargs = kwargs
     982
     983    def get_resolved_arguments(self, context):
     984        resolved_args = [var.resolve(context) for var in self.args]
     985        if self.takes_context:
     986            resolved_args = [context] + resolved_args
     987        resolved_kwargs = dict((k, v.resolve(context))
     988                                for k, v in self.kwargs.items())
     989        return resolved_args, resolved_kwargs
    813990
    814991class Library(object):
    815992    def __init__(self):
    class Library(object):  
    817994        self.tags = {}
    818995
    819996    def tag(self, name=None, compile_function=None):
    820         if name == None and compile_function == None:
     997        if name is None and compile_function is None:
    821998            # @register.tag()
    822999            return self.tag_function
    823         elif name != None and compile_function == None:
     1000        elif name is not None and compile_function is None:
    8241001            if callable(name):
    8251002                # @register.tag
    8261003                return self.tag_function(name)
    class Library(object):  
    8291006                def dec(func):
    8301007                    return self.tag(name, func)
    8311008                return dec
    832         elif name != None and compile_function != None:
     1009        elif name is not None and compile_function is not None:
    8331010            # register.tag('somename', somefunc)
    8341011            self.tags[name] = compile_function
    8351012            return compile_function
    8361013        else:
    837             raise InvalidTemplateLibrary("Unsupported arguments to Library.tag: (%r, %r)", (name, compile_function))
     1014            raise InvalidTemplateLibrary("Unsupported arguments to "
     1015                "Library.tag: (%r, %r)", (name, compile_function))
    8381016
    839     def tag_function(self,func):
     1017    def tag_function(self, func):
    8401018        self.tags[getattr(func, "_decorated_function", func).__name__] = func
    8411019        return func
    8421020
    8431021    def filter(self, name=None, filter_func=None):
    844         if name == None and filter_func == None:
     1022        if name is None and filter_func is None:
    8451023            # @register.filter()
    8461024            return self.filter_function
    847         elif filter_func == None:
     1025        elif filter_func is None:
    8481026            if callable(name):
    8491027                # @register.filter
    8501028                return self.filter_function(name)
    class Library(object):  
    8531031                def dec(func):
    8541032                    return self.filter(name, func)
    8551033                return dec
    856         elif name != None and filter_func != None:
     1034        elif name is not None and filter_func is not None:
    8571035            # register.filter('somename', somefunc)
    8581036            self.filters[name] = filter_func
    8591037            return filter_func
    8601038        else:
    861             raise InvalidTemplateLibrary("Unsupported arguments to Library.filter: (%r, %r)", (name, filter_func))
     1039            raise InvalidTemplateLibrary("Unsupported arguments to "
     1040                "Library.filter: (%r, %r)", (name, filter_func))
    8621041
    8631042    def filter_function(self, func):
    8641043        self.filters[getattr(func, "_decorated_function", func).__name__] = func
    class Library(object):  
    8661045
    8671046    def simple_tag(self, func=None, takes_context=None, name=None):
    8681047        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'")
     1048            params, varargs, varkw, defaults = getargspec(func)
    8751049
    876             class SimpleNode(Node):
    877                 def __init__(self, vars_to_resolve):
    878                     self.vars_to_resolve = map(Variable, vars_to_resolve)
     1050            class SimpleNode(TagHelperNode):
    8791051
    8801052                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)
     1053                    resolved_args, resolved_kwargs = self.get_resolved_arguments(context)
     1054                    return func(*resolved_args, **resolved_kwargs)
     1055
     1056            function_name = (name or
     1057                getattr(func, '_decorated_function', func).__name__)
     1058            compile_func = partial(generic_tag_compiler,
     1059                params=params, varargs=varargs, varkw=varkw,
     1060                defaults=defaults, name=function_name,
     1061                takes_context=takes_context, node_class=SimpleNode)
    8901062            compile_func.__doc__ = func.__doc__
    8911063            self.tag(function_name, compile_func)
    8921064            return func
    class Library(object):  
    9021074
    9031075    def assignment_tag(self, func=None, takes_context=None, name=None):
    9041076        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'")
     1077            params, varargs, varkw, defaults = getargspec(func)
    9111078
    912             class AssignmentNode(Node):
    913                 def __init__(self, params_vars, target_var):
    914                     self.params_vars = map(Variable, params_vars)
     1079            class AssignmentNode(TagHelperNode):
     1080                def __init__(self, takes_context, args, kwargs, target_var):
     1081                    super(AssignmentNode, self).__init__(takes_context, args, kwargs)
    9151082                    self.target_var = target_var
    9161083
    9171084                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)
     1085                    resolved_args, resolved_kwargs = self.get_resolved_arguments(context)
     1086                    context[self.target_var] = func(*resolved_args, **resolved_kwargs)
    9241087                    return ''
    9251088
     1089            function_name = (name or
     1090                getattr(func, '_decorated_function', func).__name__)
     1091
    9261092            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'):
     1093                bits = token.split_contents()[1:]
     1094                if len(bits) < 2 or bits[-2] != 'as':
    9341095                    raise TemplateSyntaxError(
    9351096                        "'%s' tag takes at least 2 arguments and the "
    936                         "second last argument must be 'as'" % tag_name)
    937                 params_vars = bits[:-2]
     1097                        "second last argument must be 'as'" % function_name)
    9381098                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)
     1099                bits = bits[:-2]
     1100                args, kwargs = parse_bits(parser, bits, params,
     1101                    varargs, varkw, defaults, takes_context, function_name)
     1102                return AssignmentNode(takes_context, args, kwargs, target_var)
    9491103
    950             function_name = name or getattr(func, '_decorated_function', func).__name__
    9511104            compile_func.__doc__ = func.__doc__
    9521105            self.tag(function_name, compile_func)
    9531106            return func
    class Library(object):  
    9631116
    9641117    def inclusion_tag(self, file_name, context_class=Context, takes_context=False, name=None):
    9651118        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'")
     1119            params, varargs, varkw, defaults = getargspec(func)
    9721120
    973             class InclusionNode(Node):
    974                 def __init__(self, vars_to_resolve):
    975                     self.vars_to_resolve = map(Variable, vars_to_resolve)
     1121            class InclusionNode(TagHelperNode):
    9761122
    9771123                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)
     1124                    resolved_args, resolved_kwargs = self.get_resolved_arguments(context)
     1125                    _dict = func(*resolved_args, **resolved_kwargs)
    9851126
    9861127                    if not getattr(self, 'nodelist', False):
    9871128                        from django.template.loader import get_template, select_template
    class Library(object):  
    9921133                        else:
    9931134                            t = get_template(file_name)
    9941135                        self.nodelist = t.nodelist
    995                     new_context = context_class(dict, **{
     1136                    new_context = context_class(_dict, **{
    9961137                        'autoescape': context.autoescape,
    9971138                        'current_app': context.current_app,
    9981139                        'use_l10n': context.use_l10n,
    9991140                    })
    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.
     1141                    # Copy across the CSRF token, if present, because
     1142                    # inclusion tags are often used for forms, and we need
     1143                    # instructions for using CSRF protection to be as simple
     1144                    # as possible.
    10031145                    csrf_token = context.get('csrf_token', None)
    10041146                    if csrf_token is not None:
    10051147                        new_context['csrf_token'] = csrf_token
    10061148                    return self.nodelist.render(new_context)
    10071149
    1008             function_name = name or getattr(func, '_decorated_function', func).__name__
    1009             compile_func = partial(generic_tag_compiler, params, defaults, function_name, InclusionNode)
     1150            function_name = (name or
     1151                getattr(func, '_decorated_function', func).__name__)
     1152            compile_func = partial(generic_tag_compiler,
     1153                params=params, varargs=varargs, varkw=varkw,
     1154                defaults=defaults, name=function_name,
     1155                takes_context=takes_context, node_class=InclusionNode)
    10101156            compile_func.__doc__ = func.__doc__
    10111157            self.tag(function_name, compile_func)
    10121158            return func
    def import_library(taglib_module):  
    10181164    Verifies that the library contains a 'register' attribute, and
    10191165    returns that attribute as the representation of the library
    10201166    """
    1021     app_path, taglib = taglib_module.rsplit('.',1)
     1167    app_path, taglib = taglib_module.rsplit('.', 1)
    10221168    app_module = import_module(app_path)
    10231169    try:
    10241170        mod = import_module(taglib_module)
    10251171    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.
     1172        # If the ImportError is because the taglib submodule does not exist,
     1173        # that's not an error that should be raised. If the submodule exists
     1174        # and raised an ImportError on the attempt to load it, that we want
     1175        # to raise.
    10291176        if not module_has_submodule(app_module, taglib):
    10301177            return None
    10311178        else:
    1032             raise InvalidTemplateLibrary("ImportError raised loading %s: %s" % (taglib_module, e))
     1179            raise InvalidTemplateLibrary("ImportError raised loading %s: %s" %
     1180                                         (taglib_module, e))
    10331181    try:
    10341182        return mod.register
    10351183    except AttributeError:
    1036         raise InvalidTemplateLibrary("Template library %s does not have a variable named 'register'" % taglib_module)
     1184        raise InvalidTemplateLibrary("Template library %s does not have "
     1185                                     "a variable named 'register'" %
     1186                                     taglib_module)
    10371187
    10381188templatetags_modules = []
    10391189
    def get_templatetags_modules():  
    10451195    global templatetags_modules
    10461196    if not templatetags_modules:
    10471197        _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.
     1198        # Populate list once per process. Mutate the local list first, and
     1199        # then assign it to the global name to ensure there are no cases where
     1200        # two threads try to populate it simultaneously.
    10511201        for app_module in ['django'] + list(settings.INSTALLED_APPS):
    10521202            try:
    10531203                templatetag_module = '%s.templatetags' % app_module
    def get_library(library_name):  
    10621212    """
    10631213    Load the template library module with the given name.
    10641214
    1065     If library is not already loaded loop over all templatetags modules to locate it.
     1215    If library is not already loaded loop over all templatetags modules
     1216    to locate it.
    10661217
    10671218    {% load somelib %} and {% load someotherlib %} loops twice.
    10681219
    1069     Subsequent loads eg. {% load somelib %} in the same process will grab the cached
    1070     module from libraries.
     1220    Subsequent loads eg. {% load somelib %} in the same process will grab
     1221    the cached module from libraries.
    10711222    """
    10721223    lib = libraries.get(library_name, None)
    10731224    if not lib:
    def get_library(library_name):  
    10811232                libraries[library_name] = lib
    10821233                break
    10831234        if not lib:
    1084             raise InvalidTemplateLibrary("Template library %s not found, tried %s" % (library_name, ','.join(tried_modules)))
     1235            raise InvalidTemplateLibrary("Template library %s not found, "
     1236                                         "tried %s" %
     1237                                         (library_name,
     1238                                          ','.join(tried_modules)))
    10851239    return lib
    10861240
     1241
    10871242def add_to_builtins(module):
    10881243    builtins.append(import_library(module))
    10891244
     1245
    10901246add_to_builtins('django.template.defaulttags')
    10911247add_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/releases/1.4.txt

    diff --git a/docs/releases/1.4.txt b/docs/releases/1.4.txt
    index 9cf268a..ad241d9 100644
    a b Django 1.4 also includes several smaller improvements worth noting:  
    291291
    292292* Customizable names for :meth:`~django.template.Library.simple_tag`.
    293293
     294* Added ``*args`` and ``*kwargs`` support to
     295  :meth:`~django.template.Library.simple_tag`,
     296  :meth:`~django.template.Library.inclusion_tag` and the newly introduced
     297  :meth:`~django.template.Library.assignment_tag`.
     298
    294299* In the documentation, a helpful :doc:`security overview </topics/security>`
    295300  page.
    296301
  • 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..0e07d53 100644
    a b  
    11from django import template
    22from django.template.defaultfilters import stringfilter
    33from django.template.loader import get_template
     4import operator
    45
    56register = template.Library()
    67
    def params_and_context(context, arg):  
    4041    return "params_and_context - Expected result (context value: %s): %s" % (context['value'], arg)
    4142params_and_context.anything = "Expected params_and_context __dict__"
    4243
     44@register.simple_tag
     45def simple_two_params(one, two):
     46    """Expected simple_two_params __doc__"""
     47    return "simple_two_params - Expected result: %s, %s" % (one, two)
     48simple_two_params.anything = "Expected simple_two_params __dict__"
     49
     50@register.simple_tag
     51def simple_one_default(one, two='hi'):
     52    """Expected simple_one_default __doc__"""
     53    return "simple_one_default - Expected result: %s, %s" % (one, two)
     54simple_one_default.anything = "Expected simple_one_default __dict__"
     55
     56@register.simple_tag
     57def simple_unlimited_args(one, two='hi', *args):
     58    """Expected simple_unlimited_args __doc__"""
     59    return "simple_unlimited_args - Expected result: %s" % (', '.join([unicode(arg) for arg in [one, two] + list(args)]))
     60simple_unlimited_args.anything = "Expected simple_unlimited_args __dict__"
     61
     62@register.simple_tag
     63def simple_only_unlimited_args(*args):
     64    """Expected simple_only_unlimited_args __doc__"""
     65    return "simple_only_unlimited_args - Expected result: %s" % ', '.join([unicode(arg) for arg in args])
     66simple_only_unlimited_args.anything = "Expected simple_only_unlimited_args __dict__"
     67
     68@register.simple_tag
     69def simple_unlimited_args_kwargs(one, two='hi', *args, **kwargs):
     70    """Expected simple_unlimited_args_kwargs __doc__"""
     71    # Sort the dictionary by key to guarantee the order for testing.
     72    sorted_kwarg = sorted(kwargs.iteritems(), key=operator.itemgetter(0))
     73    return "simple_unlimited_args_kwargs - Expected result: %s / %s" % (
     74        ', '.join([unicode(arg) for arg in [one, two] + list(args)]),
     75        ', '.join(['%s=%s' % (k, v) for (k, v) in sorted_kwarg])
     76        )
     77simple_unlimited_args_kwargs.anything = "Expected simple_unlimited_args_kwargs __dict__"
     78
     79@register.simple_tag(takes_context=True)
     80def simple_tag_without_context_parameter(arg):
     81    """Expected simple_tag_without_context_parameter __doc__"""
     82    return "Expected result"
     83simple_tag_without_context_parameter.anything = "Expected simple_tag_without_context_parameter __dict__"
     84
     85@register.simple_tag(takes_context=True)
     86def current_app(context):
     87    return "%s" % context.current_app
     88
     89@register.simple_tag(takes_context=True)
     90def use_l10n(context):
     91    return "%s" % context.use_l10n
     92
     93@register.simple_tag(name='minustwo')
     94def minustwo_overridden_name(value):
     95    return value - 2
     96
     97register.simple_tag(lambda x: x - 1, name='minusone')
     98
    4399@register.inclusion_tag('inclusion.html')
    44100def inclusion_no_params():
    45101    """Expected inclusion_no_params __doc__"""
    def inclusion_params_and_context_from_template(context, arg):  
    100156    return {"result" : "inclusion_params_and_context_from_template - Expected result (context value: %s): %s" % (context['value'], arg)}
    101157inclusion_params_and_context_from_template.anything = "Expected inclusion_params_and_context_from_template __dict__"
    102158
    103 @register.simple_tag(takes_context=True)
    104 def current_app(context):
    105     return "%s" % context.current_app
     159@register.inclusion_tag('inclusion.html')
     160def inclusion_two_params(one, two):
     161    """Expected inclusion_two_params __doc__"""
     162    return {"result": "inclusion_two_params - Expected result: %s, %s" % (one, two)}
     163inclusion_two_params.anything = "Expected inclusion_two_params __dict__"
     164
     165@register.inclusion_tag(get_template('inclusion.html'))
     166def inclusion_two_params_from_template(one, two):
     167    """Expected inclusion_two_params_from_template __doc__"""
     168    return {"result": "inclusion_two_params_from_template - Expected result: %s, %s" % (one, two)}
     169inclusion_two_params_from_template.anything = "Expected inclusion_two_params_from_template __dict__"
     170
     171@register.inclusion_tag('inclusion.html')
     172def inclusion_one_default(one, two='hi'):
     173    """Expected inclusion_one_default __doc__"""
     174    return {"result": "inclusion_one_default - Expected result: %s, %s" % (one, two)}
     175inclusion_one_default.anything = "Expected inclusion_one_default __dict__"
     176
     177@register.inclusion_tag(get_template('inclusion.html'))
     178def inclusion_one_default_from_template(one, two='hi'):
     179    """Expected inclusion_one_default_from_template __doc__"""
     180    return {"result": "inclusion_one_default_from_template - Expected result: %s, %s" % (one, two)}
     181inclusion_one_default_from_template.anything = "Expected inclusion_one_default_from_template __dict__"
     182
     183@register.inclusion_tag('inclusion.html')
     184def inclusion_unlimited_args(one, two='hi', *args):
     185    """Expected inclusion_unlimited_args __doc__"""
     186    return {"result": "inclusion_unlimited_args - Expected result: %s" % (', '.join([unicode(arg) for arg in [one, two] + list(args)]))}
     187inclusion_unlimited_args.anything = "Expected inclusion_unlimited_args __dict__"
     188
     189@register.inclusion_tag(get_template('inclusion.html'))
     190def inclusion_unlimited_args_from_template(one, two='hi', *args):
     191    """Expected inclusion_unlimited_args_from_template __doc__"""
     192    return {"result": "inclusion_unlimited_args_from_template - Expected result: %s" % (', '.join([unicode(arg) for arg in [one, two] + list(args)]))}
     193inclusion_unlimited_args_from_template.anything = "Expected inclusion_unlimited_args_from_template __dict__"
     194
     195@register.inclusion_tag('inclusion.html')
     196def inclusion_only_unlimited_args(*args):
     197    """Expected inclusion_only_unlimited_args __doc__"""
     198    return {"result": "inclusion_only_unlimited_args - Expected result: %s" % (', '.join([unicode(arg) for arg in args]))}
     199inclusion_only_unlimited_args.anything = "Expected inclusion_only_unlimited_args __dict__"
     200
     201@register.inclusion_tag(get_template('inclusion.html'))
     202def inclusion_only_unlimited_args_from_template(*args):
     203    """Expected inclusion_only_unlimited_args_from_template __doc__"""
     204    return {"result": "inclusion_only_unlimited_args_from_template - Expected result: %s" % (', '.join([unicode(arg) for arg in args]))}
     205inclusion_only_unlimited_args_from_template.anything = "Expected inclusion_only_unlimited_args_from_template __dict__"
    106206
    107207@register.inclusion_tag('test_incl_tag_current_app.html', takes_context=True)
    108208def inclusion_tag_current_app(context):
     209    """Expected inclusion_tag_current_app __doc__"""
    109210    return {}
    110 
    111 @register.simple_tag(takes_context=True)
    112 def use_l10n(context):
    113     return "%s" % context.use_l10n
     211inclusion_tag_current_app.anything = "Expected inclusion_tag_current_app __dict__"
    114212
    115213@register.inclusion_tag('test_incl_tag_use_l10n.html', takes_context=True)
    116214def inclusion_tag_use_l10n(context):
     215    """Expected inclusion_tag_use_l10n __doc__"""
    117216    return {}
     217inclusion_tag_use_l10n.anything = "Expected inclusion_tag_use_l10n __dict__"
     218
     219@register.inclusion_tag('inclusion.html')
     220def inclusion_unlimited_args_kwargs(one, two='hi', *args, **kwargs):
     221    """Expected inclusion_unlimited_args_kwargs __doc__"""
     222    # Sort the dictionary by key to guarantee the order for testing.
     223    sorted_kwarg = sorted(kwargs.iteritems(), key=operator.itemgetter(0))
     224    return {"result": "inclusion_unlimited_args_kwargs - Expected result: %s / %s" % (
     225        ', '.join([unicode(arg) for arg in [one, two] + list(args)]),
     226        ', '.join(['%s=%s' % (k, v) for (k, v) in sorted_kwarg])
     227        )}
     228inclusion_unlimited_args_kwargs.anything = "Expected inclusion_unlimited_args_kwargs __dict__"
     229
     230@register.inclusion_tag('inclusion.html', takes_context=True)
     231def inclusion_tag_without_context_parameter(arg):
     232    """Expected inclusion_tag_without_context_parameter __doc__"""
     233    return {}
     234inclusion_tag_without_context_parameter.anything = "Expected inclusion_tag_without_context_parameter __dict__"
    118235
    119236@register.assignment_tag
    120237def assignment_no_params():
    def assignment_params_and_context(context, arg):  
    146263    return "assignment_params_and_context - Expected result (context value: %s): %s" % (context['value'], arg)
    147264assignment_params_and_context.anything = "Expected assignment_params_and_context __dict__"
    148265
    149 register.simple_tag(lambda x: x - 1, name='minusone')
     266@register.assignment_tag
     267def assignment_two_params(one, two):
     268    """Expected assignment_two_params __doc__"""
     269    return "assignment_two_params - Expected result: %s, %s" % (one, two)
     270assignment_two_params.anything = "Expected assignment_two_params __dict__"
    150271
    151 @register.simple_tag(name='minustwo')
    152 def minustwo_overridden_name(value):
    153     return value - 2
     272@register.assignment_tag
     273def assignment_one_default(one, two='hi'):
     274    """Expected assignment_one_default __doc__"""
     275    return "assignment_one_default - Expected result: %s, %s" % (one, two)
     276assignment_one_default.anything = "Expected assignment_one_default __dict__"
     277
     278@register.assignment_tag
     279def assignment_unlimited_args(one, two='hi', *args):
     280    """Expected assignment_unlimited_args __doc__"""
     281    return "assignment_unlimited_args - Expected result: %s" % (', '.join([unicode(arg) for arg in [one, two] + list(args)]))
     282assignment_unlimited_args.anything = "Expected assignment_unlimited_args __dict__"
     283
     284@register.assignment_tag
     285def assignment_only_unlimited_args(*args):
     286    """Expected assignment_only_unlimited_args __doc__"""
     287    return "assignment_only_unlimited_args - Expected result: %s" % ', '.join([unicode(arg) for arg in args])
     288assignment_only_unlimited_args.anything = "Expected assignment_only_unlimited_args __dict__"
     289
     290@register.assignment_tag
     291def assignment_unlimited_args_kwargs(one, two='hi', *args, **kwargs):
     292    """Expected assignment_unlimited_args_kwargs __doc__"""
     293    # Sort the dictionary by key to guarantee the order for testing.
     294    sorted_kwarg = sorted(kwargs.iteritems(), key=operator.itemgetter(0))
     295    return "assignment_unlimited_args_kwargs - Expected result: %s / %s" % (
     296        ', '.join([unicode(arg) for arg in [one, two] + list(args)]),
     297        ', '.join(['%s=%s' % (k, v) for (k, v) in sorted_kwarg])
     298        )
     299assignment_unlimited_args_kwargs.anything = "Expected assignment_unlimited_args_kwargs __dict__"
     300
     301@register.assignment_tag(takes_context=True)
     302def assignment_tag_without_context_parameter(arg):
     303    """Expected assignment_tag_without_context_parameter __doc__"""
     304    return "Expected result"
     305assignment_tag_without_context_parameter.anything = "Expected assignment_tag_without_context_parameter __dict__"
     306 No newline at end of file
Back to Top