Ticket #13956: 13956.ttag_helpers_args_kwargs_support.diff

File 13956.ttag_helpers_args_kwargs_support.diff, 50.0 KB (added by Julien Phalip, 13 years ago)
  • django/template/base.py

    diff --git a/django/template/base.py b/django/template/base.py
    index c94eeb5..1accc49 100644
    a b class VariableNode(Node):  
    797797            return ''
    798798        return _render_value_in_context(output, context)
    799799
    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)
     800# Regex for token keyword arguments
     801kwarg_re = re.compile(r"(?:(\w+)=)?(.+)")
     802
     803def token_kwargs(bits, parser, support_legacy=False):
     804    """
     805    A utility method for parsing token keyword arguments.
     806
     807    :param bits: A list containing remainder of the token (split by spaces)
     808        that is to be checked for arguments. Valid arguments will be removed
     809        from this list.
     810
     811    :param support_legacy: If set to true ``True``, the legacy format
     812        ``1 as foo`` will be accepted. Otherwise, only the standard ``foo=1``
     813        format is allowed.
     814
     815    :returns: A dictionary of the arguments retrieved from the ``bits`` token
     816        list.
     817
     818    There is no requirement for all remaining token ``bits`` to be keyword
     819    arguments, so the dictionary will be returned as soon as an invalid
     820    argument format is reached.
     821    """
     822    if not bits:
     823        return {}
     824    match = kwarg_re.match(bits[0])
     825    kwarg_format = match and match.group(1)
     826    if not kwarg_format:
     827        if not support_legacy:
     828            return {}
     829        if len(bits) < 3 or bits[1] != 'as':
     830            return {}
     831
     832    kwargs = {}
     833    while bits:
     834        if kwarg_format:
     835            match = kwarg_re.match(bits[0])
     836            if not match or not match.group(1):
     837                return kwargs
     838            key, value = match.groups()
     839            del bits[:1]
     840        else:
     841            if len(bits) < 3 or bits[1] != 'as':
     842                return kwargs
     843            key, value = bits[2], bits[0]
     844            del bits[:3]
     845        kwargs[key] = parser.compile_filter(value)
     846        if bits and not kwarg_format:
     847            if bits[0] != 'and':
     848                return kwargs
     849            del bits[:1]
     850    return kwargs
     851
     852def parse_bits(parser, bits, params, varargs, varkw, defaults,
     853               takes_context, name):
     854    """ Parses bits for template tag helpers (simple_tag, include_tag and
     855        assignment_tag), in particular by detecting syntax errors and by
     856        extracting positional and keyword arguments.
     857    """
     858    if takes_context:
     859        if params[0] == 'context':
     860            params = params[1:]
     861        else:
     862            raise TemplateSyntaxError(
     863                "'%s' is decorated with takes_context=True so it must "
     864                "have a first argument of 'context'" % name)
     865    args = []
     866    kwargs = {}
     867    unhandled_params = list(params)
     868    for bit in bits:
     869        # First we try to extract a potential kwarg from the bit
     870        kwarg = token_kwargs([bit], parser)
     871        if kwarg:
     872            # The kwarg was successfully extracted
     873            param, value = kwarg.items()[0]
     874            if param not in params and varkw is None:
     875                # An unexpected keyword argument was supplied
     876                raise TemplateSyntaxError(
     877                    "'%s' received unexpected keyword argument '%s'" %
     878                    (name, param))
     879            elif param in kwargs:
     880                # The keyword argument has already been supplied once
     881                raise TemplateSyntaxError(
     882                    "'%s' received multiple values for keyword argument '%s'" %
     883                    (name, param))
     884            else:
     885                # All good, record the keyword argument
     886                kwargs[str(param)] = value
     887                if param in unhandled_params:
     888                    # If using the keyword syntax for a positional arg, then
     889                    # consume it.
     890                    unhandled_params.remove(param)
    809891        else:
    810             message = "%s takes between %s and %s arguments" % (name, bmin, bmax)
    811         raise TemplateSyntaxError(message)
    812     return node_class(bits)
     892            if kwargs:
     893                raise TemplateSyntaxError(
     894                    "'%s' received some positional argument(s) after some "
     895                    "keyword argument(s)" % name)
     896            else:
     897                # Record the positional argument
     898                args.append(parser.compile_filter(bit))
     899                try:
     900                    # Consume from the list of expected positional arguments
     901                    unhandled_params.pop(0)
     902                except IndexError:
     903                    if varargs is None:
     904                        raise TemplateSyntaxError(
     905                            "'%s' received too many positional arguments" %
     906                            name)
     907    if defaults is not None:
     908        # Consider the last n params handled, where n is the
     909        # number of defaults.
     910        unhandled_params = unhandled_params[:-len(defaults)]
     911    if unhandled_params:
     912        # Some positional arguments were not supplied
     913        raise TemplateSyntaxError(
     914            u"'%s' did not receive value(s) for the argument(s): %s" % (
     915                name,
     916                u", ".join([u"'%s'" % p for p in unhandled_params])
     917            ))
     918    return args, kwargs
     919
     920def generic_tag_compiler(parser, token, params, varargs, varkw, defaults,
     921                         name, takes_context, node_class):
     922    """Returns a template.Node subclass."""
     923    bits = token.split_contents()[1:]
     924    args, kwargs = parse_bits(parser, bits, params, varargs, varkw,
     925                              defaults, takes_context, name)
     926    return node_class(takes_context, args, kwargs)
     927
     928class TagHelperNode(Node):
     929    """
     930    Base class for tag helper nodes such as SimpleNode, InclusionNode and
     931    AssignmentNode. Manages the positional and keyword arguments to be passed
     932    to the decorated function.
     933    """
     934
     935    def __init__(self, takes_context, args, kwargs):
     936        self.takes_context = takes_context
     937        self.args = args
     938        self.kwargs = kwargs
     939
     940    def get_resolved_arguments(self, context):
     941        resolved_args = [var.resolve(context) for var in self.args]
     942        if self.takes_context:
     943            resolved_args = [context] + resolved_args
     944        resolved_kwargs = dict(
     945                [(k, v.resolve(context)) for k, v in self.kwargs.items()])
     946        return resolved_args, resolved_kwargs
    813947
    814948class Library(object):
    815949    def __init__(self):
    class Library(object):  
    817951        self.tags = {}
    818952
    819953    def tag(self, name=None, compile_function=None):
    820         if name == None and compile_function == None:
     954        if name is None and compile_function is None:
    821955            # @register.tag()
    822956            return self.tag_function
    823         elif name != None and compile_function == None:
     957        elif name is not None and compile_function is None:
    824958            if callable(name):
    825959                # @register.tag
    826960                return self.tag_function(name)
    class Library(object):  
    829963                def dec(func):
    830964                    return self.tag(name, func)
    831965                return dec
    832         elif name != None and compile_function != None:
     966        elif name is not None and compile_function is not None:
    833967            # register.tag('somename', somefunc)
    834968            self.tags[name] = compile_function
    835969            return compile_function
    class Library(object):  
    841975        return func
    842976
    843977    def filter(self, name=None, filter_func=None):
    844         if name == None and filter_func == None:
     978        if name is None and filter_func is None:
    845979            # @register.filter()
    846980            return self.filter_function
    847         elif filter_func == None:
     981        elif filter_func is None:
    848982            if callable(name):
    849983                # @register.filter
    850984                return self.filter_function(name)
    class Library(object):  
    853987                def dec(func):
    854988                    return self.filter(name, func)
    855989                return dec
    856         elif name != None and filter_func != None:
     990        elif name is not None and filter_func is not None:
    857991            # register.filter('somename', somefunc)
    858992            self.filters[name] = filter_func
    859993            return filter_func
    class Library(object):  
    8661000
    8671001    def simple_tag(self, func=None, takes_context=None, name=None):
    8681002        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'")
     1003            params, varargs, varkw, defaults = getargspec(func)
    8751004
    876             class SimpleNode(Node):
    877                 def __init__(self, vars_to_resolve):
    878                     self.vars_to_resolve = map(Variable, vars_to_resolve)
     1005            class SimpleNode(TagHelperNode):
    8791006
    8801007                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)
     1008                    resolved_args, resolved_kwargs = self.get_resolved_arguments(context)
     1009                    return func(*resolved_args, **resolved_kwargs)
    8871010
    8881011            function_name = name or getattr(func, '_decorated_function', func).__name__
    889             compile_func = partial(generic_tag_compiler, params, defaults, function_name, SimpleNode)
     1012            compile_func = partial(generic_tag_compiler, params=params, varargs=varargs, varkw=varkw, defaults=defaults, name=function_name, takes_context=takes_context, node_class=SimpleNode)
    8901013            compile_func.__doc__ = func.__doc__
    8911014            self.tag(function_name, compile_func)
    8921015            return func
    class Library(object):  
    9021025
    9031026    def assignment_tag(self, func=None, takes_context=None, name=None):
    9041027        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'")
     1028            params, varargs, varkw, defaults = getargspec(func)
    9111029
    912             class AssignmentNode(Node):
    913                 def __init__(self, params_vars, target_var):
    914                     self.params_vars = map(Variable, params_vars)
     1030            class AssignmentNode(TagHelperNode):
     1031                def __init__(self, takes_context, args, kwargs, target_var):
     1032                    super(AssignmentNode, self).__init__(takes_context, args, kwargs)
    9151033                    self.target_var = target_var
    9161034
    9171035                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)
     1036                    resolved_args, resolved_kwargs = self.get_resolved_arguments(context)
     1037                    context[self.target_var] = func(*resolved_args, **resolved_kwargs)
    9241038                    return ''
    9251039
     1040            function_name = name or getattr(func, '_decorated_function', func).__name__
     1041
    9261042            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'):
     1043                bits = token.split_contents()[1:]
     1044                if len(bits) < 2 or bits[-2] != 'as':
    9341045                    raise TemplateSyntaxError(
    9351046                        "'%s' tag takes at least 2 arguments and the "
    936                         "second last argument must be 'as'" % tag_name)
    937                 params_vars = bits[:-2]
     1047                        "second last argument must be 'as'" % function_name)
    9381048                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)
     1049                bits = bits[:-2]
     1050                args, kwargs = parse_bits(parser, bits, params, varargs, varkw, defaults, takes_context, function_name)
     1051                return AssignmentNode(takes_context, args, kwargs, target_var)
    9491052
    950             function_name = name or getattr(func, '_decorated_function', func).__name__
    9511053            compile_func.__doc__ = func.__doc__
    9521054            self.tag(function_name, compile_func)
    9531055            return func
    class Library(object):  
    9631065
    9641066    def inclusion_tag(self, file_name, context_class=Context, takes_context=False, name=None):
    9651067        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'")
     1068            params, varargs, varkw, defaults = getargspec(func)
    9721069
    973             class InclusionNode(Node):
    974                 def __init__(self, vars_to_resolve):
    975                     self.vars_to_resolve = map(Variable, vars_to_resolve)
     1070            class InclusionNode(TagHelperNode):
    9761071
    9771072                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)
     1073                    resolved_args, resolved_kwargs = self.get_resolved_arguments(context)
     1074                    _dict = func(*resolved_args, **resolved_kwargs)
    9851075
    9861076                    if not getattr(self, 'nodelist', False):
    9871077                        from django.template.loader import get_template, select_template
    class Library(object):  
    9921082                        else:
    9931083                            t = get_template(file_name)
    9941084                        self.nodelist = t.nodelist
    995                     new_context = context_class(dict, **{
     1085                    new_context = context_class(_dict, **{
    9961086                        'autoescape': context.autoescape,
    9971087                        'current_app': context.current_app,
    9981088                        'use_l10n': context.use_l10n,
    class Library(object):  
    10061096                    return self.nodelist.render(new_context)
    10071097
    10081098            function_name = name or getattr(func, '_decorated_function', func).__name__
    1009             compile_func = partial(generic_tag_compiler, params, defaults, function_name, InclusionNode)
     1099            compile_func = partial(generic_tag_compiler, params=params, varargs=varargs, varkw=varkw, defaults=defaults, name=function_name, takes_context=takes_context, node_class=InclusionNode)
    10101100            compile_func.__doc__ = func.__doc__
    10111101            self.tag(function_name, compile_func)
    10121102            return func
  • django/template/defaulttags.py

    diff --git a/django/template/defaulttags.py b/django/template/defaulttags.py
    index 06838cf..f2bf133 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 90c5e24..54efc42 100644
    a b Django 1.4 also includes several smaller improvements worth noting:  
    275275
    276276* Customizable names for :meth:`~django.template.Library.simple_tag`.
    277277
     278* Added ``*args`` and ``*kwargs`` support to
     279  :meth:`~django.template.Library.simple_tag`,
     280  :meth:`~django.template.Library.inclusion_tag` and the newly introduced
     281  :meth:`~django.template.Library.assignment_tag`.
     282
    278283* In the documentation, a helpful :doc:`security overview </topics/security>`
    279284  page.
    280285
  • 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