diff --git a/django/template/base.py b/django/template/base.py
index c94eeb5..1accc49 100644
--- a/django/template/base.py
+++ b/django/template/base.py
@@ -797,19 +797,153 @@ class VariableNode(Node):
             return ''
         return _render_value_in_context(output, context)
 
-def generic_tag_compiler(params, defaults, name, node_class, parser, token):
-    "Returns a template.Node subclass."
-    bits = token.split_contents()[1:]
-    bmax = len(params)
-    def_len = defaults and len(defaults) or 0
-    bmin = bmax - def_len
-    if(len(bits) < bmin or len(bits) > bmax):
-        if bmin == bmax:
-            message = "%s takes %s arguments" % (name, bmin)
+# Regex for token keyword arguments
+kwarg_re = re.compile(r"(?:(\w+)=)?(.+)")
+
+def token_kwargs(bits, parser, support_legacy=False):
+    """
+    A utility method for parsing token keyword arguments.
+
+    :param bits: A list containing remainder of the token (split by spaces)
+        that is to be checked for arguments. Valid arguments will be removed
+        from this list.
+
+    :param support_legacy: If set to true ``True``, the legacy format
+        ``1 as foo`` will be accepted. Otherwise, only the standard ``foo=1``
+        format is allowed.
+
+    :returns: A dictionary of the arguments retrieved from the ``bits`` token
+        list.
+
+    There is no requirement for all remaining token ``bits`` to be keyword
+    arguments, so the dictionary will be returned as soon as an invalid
+    argument format is reached.
+    """
+    if not bits:
+        return {}
+    match = kwarg_re.match(bits[0])
+    kwarg_format = match and match.group(1)
+    if not kwarg_format:
+        if not support_legacy:
+            return {}
+        if len(bits) < 3 or bits[1] != 'as':
+            return {}
+
+    kwargs = {}
+    while bits:
+        if kwarg_format:
+            match = kwarg_re.match(bits[0])
+            if not match or not match.group(1):
+                return kwargs
+            key, value = match.groups()
+            del bits[:1]
+        else:
+            if len(bits) < 3 or bits[1] != 'as':
+                return kwargs
+            key, value = bits[2], bits[0]
+            del bits[:3]
+        kwargs[key] = parser.compile_filter(value)
+        if bits and not kwarg_format:
+            if bits[0] != 'and':
+                return kwargs
+            del bits[:1]
+    return kwargs
+
+def parse_bits(parser, bits, params, varargs, varkw, defaults,
+               takes_context, name):
+    """ Parses bits for template tag helpers (simple_tag, include_tag and
+        assignment_tag), in particular by detecting syntax errors and by
+        extracting positional and keyword arguments.
+    """
+    if takes_context:
+        if params[0] == 'context':
+            params = params[1:]
+        else:
+            raise TemplateSyntaxError(
+                "'%s' is decorated with takes_context=True so it must "
+                "have a first argument of 'context'" % name)
+    args = []
+    kwargs = {}
+    unhandled_params = list(params)
+    for bit in bits:
+        # First we try to extract a potential kwarg from the bit
+        kwarg = token_kwargs([bit], parser)
+        if kwarg:
+            # The kwarg was successfully extracted
+            param, value = kwarg.items()[0]
+            if param not in params and varkw is None:
+                # An unexpected keyword argument was supplied
+                raise TemplateSyntaxError(
+                    "'%s' received unexpected keyword argument '%s'" %
+                    (name, param))
+            elif param in kwargs:
+                # The keyword argument has already been supplied once
+                raise TemplateSyntaxError(
+                    "'%s' received multiple values for keyword argument '%s'" %
+                    (name, param))
+            else:
+                # All good, record the keyword argument
+                kwargs[str(param)] = value
+                if param in unhandled_params:
+                    # If using the keyword syntax for a positional arg, then
+                    # consume it.
+                    unhandled_params.remove(param)
         else:
-            message = "%s takes between %s and %s arguments" % (name, bmin, bmax)
-        raise TemplateSyntaxError(message)
-    return node_class(bits)
+            if kwargs:
+                raise TemplateSyntaxError(
+                    "'%s' received some positional argument(s) after some "
+                    "keyword argument(s)" % name)
+            else:
+                # Record the positional argument
+                args.append(parser.compile_filter(bit))
+                try:
+                    # Consume from the list of expected positional arguments
+                    unhandled_params.pop(0)
+                except IndexError:
+                    if varargs is None:
+                        raise TemplateSyntaxError(
+                            "'%s' received too many positional arguments" %
+                            name)
+    if defaults is not None:
+        # Consider the last n params handled, where n is the
+        # number of defaults.
+        unhandled_params = unhandled_params[:-len(defaults)]
+    if unhandled_params:
+        # Some positional arguments were not supplied
+        raise TemplateSyntaxError(
+            u"'%s' did not receive value(s) for the argument(s): %s" % (
+                name,
+                u", ".join([u"'%s'" % p for p in unhandled_params])
+            ))
+    return args, kwargs
+
+def generic_tag_compiler(parser, token, params, varargs, varkw, defaults,
+                         name, takes_context, node_class):
+    """Returns a template.Node subclass."""
+    bits = token.split_contents()[1:]
+    args, kwargs = parse_bits(parser, bits, params, varargs, varkw,
+                              defaults, takes_context, name)
+    return node_class(takes_context, args, kwargs)
+
+class TagHelperNode(Node):
+    """
+    Base class for tag helper nodes such as SimpleNode, InclusionNode and
+    AssignmentNode. Manages the positional and keyword arguments to be passed
+    to the decorated function.
+    """
+
+    def __init__(self, takes_context, args, kwargs):
+        self.takes_context = takes_context
+        self.args = args
+        self.kwargs = kwargs
+
+    def get_resolved_arguments(self, context):
+        resolved_args = [var.resolve(context) for var in self.args]
+        if self.takes_context:
+            resolved_args = [context] + resolved_args
+        resolved_kwargs = dict(
+                [(k, v.resolve(context)) for k, v in self.kwargs.items()])
+        return resolved_args, resolved_kwargs
 
 class Library(object):
     def __init__(self):
@@ -817,10 +951,10 @@ class Library(object):
         self.tags = {}
 
     def tag(self, name=None, compile_function=None):
-        if name == None and compile_function == None:
+        if name is None and compile_function is None:
             # @register.tag()
             return self.tag_function
-        elif name != None and compile_function == None:
+        elif name is not None and compile_function is None:
             if callable(name):
                 # @register.tag
                 return self.tag_function(name)
@@ -829,7 +963,7 @@ class Library(object):
                 def dec(func):
                     return self.tag(name, func)
                 return dec
-        elif name != None and compile_function != None:
+        elif name is not None and compile_function is not None:
             # register.tag('somename', somefunc)
             self.tags[name] = compile_function
             return compile_function
@@ -841,10 +975,10 @@ class Library(object):
         return func
 
     def filter(self, name=None, filter_func=None):
-        if name == None and filter_func == None:
+        if name is None and filter_func is None:
             # @register.filter()
             return self.filter_function
-        elif filter_func == None:
+        elif filter_func is None:
             if callable(name):
                 # @register.filter
                 return self.filter_function(name)
@@ -853,7 +987,7 @@ class Library(object):
                 def dec(func):
                     return self.filter(name, func)
                 return dec
-        elif name != None and filter_func != None:
+        elif name is not None and filter_func is not None:
             # register.filter('somename', somefunc)
             self.filters[name] = filter_func
             return filter_func
@@ -866,27 +1000,16 @@ class Library(object):
 
     def simple_tag(self, func=None, takes_context=None, name=None):
         def dec(func):
-            params, xx, xxx, defaults = getargspec(func)
-            if takes_context:
-                if params[0] == 'context':
-                    params = params[1:]
-                else:
-                    raise TemplateSyntaxError("Any tag function decorated with takes_context=True must have a first argument of 'context'")
+            params, varargs, varkw, defaults = getargspec(func)
 
-            class SimpleNode(Node):
-                def __init__(self, vars_to_resolve):
-                    self.vars_to_resolve = map(Variable, vars_to_resolve)
+            class SimpleNode(TagHelperNode):
 
                 def render(self, context):
-                    resolved_vars = [var.resolve(context) for var in self.vars_to_resolve]
-                    if takes_context:
-                        func_args = [context] + resolved_vars
-                    else:
-                        func_args = resolved_vars
-                    return func(*func_args)
+                    resolved_args, resolved_kwargs = self.get_resolved_arguments(context)
+                    return func(*resolved_args, **resolved_kwargs)
 
             function_name = name or getattr(func, '_decorated_function', func).__name__
-            compile_func = partial(generic_tag_compiler, params, defaults, function_name, SimpleNode)
+            compile_func = partial(generic_tag_compiler, params=params, varargs=varargs, varkw=varkw, defaults=defaults, name=function_name, takes_context=takes_context, node_class=SimpleNode)
             compile_func.__doc__ = func.__doc__
             self.tag(function_name, compile_func)
             return func
@@ -902,52 +1025,31 @@ class Library(object):
 
     def assignment_tag(self, func=None, takes_context=None, name=None):
         def dec(func):
-            params, xx, xxx, defaults = getargspec(func)
-            if takes_context:
-                if params[0] == 'context':
-                    params = params[1:]
-                else:
-                    raise TemplateSyntaxError("Any tag function decorated with takes_context=True must have a first argument of 'context'")
+            params, varargs, varkw, defaults = getargspec(func)
 
-            class AssignmentNode(Node):
-                def __init__(self, params_vars, target_var):
-                    self.params_vars = map(Variable, params_vars)
+            class AssignmentNode(TagHelperNode):
+                def __init__(self, takes_context, args, kwargs, target_var):
+                    super(AssignmentNode, self).__init__(takes_context, args, kwargs)
                     self.target_var = target_var
 
                 def render(self, context):
-                    resolved_vars = [var.resolve(context) for var in self.params_vars]
-                    if takes_context:
-                        func_args = [context] + resolved_vars
-                    else:
-                        func_args = resolved_vars
-                    context[self.target_var] = func(*func_args)
+                    resolved_args, resolved_kwargs = self.get_resolved_arguments(context)
+                    context[self.target_var] = func(*resolved_args, **resolved_kwargs)
                     return ''
 
+            function_name = name or getattr(func, '_decorated_function', func).__name__
+
             def compile_func(parser, token):
-                bits = token.split_contents()
-                tag_name = bits[0]
-                bits = bits[1:]
-                params_max = len(params)
-                defaults_length = defaults and len(defaults) or 0
-                params_min = params_max - defaults_length
-                if (len(bits) < 2 or bits[-2] != 'as'):
+                bits = token.split_contents()[1:]
+                if len(bits) < 2 or bits[-2] != 'as':
                     raise TemplateSyntaxError(
                         "'%s' tag takes at least 2 arguments and the "
-                        "second last argument must be 'as'" % tag_name)
-                params_vars = bits[:-2]
+                        "second last argument must be 'as'" % function_name)
                 target_var = bits[-1]
-                if (len(params_vars) < params_min or
-                        len(params_vars) > params_max):
-                    if params_min == params_max:
-                        raise TemplateSyntaxError(
-                            "%s takes %s arguments" % (tag_name, params_min))
-                    else:
-                        raise TemplateSyntaxError(
-                            "%s takes between %s and %s arguments"
-                            % (tag_name, params_min, params_max))
-                return AssignmentNode(params_vars, target_var)
+                bits = bits[:-2]
+                args, kwargs = parse_bits(parser, bits, params, varargs, varkw, defaults, takes_context, function_name)
+                return AssignmentNode(takes_context, args, kwargs, target_var)
 
-            function_name = name or getattr(func, '_decorated_function', func).__name__
             compile_func.__doc__ = func.__doc__
             self.tag(function_name, compile_func)
             return func
@@ -963,25 +1065,13 @@ class Library(object):
 
     def inclusion_tag(self, file_name, context_class=Context, takes_context=False, name=None):
         def dec(func):
-            params, xx, xxx, defaults = getargspec(func)
-            if takes_context:
-                if params[0] == 'context':
-                    params = params[1:]
-                else:
-                    raise TemplateSyntaxError("Any tag function decorated with takes_context=True must have a first argument of 'context'")
+            params, varargs, varkw, defaults = getargspec(func)
 
-            class InclusionNode(Node):
-                def __init__(self, vars_to_resolve):
-                    self.vars_to_resolve = map(Variable, vars_to_resolve)
+            class InclusionNode(TagHelperNode):
 
                 def render(self, context):
-                    resolved_vars = [var.resolve(context) for var in self.vars_to_resolve]
-                    if takes_context:
-                        args = [context] + resolved_vars
-                    else:
-                        args = resolved_vars
-
-                    dict = func(*args)
+                    resolved_args, resolved_kwargs = self.get_resolved_arguments(context)
+                    _dict = func(*resolved_args, **resolved_kwargs)
 
                     if not getattr(self, 'nodelist', False):
                         from django.template.loader import get_template, select_template
@@ -992,7 +1082,7 @@ class Library(object):
                         else:
                             t = get_template(file_name)
                         self.nodelist = t.nodelist
-                    new_context = context_class(dict, **{
+                    new_context = context_class(_dict, **{
                         'autoescape': context.autoescape,
                         'current_app': context.current_app,
                         'use_l10n': context.use_l10n,
@@ -1006,7 +1096,7 @@ class Library(object):
                     return self.nodelist.render(new_context)
 
             function_name = name or getattr(func, '_decorated_function', func).__name__
-            compile_func = partial(generic_tag_compiler, params, defaults, function_name, InclusionNode)
+            compile_func = partial(generic_tag_compiler, params=params, varargs=varargs, varkw=varkw, defaults=defaults, name=function_name, takes_context=takes_context, node_class=InclusionNode)
             compile_func.__doc__ = func.__doc__
             self.tag(function_name, compile_func)
             return func
diff --git a/django/template/defaulttags.py b/django/template/defaulttags.py
index 06838cf..f2bf133 100644
--- a/django/template/defaulttags.py
+++ b/django/template/defaulttags.py
@@ -10,64 +10,13 @@ from django.template.base import (Node, NodeList, Template, Library,
     TemplateSyntaxError, VariableDoesNotExist, InvalidTemplateLibrary,
     BLOCK_TAG_START, BLOCK_TAG_END, VARIABLE_TAG_START, VARIABLE_TAG_END,
     SINGLE_BRACE_START, SINGLE_BRACE_END, COMMENT_TAG_START, COMMENT_TAG_END,
-    get_library)
+    get_library, token_kwargs, kwarg_re)
 from django.template.smartif import IfParser, Literal
 from django.template.defaultfilters import date
 from django.utils.encoding import smart_str, smart_unicode
 from django.utils.safestring import mark_safe
 
 register = Library()
-# Regex for token keyword arguments
-kwarg_re = re.compile(r"(?:(\w+)=)?(.+)")
-
-def token_kwargs(bits, parser, support_legacy=False):
-    """
-    A utility method for parsing token keyword arguments.
-
-    :param bits: A list containing remainder of the token (split by spaces)
-        that is to be checked for arguments. Valid arguments will be removed
-        from this list.
-
-    :param support_legacy: If set to true ``True``, the legacy format
-        ``1 as foo`` will be accepted. Otherwise, only the standard ``foo=1``
-        format is allowed.
-
-    :returns: A dictionary of the arguments retrieved from the ``bits`` token
-        list.
-
-    There is no requirement for all remaining token ``bits`` to be keyword
-    arguments, so the dictionary will be returned as soon as an invalid
-    argument format is reached.
-    """
-    if not bits:
-        return {}
-    match = kwarg_re.match(bits[0])
-    kwarg_format = match and match.group(1)
-    if not kwarg_format:
-        if not support_legacy:
-            return {}
-        if len(bits) < 3 or bits[1] != 'as':
-            return {}
-
-    kwargs = {}
-    while bits:
-        if kwarg_format:
-            match = kwarg_re.match(bits[0])
-            if not match or not match.group(1):
-                return kwargs
-            key, value = match.groups()
-            del bits[:1]
-        else:
-            if len(bits) < 3 or bits[1] != 'as':
-                return kwargs
-            key, value = bits[2], bits[0]
-            del bits[:3]
-        kwargs[key] = parser.compile_filter(value)
-        if bits and not kwarg_format:
-            if bits[0] != 'and':
-                return kwargs
-            del bits[:1]
-    return kwargs
 
 class AutoEscapeControlNode(Node):
     """Implements the actions of the autoescape tag."""
diff --git a/docs/releases/1.4.txt b/docs/releases/1.4.txt
index 90c5e24..54efc42 100644
--- a/docs/releases/1.4.txt
+++ b/docs/releases/1.4.txt
@@ -275,6 +275,11 @@ Django 1.4 also includes several smaller improvements worth noting:
 
 * Customizable names for :meth:`~django.template.Library.simple_tag`.
 
+* Added ``*args`` and ``*kwargs`` support to
+  :meth:`~django.template.Library.simple_tag`,
+  :meth:`~django.template.Library.inclusion_tag` and the newly introduced
+  :meth:`~django.template.Library.assignment_tag`.
+
 * In the documentation, a helpful :doc:`security overview </topics/security>`
   page.
 
diff --git a/tests/regressiontests/templates/custom.py b/tests/regressiontests/templates/custom.py
index d781874..c05229e 100644
--- a/tests/regressiontests/templates/custom.py
+++ b/tests/regressiontests/templates/custom.py
@@ -35,6 +35,56 @@ class CustomTagTests(TestCase):
         t = template.Template('{% load custom %}{% params_and_context 37 %}')
         self.assertEqual(t.render(c), u'params_and_context - Expected result (context value: 42): 37')
 
+        t = template.Template('{% load custom %}{% simple_two_params 37 42 %}')
+        self.assertEqual(t.render(c), u'simple_two_params - Expected result: 37, 42')
+
+        t = template.Template('{% load custom %}{% simple_one_default 37 %}')
+        self.assertEqual(t.render(c), u'simple_one_default - Expected result: 37, hi')
+
+        t = template.Template('{% load custom %}{% simple_one_default 37 two="hello" %}')
+        self.assertEqual(t.render(c), u'simple_one_default - Expected result: 37, hello')
+
+        t = template.Template('{% load custom %}{% simple_one_default one=99 two="hello" %}')
+        self.assertEqual(t.render(c), u'simple_one_default - Expected result: 99, hello')
+
+        self.assertRaisesRegexp(template.TemplateSyntaxError,
+            "'simple_one_default' received unexpected keyword argument 'three'",
+            template.Template, '{% load custom %}{% simple_one_default 99 two="hello" three="foo" %}')
+
+        t = template.Template('{% load custom %}{% simple_one_default 37 42 %}')
+        self.assertEqual(t.render(c), u'simple_one_default - Expected result: 37, 42')
+
+        t = template.Template('{% load custom %}{% simple_unlimited_args 37 %}')
+        self.assertEqual(t.render(c), u'simple_unlimited_args - Expected result: 37, hi')
+
+        t = template.Template('{% load custom %}{% simple_unlimited_args 37 42 56 89 %}')
+        self.assertEqual(t.render(c), u'simple_unlimited_args - Expected result: 37, 42, 56, 89')
+
+        t = template.Template('{% load custom %}{% simple_only_unlimited_args %}')
+        self.assertEqual(t.render(c), u'simple_only_unlimited_args - Expected result: ')
+
+        t = template.Template('{% load custom %}{% simple_only_unlimited_args 37 42 56 89 %}')
+        self.assertEqual(t.render(c), u'simple_only_unlimited_args - Expected result: 37, 42, 56, 89')
+
+        self.assertRaisesRegexp(template.TemplateSyntaxError,
+            "'simple_two_params' received too many positional arguments",
+            template.Template, '{% load custom %}{% simple_two_params 37 42 56 %}')
+
+        self.assertRaisesRegexp(template.TemplateSyntaxError,
+            "'simple_one_default' received too many positional arguments",
+            template.Template, '{% load custom %}{% simple_one_default 37 42 56 %}')
+
+        t = template.Template('{% load custom %}{% simple_unlimited_args_kwargs 37 40|add:2 56 eggs="scrambled" four=1|add:3 %}')
+        self.assertEqual(t.render(c), u'simple_unlimited_args_kwargs - Expected result: 37, 42, 56 / eggs=scrambled, four=4')
+
+        self.assertRaisesRegexp(template.TemplateSyntaxError,
+            "'simple_unlimited_args_kwargs' received some positional argument\(s\) after some keyword argument\(s\)",
+            template.Template, '{% load custom %}{% simple_unlimited_args_kwargs 37 40|add:2 eggs="scrambled" 56 four=1|add:3 %}')
+
+        self.assertRaisesRegexp(template.TemplateSyntaxError,
+            "'simple_unlimited_args_kwargs' received multiple values for keyword argument 'eggs'",
+            template.Template, '{% load custom %}{% simple_unlimited_args_kwargs 37 eggs="scrambled" eggs="scrambled" %}')
+
     def test_simple_tag_registration(self):
         # Test that the decorators preserve the decorated function's docstring, name and attributes.
         self.verify_tag(custom.no_params, 'no_params')
@@ -42,16 +92,14 @@ class CustomTagTests(TestCase):
         self.verify_tag(custom.explicit_no_context, 'explicit_no_context')
         self.verify_tag(custom.no_params_with_context, 'no_params_with_context')
         self.verify_tag(custom.params_and_context, 'params_and_context')
+        self.verify_tag(custom.simple_unlimited_args_kwargs, 'simple_unlimited_args_kwargs')
+        self.verify_tag(custom.simple_tag_without_context_parameter, 'simple_tag_without_context_parameter')
 
     def test_simple_tag_missing_context(self):
-        # That the 'context' parameter must be present when takes_context is True
-        def a_simple_tag_without_parameters(arg):
-            """Expected __doc__"""
-            return "Expected result"
-
-        register = template.Library()
-        decorator = register.simple_tag(takes_context=True)
-        self.assertRaises(template.TemplateSyntaxError, decorator, a_simple_tag_without_parameters)
+        # The 'context' parameter must be present when takes_context is True
+        self.assertRaisesRegexp(template.TemplateSyntaxError,
+            "'simple_tag_without_context_parameter' is decorated with takes_context=True so it must have a first argument of 'context'",
+            template.Template, '{% load custom %}{% simple_tag_without_context_parameter 123 %}')
 
     def test_inclusion_tags(self):
         c = template.Context({'value': 42})
@@ -71,6 +119,70 @@ class CustomTagTests(TestCase):
         t = template.Template('{% load custom %}{% inclusion_params_and_context 37 %}')
         self.assertEqual(t.render(c), u'inclusion_params_and_context - Expected result (context value: 42): 37\n')
 
+        t = template.Template('{% load custom %}{% inclusion_two_params 37 42 %}')
+        self.assertEqual(t.render(c), u'inclusion_two_params - Expected result: 37, 42\n')
+
+        t = template.Template('{% load custom %}{% inclusion_one_default 37 %}')
+        self.assertEqual(t.render(c), u'inclusion_one_default - Expected result: 37, hi\n')
+
+        t = template.Template('{% load custom %}{% inclusion_one_default 37 two="hello" %}')
+        self.assertEqual(t.render(c), u'inclusion_one_default - Expected result: 37, hello\n')
+
+        t = template.Template('{% load custom %}{% inclusion_one_default one=99 two="hello" %}')
+        self.assertEqual(t.render(c), u'inclusion_one_default - Expected result: 99, hello\n')
+
+        self.assertRaisesRegexp(template.TemplateSyntaxError,
+            "'inclusion_one_default' received unexpected keyword argument 'three'",
+            template.Template, '{% load custom %}{% inclusion_one_default 99 two="hello" three="foo" %}')
+
+        t = template.Template('{% load custom %}{% inclusion_one_default 37 42 %}')
+        self.assertEqual(t.render(c), u'inclusion_one_default - Expected result: 37, 42\n')
+
+        t = template.Template('{% load custom %}{% inclusion_unlimited_args 37 %}')
+        self.assertEqual(t.render(c), u'inclusion_unlimited_args - Expected result: 37, hi\n')
+
+        t = template.Template('{% load custom %}{% inclusion_unlimited_args 37 42 56 89 %}')
+        self.assertEqual(t.render(c), u'inclusion_unlimited_args - Expected result: 37, 42, 56, 89\n')
+
+        t = template.Template('{% load custom %}{% inclusion_only_unlimited_args %}')
+        self.assertEqual(t.render(c), u'inclusion_only_unlimited_args - Expected result: \n')
+
+        t = template.Template('{% load custom %}{% inclusion_only_unlimited_args 37 42 56 89 %}')
+        self.assertEqual(t.render(c), u'inclusion_only_unlimited_args - Expected result: 37, 42, 56, 89\n')
+
+        self.assertRaisesRegexp(template.TemplateSyntaxError,
+            "'inclusion_two_params' received too many positional arguments",
+            template.Template, '{% load custom %}{% inclusion_two_params 37 42 56 %}')
+
+        self.assertRaisesRegexp(template.TemplateSyntaxError,
+            "'inclusion_one_default' received too many positional arguments",
+            template.Template, '{% load custom %}{% inclusion_one_default 37 42 56 %}')
+
+        self.assertRaisesRegexp(template.TemplateSyntaxError,
+            "'inclusion_one_default' did not receive value\(s\) for the argument\(s\): 'one'",
+            template.Template, '{% load custom %}{% inclusion_one_default %}')
+
+        self.assertRaisesRegexp(template.TemplateSyntaxError,
+            "'inclusion_unlimited_args' did not receive value\(s\) for the argument\(s\): 'one'",
+            template.Template, '{% load custom %}{% inclusion_unlimited_args %}')
+
+        t = template.Template('{% load custom %}{% inclusion_unlimited_args_kwargs 37 40|add:2 56 eggs="scrambled" four=1|add:3 %}')
+        self.assertEqual(t.render(c), u'inclusion_unlimited_args_kwargs - Expected result: 37, 42, 56 / eggs=scrambled, four=4\n')
+
+        self.assertRaisesRegexp(template.TemplateSyntaxError,
+            "'inclusion_unlimited_args_kwargs' received some positional argument\(s\) after some keyword argument\(s\)",
+            template.Template, '{% load custom %}{% inclusion_unlimited_args_kwargs 37 40|add:2 eggs="scrambled" 56 four=1|add:3 %}')
+
+        self.assertRaisesRegexp(template.TemplateSyntaxError,
+            "'inclusion_unlimited_args_kwargs' received multiple values for keyword argument 'eggs'",
+            template.Template, '{% load custom %}{% inclusion_unlimited_args_kwargs 37 eggs="scrambled" eggs="scrambled" %}')
+
+    def test_include_tag_missing_context(self):
+        # The 'context' parameter must be present when takes_context is True
+        self.assertRaisesRegexp(template.TemplateSyntaxError,
+            "'inclusion_tag_without_context_parameter' is decorated with takes_context=True so it must have a first argument of 'context'",
+            template.Template, '{% load custom %}{% inclusion_tag_without_context_parameter 123 %}')
+
     def test_inclusion_tags_from_template(self):
         c = template.Context({'value': 42})
 
@@ -89,6 +201,27 @@ class CustomTagTests(TestCase):
         t = template.Template('{% load custom %}{% inclusion_params_and_context_from_template 37 %}')
         self.assertEqual(t.render(c), u'inclusion_params_and_context_from_template - Expected result (context value: 42): 37\n')
 
+        t = template.Template('{% load custom %}{% inclusion_two_params_from_template 37 42 %}')
+        self.assertEqual(t.render(c), u'inclusion_two_params_from_template - Expected result: 37, 42\n')
+
+        t = template.Template('{% load custom %}{% inclusion_one_default_from_template 37 %}')
+        self.assertEqual(t.render(c), u'inclusion_one_default_from_template - Expected result: 37, hi\n')
+
+        t = template.Template('{% load custom %}{% inclusion_one_default_from_template 37 42 %}')
+        self.assertEqual(t.render(c), u'inclusion_one_default_from_template - Expected result: 37, 42\n')
+
+        t = template.Template('{% load custom %}{% inclusion_unlimited_args_from_template 37 %}')
+        self.assertEqual(t.render(c), u'inclusion_unlimited_args_from_template - Expected result: 37, hi\n')
+
+        t = template.Template('{% load custom %}{% inclusion_unlimited_args_from_template 37 42 56 89 %}')
+        self.assertEqual(t.render(c), u'inclusion_unlimited_args_from_template - Expected result: 37, 42, 56, 89\n')
+
+        t = template.Template('{% load custom %}{% inclusion_only_unlimited_args_from_template %}')
+        self.assertEqual(t.render(c), u'inclusion_only_unlimited_args_from_template - Expected result: \n')
+
+        t = template.Template('{% load custom %}{% inclusion_only_unlimited_args_from_template 37 42 56 89 %}')
+        self.assertEqual(t.render(c), u'inclusion_only_unlimited_args_from_template - Expected result: 37, 42, 56, 89\n')
+
     def test_inclusion_tag_registration(self):
         # Test that the decorators preserve the decorated function's docstring, name and attributes.
         self.verify_tag(custom.inclusion_no_params, 'inclusion_no_params')
@@ -96,6 +229,14 @@ class CustomTagTests(TestCase):
         self.verify_tag(custom.inclusion_explicit_no_context, 'inclusion_explicit_no_context')
         self.verify_tag(custom.inclusion_no_params_with_context, 'inclusion_no_params_with_context')
         self.verify_tag(custom.inclusion_params_and_context, 'inclusion_params_and_context')
+        self.verify_tag(custom.inclusion_two_params, 'inclusion_two_params')
+        self.verify_tag(custom.inclusion_one_default, 'inclusion_one_default')
+        self.verify_tag(custom.inclusion_unlimited_args, 'inclusion_unlimited_args')
+        self.verify_tag(custom.inclusion_only_unlimited_args, 'inclusion_only_unlimited_args')
+        self.verify_tag(custom.inclusion_tag_without_context_parameter, 'inclusion_tag_without_context_parameter')
+        self.verify_tag(custom.inclusion_tag_use_l10n, 'inclusion_tag_use_l10n')
+        self.verify_tag(custom.inclusion_tag_current_app, 'inclusion_tag_current_app')
+        self.verify_tag(custom.inclusion_unlimited_args_kwargs, 'inclusion_unlimited_args_kwargs')
 
     def test_15070_current_app(self):
         """
@@ -139,6 +280,37 @@ class CustomTagTests(TestCase):
         t = template.Template('{% load custom %}{% assignment_params_and_context 37 as var %}The result is: {{ var }}')
         self.assertEqual(t.render(c), u'The result is: assignment_params_and_context - Expected result (context value: 42): 37')
 
+        t = template.Template('{% load custom %}{% assignment_two_params 37 42 as var %}The result is: {{ var }}')
+        self.assertEqual(t.render(c), u'The result is: assignment_two_params - Expected result: 37, 42')
+
+        t = template.Template('{% load custom %}{% assignment_one_default 37 as var %}The result is: {{ var }}')
+        self.assertEqual(t.render(c), u'The result is: assignment_one_default - Expected result: 37, hi')
+
+        t = template.Template('{% load custom %}{% assignment_one_default 37 two="hello" as var %}The result is: {{ var }}')
+        self.assertEqual(t.render(c), u'The result is: assignment_one_default - Expected result: 37, hello')
+
+        t = template.Template('{% load custom %}{% assignment_one_default one=99 two="hello" as var %}The result is: {{ var }}')
+        self.assertEqual(t.render(c), u'The result is: assignment_one_default - Expected result: 99, hello')
+
+        self.assertRaisesRegexp(template.TemplateSyntaxError,
+            "'assignment_one_default' received unexpected keyword argument 'three'",
+            template.Template, '{% load custom %}{% assignment_one_default 99 two="hello" three="foo" as var %}')
+
+        t = template.Template('{% load custom %}{% assignment_one_default 37 42 as var %}The result is: {{ var }}')
+        self.assertEqual(t.render(c), u'The result is: assignment_one_default - Expected result: 37, 42')
+
+        t = template.Template('{% load custom %}{% assignment_unlimited_args 37 as var %}The result is: {{ var }}')
+        self.assertEqual(t.render(c), u'The result is: assignment_unlimited_args - Expected result: 37, hi')
+
+        t = template.Template('{% load custom %}{% assignment_unlimited_args 37 42 56 89 as var %}The result is: {{ var }}')
+        self.assertEqual(t.render(c), u'The result is: assignment_unlimited_args - Expected result: 37, 42, 56, 89')
+
+        t = template.Template('{% load custom %}{% assignment_only_unlimited_args as var %}The result is: {{ var }}')
+        self.assertEqual(t.render(c), u'The result is: assignment_only_unlimited_args - Expected result: ')
+
+        t = template.Template('{% load custom %}{% assignment_only_unlimited_args 37 42 56 89 as var %}The result is: {{ var }}')
+        self.assertEqual(t.render(c), u'The result is: assignment_only_unlimited_args - Expected result: 37, 42, 56, 89')
+
         self.assertRaisesRegexp(template.TemplateSyntaxError,
             "'assignment_one_param' tag takes at least 2 arguments and the second last argument must be 'as'",
             template.Template, '{% load custom %}{% assignment_one_param 37 %}The result is: {{ var }}')
@@ -151,6 +323,33 @@ class CustomTagTests(TestCase):
             "'assignment_one_param' tag takes at least 2 arguments and the second last argument must be 'as'",
             template.Template, '{% load custom %}{% assignment_one_param 37 ass var %}The result is: {{ var }}')
 
+        self.assertRaisesRegexp(template.TemplateSyntaxError,
+            "'assignment_two_params' received too many positional arguments",
+            template.Template, '{% load custom %}{% assignment_two_params 37 42 56 as var %}The result is: {{ var }}')
+
+        self.assertRaisesRegexp(template.TemplateSyntaxError,
+            "'assignment_one_default' received too many positional arguments",
+            template.Template, '{% load custom %}{% assignment_one_default 37 42 56 as var %}The result is: {{ var }}')
+
+        self.assertRaisesRegexp(template.TemplateSyntaxError,
+            "'assignment_one_default' did not receive value\(s\) for the argument\(s\): 'one'",
+            template.Template, '{% load custom %}{% assignment_one_default as var %}The result is: {{ var }}')
+
+        self.assertRaisesRegexp(template.TemplateSyntaxError,
+            "'assignment_unlimited_args' did not receive value\(s\) for the argument\(s\): 'one'",
+            template.Template, '{% load custom %}{% assignment_unlimited_args as var %}The result is: {{ var }}')
+
+        t = template.Template('{% load custom %}{% assignment_unlimited_args_kwargs 37 40|add:2 56 eggs="scrambled" four=1|add:3 as var %}The result is: {{ var }}')
+        self.assertEqual(t.render(c), u'The result is: assignment_unlimited_args_kwargs - Expected result: 37, 42, 56 / eggs=scrambled, four=4')
+
+        self.assertRaisesRegexp(template.TemplateSyntaxError,
+            "'assignment_unlimited_args_kwargs' received some positional argument\(s\) after some keyword argument\(s\)",
+            template.Template, '{% load custom %}{% assignment_unlimited_args_kwargs 37 40|add:2 eggs="scrambled" 56 four=1|add:3 as var %}The result is: {{ var }}')
+
+        self.assertRaisesRegexp(template.TemplateSyntaxError,
+            "'assignment_unlimited_args_kwargs' received multiple values for keyword argument 'eggs'",
+            template.Template, '{% load custom %}{% assignment_unlimited_args_kwargs 37 eggs="scrambled" eggs="scrambled" as var %}The result is: {{ var }}')
+
     def test_assignment_tag_registration(self):
         # Test that the decorators preserve the decorated function's docstring, name and attributes.
         self.verify_tag(custom.assignment_no_params, 'assignment_no_params')
@@ -158,16 +357,16 @@ class CustomTagTests(TestCase):
         self.verify_tag(custom.assignment_explicit_no_context, 'assignment_explicit_no_context')
         self.verify_tag(custom.assignment_no_params_with_context, 'assignment_no_params_with_context')
         self.verify_tag(custom.assignment_params_and_context, 'assignment_params_and_context')
+        self.verify_tag(custom.assignment_one_default, 'assignment_one_default')
+        self.verify_tag(custom.assignment_two_params, 'assignment_two_params')
+        self.verify_tag(custom.assignment_unlimited_args, 'assignment_unlimited_args')
+        self.verify_tag(custom.assignment_only_unlimited_args, 'assignment_only_unlimited_args')
+        self.verify_tag(custom.assignment_unlimited_args, 'assignment_unlimited_args')
+        self.verify_tag(custom.assignment_unlimited_args_kwargs, 'assignment_unlimited_args_kwargs')
+        self.verify_tag(custom.assignment_tag_without_context_parameter, 'assignment_tag_without_context_parameter')
 
     def test_assignment_tag_missing_context(self):
-        # That the 'context' parameter must be present when takes_context is True
-        def an_assignment_tag_without_parameters(arg):
-            """Expected __doc__"""
-            return "Expected result"
-
-        register = template.Library()
-        decorator = register.assignment_tag(takes_context=True)
-
+        # The 'context' parameter must be present when takes_context is True
         self.assertRaisesRegexp(template.TemplateSyntaxError,
-            "Any tag function decorated with takes_context=True must have a first argument of 'context'",
-            decorator, an_assignment_tag_without_parameters)
+            "'assignment_tag_without_context_parameter' is decorated with takes_context=True so it must have a first argument of 'context'",
+            template.Template, '{% load custom %}{% assignment_tag_without_context_parameter 123 as var %}')
diff --git a/tests/regressiontests/templates/templatetags/custom.py b/tests/regressiontests/templates/templatetags/custom.py
index dfa4171..0e07d53 100644
--- a/tests/regressiontests/templates/templatetags/custom.py
+++ b/tests/regressiontests/templates/templatetags/custom.py
@@ -1,6 +1,7 @@
 from django import template
 from django.template.defaultfilters import stringfilter
 from django.template.loader import get_template
+import operator
 
 register = template.Library()
 
@@ -40,6 +41,61 @@ def params_and_context(context, arg):
     return "params_and_context - Expected result (context value: %s): %s" % (context['value'], arg)
 params_and_context.anything = "Expected params_and_context __dict__"
 
+@register.simple_tag
+def simple_two_params(one, two):
+    """Expected simple_two_params __doc__"""
+    return "simple_two_params - Expected result: %s, %s" % (one, two)
+simple_two_params.anything = "Expected simple_two_params __dict__"
+
+@register.simple_tag
+def simple_one_default(one, two='hi'):
+    """Expected simple_one_default __doc__"""
+    return "simple_one_default - Expected result: %s, %s" % (one, two)
+simple_one_default.anything = "Expected simple_one_default __dict__"
+
+@register.simple_tag
+def simple_unlimited_args(one, two='hi', *args):
+    """Expected simple_unlimited_args __doc__"""
+    return "simple_unlimited_args - Expected result: %s" % (', '.join([unicode(arg) for arg in [one, two] + list(args)]))
+simple_unlimited_args.anything = "Expected simple_unlimited_args __dict__"
+
+@register.simple_tag
+def simple_only_unlimited_args(*args):
+    """Expected simple_only_unlimited_args __doc__"""
+    return "simple_only_unlimited_args - Expected result: %s" % ', '.join([unicode(arg) for arg in args])
+simple_only_unlimited_args.anything = "Expected simple_only_unlimited_args __dict__"
+
+@register.simple_tag
+def simple_unlimited_args_kwargs(one, two='hi', *args, **kwargs):
+    """Expected simple_unlimited_args_kwargs __doc__"""
+    # Sort the dictionary by key to guarantee the order for testing.
+    sorted_kwarg = sorted(kwargs.iteritems(), key=operator.itemgetter(0))
+    return "simple_unlimited_args_kwargs - Expected result: %s / %s" % (
+        ', '.join([unicode(arg) for arg in [one, two] + list(args)]),
+        ', '.join(['%s=%s' % (k, v) for (k, v) in sorted_kwarg])
+        )
+simple_unlimited_args_kwargs.anything = "Expected simple_unlimited_args_kwargs __dict__"
+
+@register.simple_tag(takes_context=True)
+def simple_tag_without_context_parameter(arg):
+    """Expected simple_tag_without_context_parameter __doc__"""
+    return "Expected result"
+simple_tag_without_context_parameter.anything = "Expected simple_tag_without_context_parameter __dict__"
+
+@register.simple_tag(takes_context=True)
+def current_app(context):
+    return "%s" % context.current_app
+
+@register.simple_tag(takes_context=True)
+def use_l10n(context):
+    return "%s" % context.use_l10n
+
+@register.simple_tag(name='minustwo')
+def minustwo_overridden_name(value):
+    return value - 2
+
+register.simple_tag(lambda x: x - 1, name='minusone')
+
 @register.inclusion_tag('inclusion.html')
 def inclusion_no_params():
     """Expected inclusion_no_params __doc__"""
@@ -100,21 +156,82 @@ def inclusion_params_and_context_from_template(context, arg):
     return {"result" : "inclusion_params_and_context_from_template - Expected result (context value: %s): %s" % (context['value'], arg)}
 inclusion_params_and_context_from_template.anything = "Expected inclusion_params_and_context_from_template __dict__"
 
-@register.simple_tag(takes_context=True)
-def current_app(context):
-    return "%s" % context.current_app
+@register.inclusion_tag('inclusion.html')
+def inclusion_two_params(one, two):
+    """Expected inclusion_two_params __doc__"""
+    return {"result": "inclusion_two_params - Expected result: %s, %s" % (one, two)}
+inclusion_two_params.anything = "Expected inclusion_two_params __dict__"
+
+@register.inclusion_tag(get_template('inclusion.html'))
+def inclusion_two_params_from_template(one, two):
+    """Expected inclusion_two_params_from_template __doc__"""
+    return {"result": "inclusion_two_params_from_template - Expected result: %s, %s" % (one, two)}
+inclusion_two_params_from_template.anything = "Expected inclusion_two_params_from_template __dict__"
+
+@register.inclusion_tag('inclusion.html')
+def inclusion_one_default(one, two='hi'):
+    """Expected inclusion_one_default __doc__"""
+    return {"result": "inclusion_one_default - Expected result: %s, %s" % (one, two)}
+inclusion_one_default.anything = "Expected inclusion_one_default __dict__"
+
+@register.inclusion_tag(get_template('inclusion.html'))
+def inclusion_one_default_from_template(one, two='hi'):
+    """Expected inclusion_one_default_from_template __doc__"""
+    return {"result": "inclusion_one_default_from_template - Expected result: %s, %s" % (one, two)}
+inclusion_one_default_from_template.anything = "Expected inclusion_one_default_from_template __dict__"
+
+@register.inclusion_tag('inclusion.html')
+def inclusion_unlimited_args(one, two='hi', *args):
+    """Expected inclusion_unlimited_args __doc__"""
+    return {"result": "inclusion_unlimited_args - Expected result: %s" % (', '.join([unicode(arg) for arg in [one, two] + list(args)]))}
+inclusion_unlimited_args.anything = "Expected inclusion_unlimited_args __dict__"
+
+@register.inclusion_tag(get_template('inclusion.html'))
+def inclusion_unlimited_args_from_template(one, two='hi', *args):
+    """Expected inclusion_unlimited_args_from_template __doc__"""
+    return {"result": "inclusion_unlimited_args_from_template - Expected result: %s" % (', '.join([unicode(arg) for arg in [one, two] + list(args)]))}
+inclusion_unlimited_args_from_template.anything = "Expected inclusion_unlimited_args_from_template __dict__"
+
+@register.inclusion_tag('inclusion.html')
+def inclusion_only_unlimited_args(*args):
+    """Expected inclusion_only_unlimited_args __doc__"""
+    return {"result": "inclusion_only_unlimited_args - Expected result: %s" % (', '.join([unicode(arg) for arg in args]))}
+inclusion_only_unlimited_args.anything = "Expected inclusion_only_unlimited_args __dict__"
+
+@register.inclusion_tag(get_template('inclusion.html'))
+def inclusion_only_unlimited_args_from_template(*args):
+    """Expected inclusion_only_unlimited_args_from_template __doc__"""
+    return {"result": "inclusion_only_unlimited_args_from_template - Expected result: %s" % (', '.join([unicode(arg) for arg in args]))}
+inclusion_only_unlimited_args_from_template.anything = "Expected inclusion_only_unlimited_args_from_template __dict__"
 
 @register.inclusion_tag('test_incl_tag_current_app.html', takes_context=True)
 def inclusion_tag_current_app(context):
+    """Expected inclusion_tag_current_app __doc__"""
     return {}
-
-@register.simple_tag(takes_context=True)
-def use_l10n(context):
-    return "%s" % context.use_l10n
+inclusion_tag_current_app.anything = "Expected inclusion_tag_current_app __dict__"
 
 @register.inclusion_tag('test_incl_tag_use_l10n.html', takes_context=True)
 def inclusion_tag_use_l10n(context):
+    """Expected inclusion_tag_use_l10n __doc__"""
     return {}
+inclusion_tag_use_l10n.anything = "Expected inclusion_tag_use_l10n __dict__"
+
+@register.inclusion_tag('inclusion.html')
+def inclusion_unlimited_args_kwargs(one, two='hi', *args, **kwargs):
+    """Expected inclusion_unlimited_args_kwargs __doc__"""
+    # Sort the dictionary by key to guarantee the order for testing.
+    sorted_kwarg = sorted(kwargs.iteritems(), key=operator.itemgetter(0))
+    return {"result": "inclusion_unlimited_args_kwargs - Expected result: %s / %s" % (
+        ', '.join([unicode(arg) for arg in [one, two] + list(args)]),
+        ', '.join(['%s=%s' % (k, v) for (k, v) in sorted_kwarg])
+        )}
+inclusion_unlimited_args_kwargs.anything = "Expected inclusion_unlimited_args_kwargs __dict__"
+
+@register.inclusion_tag('inclusion.html', takes_context=True)
+def inclusion_tag_without_context_parameter(arg):
+    """Expected inclusion_tag_without_context_parameter __doc__"""
+    return {}
+inclusion_tag_without_context_parameter.anything = "Expected inclusion_tag_without_context_parameter __dict__"
 
 @register.assignment_tag
 def assignment_no_params():
@@ -146,8 +263,43 @@ def assignment_params_and_context(context, arg):
     return "assignment_params_and_context - Expected result (context value: %s): %s" % (context['value'], arg)
 assignment_params_and_context.anything = "Expected assignment_params_and_context __dict__"
 
-register.simple_tag(lambda x: x - 1, name='minusone')
+@register.assignment_tag
+def assignment_two_params(one, two):
+    """Expected assignment_two_params __doc__"""
+    return "assignment_two_params - Expected result: %s, %s" % (one, two)
+assignment_two_params.anything = "Expected assignment_two_params __dict__"
 
-@register.simple_tag(name='minustwo')
-def minustwo_overridden_name(value):
-    return value - 2
+@register.assignment_tag
+def assignment_one_default(one, two='hi'):
+    """Expected assignment_one_default __doc__"""
+    return "assignment_one_default - Expected result: %s, %s" % (one, two)
+assignment_one_default.anything = "Expected assignment_one_default __dict__"
+
+@register.assignment_tag
+def assignment_unlimited_args(one, two='hi', *args):
+    """Expected assignment_unlimited_args __doc__"""
+    return "assignment_unlimited_args - Expected result: %s" % (', '.join([unicode(arg) for arg in [one, two] + list(args)]))
+assignment_unlimited_args.anything = "Expected assignment_unlimited_args __dict__"
+
+@register.assignment_tag
+def assignment_only_unlimited_args(*args):
+    """Expected assignment_only_unlimited_args __doc__"""
+    return "assignment_only_unlimited_args - Expected result: %s" % ', '.join([unicode(arg) for arg in args])
+assignment_only_unlimited_args.anything = "Expected assignment_only_unlimited_args __dict__"
+
+@register.assignment_tag
+def assignment_unlimited_args_kwargs(one, two='hi', *args, **kwargs):
+    """Expected assignment_unlimited_args_kwargs __doc__"""
+    # Sort the dictionary by key to guarantee the order for testing.
+    sorted_kwarg = sorted(kwargs.iteritems(), key=operator.itemgetter(0))
+    return "assignment_unlimited_args_kwargs - Expected result: %s / %s" % (
+        ', '.join([unicode(arg) for arg in [one, two] + list(args)]),
+        ', '.join(['%s=%s' % (k, v) for (k, v) in sorted_kwarg])
+        )
+assignment_unlimited_args_kwargs.anything = "Expected assignment_unlimited_args_kwargs __dict__"
+
+@register.assignment_tag(takes_context=True)
+def assignment_tag_without_context_parameter(arg):
+    """Expected assignment_tag_without_context_parameter __doc__"""
+    return "Expected result"
+assignment_tag_without_context_parameter.anything = "Expected assignment_tag_without_context_parameter __dict__"
\ No newline at end of file
