diff --git a/django/template/base.py b/django/template/base.py
index c94eeb5..6fad075 100644
--- a/django/template/base.py
+++ b/django/template/base.py
@@ -19,6 +19,12 @@ TOKEN_TEXT = 0
 TOKEN_VAR = 1
 TOKEN_BLOCK = 2
 TOKEN_COMMENT = 3
+TOKEN_MAPPING = {
+    TOKEN_TEXT: 'Text',
+    TOKEN_VAR: 'Var',
+    TOKEN_BLOCK: 'Block',
+    TOKEN_COMMENT: 'Comment',
+}
 
 # template syntax constants
 FILTER_SEPARATOR = '|'
@@ -41,9 +47,10 @@ ALLOWED_VARIABLE_CHARS = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01
 UNKNOWN_SOURCE = '<unknown source>'
 
 # match a variable or block tag and capture the entire tag, including start/end delimiters
-tag_re = re.compile('(%s.*?%s|%s.*?%s|%s.*?%s)' % (re.escape(BLOCK_TAG_START), re.escape(BLOCK_TAG_END),
-                                          re.escape(VARIABLE_TAG_START), re.escape(VARIABLE_TAG_END),
-                                          re.escape(COMMENT_TAG_START), re.escape(COMMENT_TAG_END)))
+tag_re = (re.compile('(%s.*?%s|%s.*?%s|%s.*?%s)' %
+          (re.escape(BLOCK_TAG_START), re.escape(BLOCK_TAG_END),
+           re.escape(VARIABLE_TAG_START), re.escape(VARIABLE_TAG_END),
+           re.escape(COMMENT_TAG_START), re.escape(COMMENT_TAG_END))))
 
 # global dictionary of libraries that have been loaded using get_library
 libraries = {}
@@ -73,7 +80,8 @@ class VariableDoesNotExist(Exception):
         return unicode(self).encode('utf-8')
 
     def __unicode__(self):
-        return self.msg % tuple([force_unicode(p, errors='replace') for p in self.params])
+        return self.msg % tuple([force_unicode(p, errors='replace')
+                                 for p in self.params])
 
 class InvalidTemplateLibrary(Exception):
     pass
@@ -101,7 +109,8 @@ class Template(object):
         try:
             template_string = smart_unicode(template_string)
         except UnicodeDecodeError:
-            raise TemplateEncodingError("Templates can only be constructed from unicode or UTF-8 strings.")
+            raise TemplateEncodingError("Templates can only be constructed "
+                                        "from unicode or UTF-8 strings.")
         if settings.TEMPLATE_DEBUG and origin is None:
             origin = StringOrigin(template_string)
         self.nodelist = compile_string(template_string, origin)
@@ -141,9 +150,9 @@ class Token(object):
         self.lineno = None
 
     def __str__(self):
-        return '<%s token: "%s...">' % \
-            ({TOKEN_TEXT: 'Text', TOKEN_VAR: 'Var', TOKEN_BLOCK: 'Block', TOKEN_COMMENT: 'Comment'}[self.token_type],
-            self.contents[:20].replace('\n', ''))
+        token_name = TOKEN_MAPPING[self.token_type]
+        return ('<%s token: "%s...">' %
+                (token_name, self.contents[:20].replace('\n', '')))
 
     def split_contents(self):
         split = []
@@ -207,7 +216,8 @@ class Parser(object):
             self.add_library(lib)
 
     def parse(self, parse_until=None):
-        if parse_until is None: parse_until = []
+        if parse_until is None:
+            parse_until = []
         nodelist = self.create_nodelist()
         while self.tokens:
             token = self.next_token()
@@ -218,17 +228,19 @@ class Parser(object):
                     self.empty_variable(token)
                 filter_expression = self.compile_filter(token.contents)
                 var_node = self.create_variable_node(filter_expression)
-                self.extend_nodelist(nodelist, var_node,token)
+                self.extend_nodelist(nodelist, var_node, token)
             elif token.token_type == TOKEN_BLOCK:
                 if token.contents in parse_until:
-                    # put token back on token list so calling code knows why it terminated
+                    # put token back on token list so calling
+                    # code knows why it terminated
                     self.prepend_token(token)
                     return nodelist
                 try:
                     command = token.contents.split()[0]
                 except IndexError:
                     self.empty_block_tag(token)
-                # execute callback function for this tag and append resulting node
+                # execute callback function for this tag and append
+                # resulting node
                 self.enter_command(command, token)
                 try:
                     compile_func = self.tags[command]
@@ -264,7 +276,8 @@ class Parser(object):
                 if nodelist.contains_nontext:
                     raise AttributeError
             except AttributeError:
-                raise TemplateSyntaxError("%r must be the first tag in the template." % node)
+                raise TemplateSyntaxError("%r must be the first tag "
+                                          "in the template." % node)
         if isinstance(nodelist, NodeList) and not isinstance(node, TextNode):
             nodelist.contains_nontext = True
         nodelist.append(node)
@@ -286,11 +299,12 @@ class Parser(object):
 
     def invalid_block_tag(self, token, command, parse_until=None):
         if parse_until:
-            raise self.error(token, "Invalid block tag: '%s', expected %s" % (command, get_text_list(["'%s'" % p for p in parse_until])))
+            raise self.error(token, "Invalid block tag: '%s', expected %s" %
+                (command, get_text_list(["'%s'" % p for p in parse_until])))
         raise self.error(token, "Invalid block tag: '%s'" % command)
 
     def unclosed_block_tag(self, parse_until):
-        raise self.error(None, "Unclosed tags: %s " %  ', '.join(parse_until))
+        raise self.error(None, "Unclosed tags: %s " % ', '.join(parse_until))
 
     def compile_function_error(self, token, e):
         pass
@@ -320,8 +334,9 @@ class Parser(object):
 
 class TokenParser(object):
     """
-    Subclass this and implement the top() method to parse a template line. When
-    instantiating the parser, pass in the line from the Django template parser.
+    Subclass this and implement the top() method to parse a template line.
+    When instantiating the parser, pass in the line from the Django template
+    parser.
 
     The parser's "tagname" instance-variable stores the name of the tag that
     the filter was called with.
@@ -351,7 +366,8 @@ class TokenParser(object):
         subject = self.subject
         i = self.pointer
         if i >= len(subject):
-            raise TemplateSyntaxError("expected another tag, found end of string: %s" % subject)
+            raise TemplateSyntaxError("expected another tag, found "
+                                      "end of string: %s" % subject)
         p = i
         while i < len(subject) and subject[i] not in (' ', '\t'):
             i += 1
@@ -363,12 +379,18 @@ class TokenParser(object):
         return s
 
     def value(self):
-        "A microparser that parses for a value: some string constant or variable name."
+        """
+        A microparser that parses for a value: some string constant or
+        variable name.
+        """
         subject = self.subject
         i = self.pointer
 
         def next_space_index(subject, i):
-            "Increment pointer until a real space (i.e. a space not within quotes) is encountered"
+            """
+            Increment pointer until a real space (i.e. a space not within
+            quotes) is encountered
+            """
             while i < len(subject) and subject[i] not in (' ', '\t'):
                 if subject[i] in ('"', "'"):
                     c = subject[i]
@@ -376,22 +398,29 @@ class TokenParser(object):
                     while i < len(subject) and subject[i] != c:
                         i += 1
                     if i >= len(subject):
-                        raise TemplateSyntaxError("Searching for value. Unexpected end of string in column %d: %s" % (i, subject))
+                        raise TemplateSyntaxError("Searching for value. "
+                            "Unexpected end of string in column %d: %s" %
+                            (i, subject))
                 i += 1
             return i
 
         if i >= len(subject):
-            raise TemplateSyntaxError("Searching for value. Expected another value but found end of string: %s" % subject)
+            raise TemplateSyntaxError("Searching for value. Expected another "
+                                      "value but found end of string: %s" %
+                                      subject)
         if subject[i] in ('"', "'"):
             p = i
             i += 1
             while i < len(subject) and subject[i] != subject[p]:
                 i += 1
             if i >= len(subject):
-                raise TemplateSyntaxError("Searching for value. Unexpected end of string in column %d: %s" % (i, subject))
+                raise TemplateSyntaxError("Searching for value. Unexpected "
+                                          "end of string in column %d: %s" %
+                                          (i, subject))
             i += 1
 
-            # Continue parsing until next "real" space, so that filters are also included
+            # Continue parsing until next "real" space,
+            # so that filters are also included
             i = next_space_index(subject, i)
 
             res = subject[p:i]
@@ -419,10 +448,10 @@ constant_string = r"""
 %(strdq)s|
 %(strsq)s)
 """ % {
-    'strdq': r'"[^"\\]*(?:\\.[^"\\]*)*"', # double-quoted string
-    'strsq': r"'[^'\\]*(?:\\.[^'\\]*)*'", # single-quoted string
-    'i18n_open' : re.escape("_("),
-    'i18n_close' : re.escape(")"),
+    'strdq': r'"[^"\\]*(?:\\.[^"\\]*)*"',  # double-quoted string
+    'strsq': r"'[^'\\]*(?:\\.[^'\\]*)*'",  # single-quoted string
+    'i18n_open': re.escape("_("),
+    'i18n_close': re.escape(")"),
     }
 constant_string = constant_string.replace("\n", "")
 
@@ -440,18 +469,19 @@ filter_raw_string = r"""
  )""" % {
     'constant': constant_string,
     'num': r'[-+\.]?\d[\d\.e]*',
-    'var_chars': "\w\." ,
+    'var_chars': "\w\.",
     'filter_sep': re.escape(FILTER_SEPARATOR),
     'arg_sep': re.escape(FILTER_ARGUMENT_SEPARATOR),
   }
 
-filter_re = re.compile(filter_raw_string, re.UNICODE|re.VERBOSE)
+filter_re = re.compile(filter_raw_string, re.UNICODE | re.VERBOSE)
 
 class FilterExpression(object):
-    r"""
+    """
     Parses a variable token and its optional filters (all as a single string),
     and return a list of tuples of the filter name and arguments.
-    Sample:
+    Sample::
+
         >>> token = 'variable|default:"Default value"|date:"Y-m-d"'
         >>> p = Parser('')
         >>> fe = FilterExpression(token, p)
@@ -472,8 +502,10 @@ class FilterExpression(object):
         for match in matches:
             start = match.start()
             if upto != start:
-                raise TemplateSyntaxError("Could not parse some characters: %s|%s|%s"  % \
-                        (token[:upto], token[upto:start], token[start:]))
+                raise TemplateSyntaxError("Could not parse some characters: "
+                                          "%s|%s|%s" %
+                                          (token[:upto], token[upto:start],
+                                           token[start:]))
             if var_obj is None:
                 var, constant = match.group("var", "constant")
                 if constant:
@@ -482,7 +514,8 @@ class FilterExpression(object):
                     except VariableDoesNotExist:
                         var_obj = None
                 elif var is None:
-                    raise TemplateSyntaxError("Could not find variable at start of %s." % token)
+                    raise TemplateSyntaxError("Could not find variable at "
+                                              "start of %s." % token)
                 else:
                     var_obj = Variable(var)
             else:
@@ -498,7 +531,8 @@ class FilterExpression(object):
                 filters.append((filter_func, args))
             upto = match.end()
         if upto != len(token):
-            raise TemplateSyntaxError("Could not parse the remainder: '%s' from '%s'" % (token[upto:], token))
+            raise TemplateSyntaxError("Could not parse the remainder: '%s' "
+                                      "from '%s'" % (token[upto:], token))
 
         self.filters = filters
         self.var = var_obj
@@ -559,7 +593,8 @@ class FilterExpression(object):
                 provided.pop(0)
         except IndexError:
             # Not enough
-            raise TemplateSyntaxError("%s requires %d arguments, %d provided" % (name, len(nondefs), plen))
+            raise TemplateSyntaxError("%s requires %d arguments, %d provided" %
+                                      (name, len(nondefs), plen))
 
         # Defaults can be overridden.
         defaults = defaults and list(defaults) or []
@@ -568,7 +603,8 @@ class FilterExpression(object):
                 defaults.pop(0)
         except IndexError:
             # Too many.
-            raise TemplateSyntaxError("%s requires %d arguments, %d provided" % (name, len(nondefs), plen))
+            raise TemplateSyntaxError("%s requires %d arguments, %d provided" %
+                                      (name, len(nondefs), plen))
 
         return True
     args_check = staticmethod(args_check)
@@ -586,9 +622,9 @@ def resolve_variable(path, context):
     return Variable(path).resolve(context)
 
 class Variable(object):
-    r"""
-    A template variable, resolvable against a given context. The variable may be
-    a hard-coded string (if it begins and ends with single or double quote
+    """
+    A template variable, resolvable against a given context. The variable may
+    be a hard-coded string (if it begins and ends with single or double quote
     marks)::
 
         >>> c = {'article': {'section':u'News'}}
@@ -642,7 +678,9 @@ class Variable(object):
                 # Otherwise we'll set self.lookups so that resolve() knows we're
                 # dealing with a bonafide variable
                 if var.find(VARIABLE_ATTRIBUTE_SEPARATOR + '_') > -1 or var[0] == '_':
-                    raise TemplateSyntaxError("Variables and attributes may not begin with underscores: '%s'" % var)
+                    raise TemplateSyntaxError("Variables and attributes may "
+                                              "not begin with underscores: '%s'" %
+                                              var)
                 self.lookups = tuple(var.split(VARIABLE_ATTRIBUTE_SEPARATOR))
 
     def resolve(self, context):
@@ -673,22 +711,23 @@ class Variable(object):
         instead.
         """
         current = context
-        try: # catch-all for silent variable failures
+        try:  # catch-all for silent variable failures
             for bit in self.lookups:
-                try: # dictionary lookup
+                try:  # dictionary lookup
                     current = current[bit]
                 except (TypeError, AttributeError, KeyError):
-                    try: # attribute lookup
+                    try:  # attribute lookup
                         current = getattr(current, bit)
                     except (TypeError, AttributeError):
-                        try: # list-index lookup
+                        try:  # list-index lookup
                             current = current[int(bit)]
-                        except (IndexError, # list index out of range
-                                ValueError, # invalid literal for int()
-                                KeyError,   # current is a dict without `int(bit)` key
-                                TypeError,  # unsubscriptable object
-                                ):
-                            raise VariableDoesNotExist("Failed lookup for key [%s] in %r", (bit, current)) # missing attribute
+                        except (IndexError,  # list index out of range
+                                ValueError,  # invalid literal for int()
+                                KeyError,    # current is a dict without `int(bit)` key
+                                TypeError):  # unsubscriptable object
+                            raise VariableDoesNotExist("Failed lookup for key "
+                                                       "[%s] in %r",
+                                                       (bit, current))  # missing attribute
                 if callable(current):
                     if getattr(current, 'do_not_call_in_templates', False):
                         pass
@@ -700,7 +739,7 @@ class Variable(object):
                         except TypeError: # arguments *were* required
                             # GOTCHA: This will also catch any TypeError
                             # raised in the function itself.
-                            current = settings.TEMPLATE_STRING_IF_INVALID # invalid method call
+                            current = settings.TEMPLATE_STRING_IF_INVALID  # invalid method call
         except Exception, e:
             if getattr(e, 'silent_variable_failure', False):
                 current = settings.TEMPLATE_STRING_IF_INVALID
@@ -723,7 +762,10 @@ class Node(object):
         yield self
 
     def get_nodes_by_type(self, nodetype):
-        "Return a list of all nodes (within this node and its nodelist) of the given type"
+        """
+        Return a list of all nodes (within this node and its nodelist)
+        of the given type
+        """
         nodes = []
         if isinstance(self, nodetype):
             nodes.append(self)
@@ -776,7 +818,8 @@ def _render_value_in_context(value, context):
     """
     value = localize(value, use_l10n=context.use_l10n)
     value = force_unicode(value)
-    if (context.autoescape and not isinstance(value, SafeData)) or isinstance(value, EscapeData):
+    if ((context.autoescape and not isinstance(value, SafeData)) or
+            isinstance(value, EscapeData)):
         return escape(value)
     else:
         return value
@@ -793,23 +836,157 @@ class VariableNode(Node):
             output = self.filter_expression.resolve(context)
         except UnicodeDecodeError:
             # Unicode conversion can fail sometimes for reasons out of our
-            # control (e.g. exception rendering). In that case, we fail quietly.
+            # control (e.g. exception rendering). In that case, we fail
+            # quietly.
             return ''
         return _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:
-            message = "%s takes between %s and %s arguments" % (name, bmin, bmax)
-        raise TemplateSyntaxError(message)
-    return node_class(bits)
+            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:
+            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 +994,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,22 +1006,23 @@ 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
         else:
-            raise InvalidTemplateLibrary("Unsupported arguments to Library.tag: (%r, %r)", (name, compile_function))
+            raise InvalidTemplateLibrary("Unsupported arguments to "
+                "Library.tag: (%r, %r)", (name, compile_function))
 
-    def tag_function(self,func):
+    def tag_function(self, func):
         self.tags[getattr(func, "_decorated_function", func).__name__] = func
         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,12 +1031,13 @@ 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
         else:
-            raise InvalidTemplateLibrary("Unsupported arguments to Library.filter: (%r, %r)", (name, filter_func))
+            raise InvalidTemplateLibrary("Unsupported arguments to "
+                "Library.filter: (%r, %r)", (name, filter_func))
 
     def filter_function(self, func):
         self.filters[getattr(func, "_decorated_function", func).__name__] = func
@@ -866,27 +1045,20 @@ 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)
-
-            function_name = name or getattr(func, '_decorated_function', func).__name__
-            compile_func = partial(generic_tag_compiler, params, defaults, function_name, SimpleNode)
+                    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=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 +1074,33 @@ 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 +1116,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,21 +1133,26 @@ 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,
                     })
-                    # Copy across the CSRF token, if present, because inclusion
-                    # tags are often used for forms, and we need instructions
-                    # for using CSRF protection to be as simple as possible.
+                    # Copy across the CSRF token, if present, because
+                    # inclusion tags are often used for forms, and we need
+                    # instructions for using CSRF protection to be as simple
+                    # as possible.
                     csrf_token = context.get('csrf_token', None)
                     if csrf_token is not None:
                         new_context['csrf_token'] = csrf_token
                     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)
+            function_name = (name or
+                getattr(func, '_decorated_function', func).__name__)
+            compile_func = partial(generic_tag_compiler,
+                params=params, varargs=varargs, varkw=varkw,
+                defaults=defaults, name=function_name,
+                takes_context=takes_context, node_class=InclusionNode)
             compile_func.__doc__ = func.__doc__
             self.tag(function_name, compile_func)
             return func
@@ -1018,22 +1164,26 @@ def import_library(taglib_module):
     Verifies that the library contains a 'register' attribute, and
     returns that attribute as the representation of the library
     """
-    app_path, taglib = taglib_module.rsplit('.',1)
+    app_path, taglib = taglib_module.rsplit('.', 1)
     app_module = import_module(app_path)
     try:
         mod = import_module(taglib_module)
     except ImportError, e:
-        # If the ImportError is because the taglib submodule does not exist, that's not
-        # an error that should be raised. If the submodule exists and raised an ImportError
-        # on the attempt to load it, that we want to raise.
+        # If the ImportError is because the taglib submodule does not exist,
+        # that's not an error that should be raised. If the submodule exists
+        # and raised an ImportError on the attempt to load it, that we want
+        # to raise.
         if not module_has_submodule(app_module, taglib):
             return None
         else:
-            raise InvalidTemplateLibrary("ImportError raised loading %s: %s" % (taglib_module, e))
+            raise InvalidTemplateLibrary("ImportError raised loading %s: %s" %
+                                         (taglib_module, e))
     try:
         return mod.register
     except AttributeError:
-        raise InvalidTemplateLibrary("Template library %s does not have a variable named 'register'" % taglib_module)
+        raise InvalidTemplateLibrary("Template library %s does not have "
+                                     "a variable named 'register'" %
+                                     taglib_module)
 
 templatetags_modules = []
 
@@ -1045,9 +1195,9 @@ def get_templatetags_modules():
     global templatetags_modules
     if not templatetags_modules:
         _templatetags_modules = []
-        # Populate list once per process. Mutate the local list first, and then
-        # assign it to the global name to ensure there are no cases where two
-        # threads try to populate it simultaneously.
+        # Populate list once per process. Mutate the local list first, and
+        # then assign it to the global name to ensure there are no cases where
+        # two threads try to populate it simultaneously.
         for app_module in ['django'] + list(settings.INSTALLED_APPS):
             try:
                 templatetag_module = '%s.templatetags' % app_module
@@ -1062,12 +1212,13 @@ def get_library(library_name):
     """
     Load the template library module with the given name.
 
-    If library is not already loaded loop over all templatetags modules to locate it.
+    If library is not already loaded loop over all templatetags modules
+    to locate it.
 
     {% load somelib %} and {% load someotherlib %} loops twice.
 
-    Subsequent loads eg. {% load somelib %} in the same process will grab the cached
-    module from libraries.
+    Subsequent loads eg. {% load somelib %} in the same process will grab
+    the cached module from libraries.
     """
     lib = libraries.get(library_name, None)
     if not lib:
@@ -1081,11 +1232,16 @@ def get_library(library_name):
                 libraries[library_name] = lib
                 break
         if not lib:
-            raise InvalidTemplateLibrary("Template library %s not found, tried %s" % (library_name, ','.join(tried_modules)))
+            raise InvalidTemplateLibrary("Template library %s not found, "
+                                         "tried %s" %
+                                         (library_name,
+                                          ','.join(tried_modules)))
     return lib
 
+
 def add_to_builtins(module):
     builtins.append(import_library(module))
 
+
 add_to_builtins('django.template.defaulttags')
 add_to_builtins('django.template.defaultfilters')
diff --git a/django/template/defaulttags.py b/django/template/defaulttags.py
index 0b039a5..9620d1c 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 9cf268a..ad241d9 100644
--- a/docs/releases/1.4.txt
+++ b/docs/releases/1.4.txt
@@ -291,6 +291,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
