=== django/contrib/admin/templatetags/admin_list.py
==================================================================
--- django/contrib/admin/templatetags/admin_list.py	(revision 2312)
+++ django/contrib/admin/templatetags/admin_list.py	(local)
@@ -65,7 +65,7 @@
         'ALL_VAR': ALL_VAR,
         '1': 1,
     }
-pagination = register.inclusion_tag('admin/pagination')(pagination)
+pagination = register.inclusion_tag(pagination, 'admin/pagination')
 
 def result_headers(cl):
     lookup_opts = cl.lookup_opts
@@ -185,7 +185,7 @@
     return {'cl': cl,
             'result_headers': list(result_headers(cl)),
             'results': list(results(cl))}
-result_list = register.inclusion_tag("admin/change_list_results")(result_list)
+result_list = register.inclusion_tag(result_list, 'admin/change_list_results')
 
 def date_hierarchy(cl):
     if cl.lookup_opts.admin.date_hierarchy:
@@ -245,7 +245,7 @@
                     'title': year.year
                 } for year in years]
             }
-date_hierarchy = register.inclusion_tag('admin/date_hierarchy')(date_hierarchy)
+date_hierarchy = register.inclusion_tag(date_hierarchy, 'admin/date_hierarchy')
 
 def search_form(cl):
     return {
@@ -253,12 +253,12 @@
         'show_result_count': cl.result_count != cl.full_result_count and not cl.opts.one_to_one_field,
         'search_var': SEARCH_VAR
     }
-search_form = register.inclusion_tag('admin/search_form')(search_form)
+search_form = register.inclusion_tag(search_form, 'admin/search_form')
 
 def filter(cl, spec):
     return {'title': spec.title(), 'choices' : list(spec.choices(cl))}
-filter = register.inclusion_tag('admin/filter')(filter)
+filter = register.inclusion_tag(filter, 'admin/filter')
 
 def filters(cl):
     return {'cl': cl}
-filters = register.inclusion_tag('admin/filters')(filters)
+filters = register.inclusion_tag(filters, 'admin/filters')
=== django/contrib/admin/templatetags/admin_modify.py
==================================================================
--- django/contrib/admin/templatetags/admin_modify.py	(revision 2312)
+++ django/contrib/admin/templatetags/admin_modify.py	(local)
@@ -22,9 +22,9 @@
 include_admin_script = register.simple_tag(include_admin_script)
 
 def submit_row(context):
-    opts = context['opts']
-    change = context['change']
-    is_popup = context['is_popup']
+    opts = context.get('opts')
+    change = context.get('change')
+    is_popup = context.get('is_popup')
     return {
         'onclick_attrib': (opts.get_ordered_objects() and change
                             and 'onclick="submitOrderForm();"' or ''),
@@ -35,7 +35,7 @@
         'show_save_and_continue': not is_popup,
         'show_save': True
     }
-submit_row = register.inclusion_tag('admin/submit_line', takes_context=True)(submit_row)
+submit_row = register.inclusion_tag(submit_row, 'admin/submit_line', takes_context=True)
 
 def field_label(bound_field):
     class_names = []
@@ -229,4 +229,4 @@
         'bound_fields': bound_fields,
         'class_names': " ".join(class_names),
     }
-admin_field_line = register.inclusion_tag('admin/field_line', takes_context=True)(admin_field_line)
+admin_field_line = register.inclusion_tag(admin_field_line, 'admin/field_line', takes_context=True)
=== django/contrib/admin/templatetags/adminapplist.py
==================================================================
--- django/contrib/admin/templatetags/adminapplist.py	(revision 2312)
+++ django/contrib/admin/templatetags/adminapplist.py	(local)
@@ -61,4 +61,4 @@
         raise template.TemplateSyntaxError, "First argument to '%s' tag must be 'as'" % tokens[0]
     return AdminApplistNode(tokens[2])
 
-register.tag('get_admin_app_list', get_admin_app_list)
+register.tag(get_admin_app_list, name='get_admin_app_list')
=== django/contrib/admin/templatetags/log.py
==================================================================
--- django/contrib/admin/templatetags/log.py	(revision 2312)
+++ django/contrib/admin/templatetags/log.py	(local)
@@ -50,4 +50,4 @@
                 raise template.TemplateSyntaxError, "Fourth argument in '%s' must be 'for_user'" % self.tag_name
         return AdminLogNode(limit=tokens[1], varname=tokens[3], user=(len(tokens) > 5 and tokens[5] or None))
 
-register.tag('get_admin_log', DoGetAdminLog('get_admin_log'))
+register.tag(DoGetAdminLog('get_admin_log'), name='get_admin_log')
=== django/contrib/admin/views/template.py
==================================================================
--- django/contrib/admin/views/template.py	(revision 2312)
+++ django/contrib/admin/views/template.py	(local)
@@ -56,7 +56,7 @@
             node.template_dirs = settings_module.TEMPLATE_DIRS
             return node
         register = template.Library()
-        register.tag('extends', new_do_extends)
+        register.tag(new_do_extends, name='extends')
         template.builtins.append(register)
 
         # Now validate the template using the new template dirs
=== django/contrib/comments/templatetags/comments.py
==================================================================
--- django/contrib/comments/templatetags/comments.py	(revision 2312)
+++ django/contrib/comments/templatetags/comments.py	(local)
@@ -315,10 +315,10 @@
         return CommentListNode(package, module, var_name, obj_id, tokens[5], self.free, ordering)
 
 # registration comments
-register.tag('get_comment_list', DoGetCommentList(False))
-register.tag('comment_form', DoCommentForm(False))
-register.tag('get_comment_count', DoCommentCount(False))
+register.tag(DoGetCommentList(False), name='get_comment_list')
+register.tag(DoCommentForm(False), name='comment_form')
+register.tag(DoCommentCount(False), name='get_comment_count')
 # free comments
-register.tag('get_free_comment_list', DoGetCommentList(True))
-register.tag('free_comment_form', DoCommentForm(True))
-register.tag('get_free_comment_count', DoCommentCount(True))
+register.tag(DoGetCommentList(True), name='get_free_comment_list')
+register.tag(DoCommentForm(True), name='free_comment_form')
+register.tag(DoCommentCount(True), name='get_free_comment_count')
=== django/template/__init__.py
==================================================================
--- django/template/__init__.py	(revision 2312)
+++ django/template/__init__.py	(local)
@@ -54,6 +54,7 @@
 >>> t.render(c)
 '\n<html>\n\n</html>\n'
 """
+import keyword
 import re
 from inspect import getargspec
 from django.utils.functional import curry
@@ -76,6 +77,7 @@
 VARIABLE_TAG_END = '}}'
 
 ALLOWED_VARIABLE_CHARS = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_.'
+BOOLEANS = ['False', 'True']
 
 # what to report as the origin for templates that come from non-loader sources
 # (e.g. strings)
@@ -227,7 +229,7 @@
             elif token.token_type == TOKEN_VAR:
                 if not token.contents:
                     self.empty_variable(token)
-                filter_expression = self.compile_filter(token.contents)
+                filter_expression = self.compile_filter(token.contents, default=settings.TEMPLATE_STRING_IF_INVALID)
                 var_node = self.create_variable_node(filter_expression)
                 self.extend_nodelist(nodelist, var_node,token)
             elif token.token_type == TOKEN_BLOCK:
@@ -309,9 +311,9 @@
         self.tags.update(lib.tags)
         self.filters.update(lib.filters)
 
-    def compile_filter(self,token):
+    def compile_filter(self, token, default=None):
         "Convenient wrapper for FilterExpression"
-        return FilterExpression(token, self)
+        return FilterExpression(token, self, default)
 
     def find_filter(self, filter_name):
         if self.filters.has_key(filter_name):
@@ -496,13 +498,13 @@
     This class should never be instantiated outside of the
     get_filters_from_token helper function.
     """
-    def __init__(self, token, parser):
+    def __init__(self, token, parser, default=None):
         self.token = token
-        matches = filter_re.finditer(token)
+        self.default = default
         var = None
         filters = []
         upto = 0
-        for match in matches:
+        for match in filter_re.finditer(token):
             start = match.start()
             if upto != start:
                 raise TemplateSyntaxError, "Could not parse some characters: %s|%s|%s"  % \
@@ -534,13 +536,13 @@
                 upto = match.end()
         if upto != len(token):
             raise TemplateSyntaxError, "Could not parse the remainder: %s" % token[upto:]
-        self.var , self.filters = var, filters
+        self.var, self.filters = var, filters
 
     def resolve(self, context):
         try:
             obj = resolve_variable(self.var, context)
         except VariableDoesNotExist:
-            obj = settings.TEMPLATE_STRING_IF_INVALID
+            return self.default
         for func, args in self.filters:
             arg_vals = []
             for lookup, arg in args:
@@ -588,7 +590,8 @@
     """
     Returns the resolved variable, which may contain attribute syntax, within
     the given context. The variable may be a hard-coded string (if it begins
-    and ends with single or double quote marks).
+    and ends with single or double quote marks), a boolean literal, or an
+    integer or float literal.
 
     >>> c = {'article': {'section':'News'}}
     >>> resolve_variable('article.section', c)
@@ -604,46 +607,110 @@
 
     (The example assumes VARIABLE_ATTRIBUTE_SEPARATOR is '.')
     """
-    if path[0] in '0123456789':
+    if path[0].isdigit():
         number_type = '.' in path and float or int
-        try:
-           current = number_type(path)
-        except ValueError:
-           current = settings.TEMPLATE_STRING_IF_INVALID
-    elif path[0] in ('"', "'") and path[0] == path[-1]:
-        current = path[1:-1]
-    else:
-        current = context
-        bits = path.split(VARIABLE_ATTRIBUTE_SEPARATOR)
-        while bits:
-            try: # dictionary lookup
-                current = current[bits[0]]
-            except (TypeError, AttributeError, KeyError):
-                try: # attribute lookup
-                    current = getattr(current, bits[0])
-                    if callable(current):
-                        if getattr(current, 'alters_data', False):
-                            current = settings.TEMPLATE_STRING_IF_INVALID
-                        else:
-                            try: # method call (assuming no args required)
-                                current = current()
-                            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
-                            except Exception, e:
-                                if getattr(e, 'silent_variable_failure', False):
-                                    current = settings.TEMPLATE_STRING_IF_INVALID
-                                else:
-                                    raise
-                except (TypeError, AttributeError):
-                    try: # list-index lookup
-                        current = current[int(bits[0])]
-                    except (IndexError, ValueError, KeyError):
-                        raise VariableDoesNotExist, "Failed lookup for key [%s] in %r" % (bits[0], current) # missing attribute
-            del bits[0]
-    return current
+        return number_type(path)
+    if path in BOOLEANS:
+        return bool(BOOLEANS.index(path))
+    if path[0] in ('"', "'"):
+        if path[0] == path[-1]:
+            return path[1:-1]
+        raise VariableDoesNotExist, "Missing quotes in string literal %s" % (path,)
+    for bit in path.split(VARIABLE_ATTRIBUTE_SEPARATOR):
+        try: # dictionary lookup
+            context = context[bit]
+        except (TypeError, AttributeError, KeyError):
+            try: # attribute lookup
+                context = getattr(context, bit)
+            except (TypeError, AttributeError):
+                try: # list-index lookup
+                    context = context[int(bit)]
+                except (IndexError, ValueError, KeyError):
+                    raise VariableDoesNotExist, "Failed lookup for key [%s] in %r" % (bit, context) # missing attribute
+            else:
+                if callable(context):
+                    if getattr(context, 'alters_data', False):
+                        raise VariableDoesNotExist("Failed lookup for key [%s] in %r" % (bit, context))
+                    try: # method call (assuming no args required)
+                        context = context()
+                    except Exception, e:
+                        if getattr(e, 'silent_variable_failure', False):
+                            raise VariableDoesNotExist, "Failed lookup for key [%s] in %r" % (bit, context) # missing attribute
+                        raise
+    return context
 
+tag_tokens_re = re.compile(r"""
+(?:\s*)                           # ignore preceeding whitespace
+(                                 # token group
+    (?:[A-Za-z_][0-9A-Za-z_]*=)?" # optional name and open double-quote
+    [^"\\]*                       # normal characters
+    (?:\\.                        # chew up backslash and following char
+        (?:|[^"\\])*              # normal characters
+    )*                            # repeat as needed to chew up the backslashes
+    "                             # close double-quote
+|
+    (?:[A-Za-z_][0-9A-Za-z_]*=)?' # optional name and open single-quote
+    [^'\\]*                       # normal characters
+    (?:\\.                        # chew up backslash and following char
+        (?:|[^'\\])*              # normal characters
+    )*                            # repeat as needed to chew up the backslashes
+    '                             # close single-quote
+|
+    (?:[A-Za-z_][0-9A-Za-z_]*=)?  # optional name
+    [^"'\s][^\s]*                 # unquoted token
+)
+(?:\s+|$)                         # chew up following ws until next token
+""", re.VERBOSE)
+
+def tokenize_tag_contents(contents):
+
+    """ Parse tag contents into (tag_name, args_to_resolve, kwds_to_resolve).
+    If an argument name is a Python keyword append an underscore.
+
+    >>> tokenize_tag_contents("An 'easy' one.")
+    ('An', ["'easy'", 'one.'], {})
+    >>> tokenize_tag_contents(r'''Please don't try 'thi\\'s at' named="hom\\"e."''')
+    ('Please', ["don't", 'try', "'thi\'s at'"], {'named': '"hom\\\\"e."'})
+    """
+
+    bits = []
+    i, n = 0, len(contents)
+    while i < n:
+        m = tag_tokens_re.match(contents, i)
+        if not m:
+            raise ValueError("Can't parse string %s at index %s" %(repr(contents), i))
+        bit = m.group(1)
+        if bit.startswith('"'):
+            bit = bit.replace('\\"', '"')
+        elif bit.startswith("'"):
+            bit = bit.replace("\\'", "'")
+        bits.append(bit)
+        i = m.end()
+    tag_name = bits[0]
+    args_to_resolve = []
+    kwds_to_resolve = {}
+    for bit in bits[1:]:
+        if bit[0] in ("'", '"'):
+            if kwds_to_resolve:
+                raise SyntaxError("template tag syntax error: non-keyword arguments must come before keyword arguments")
+            args_to_resolve.append(bit)
+        else:
+            try:
+                name, value = bit.split('=', 1)
+                if keyword.iskeyword(name):
+                    name += '_'
+                kwds_to_resolve[name] = value
+            except ValueError:
+                if kwds_to_resolve:
+                    raise SyntaxError("template tag syntax error: non-keyword arguments must come before keyword arguments")
+                args_to_resolve.append(bit)
+    return tag_name, args_to_resolve, kwds_to_resolve
+
+def resolve_tag_arguments(context, args_to_resolve, kwds_to_resolve):
+    resolved_args = [resolve_variable(var, context) for var in args_to_resolve]
+    resolved_kwds = dict([(name, resolve_variable(var, context)) for name, var in kwds_to_resolve.items()])
+    return resolved_args, resolved_kwds
+
 class Node:
     def render(self, context):
         "Return the node rendered as a string"
@@ -737,122 +804,105 @@
             raise
         return self.encode_output(output)
 
-def generic_tag_compiler(params, defaults, name, node_class, parser, token):
-    "Returns a template.Node subclass."
-    bits = token.contents.split()[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)
-        else:
-            message = "%s takes between %s and %s arguments" % (name, bmin, bmax)
-        raise TemplateSyntaxError, message
-    return node_class(bits)
+class SimpleNode(Node):
+    def __init__(self, func, takes_context, block_nodelist, args_to_resolve, kwds_to_resolve):
+        self.func = func
+        self.takes_context = takes_context
+        self.block_nodelist = block_nodelist
+        self.args_to_resolve = args_to_resolve
+        self.kwds_to_resolve = kwds_to_resolve
 
-class Library(object):
-    def __init__(self):
-        self.filters = {}
-        self.tags = {}
+    def render(self, context):
+        resolved_args, resolved_kwds = resolve_tag_arguments(context, self.args_to_resolve, self.kwds_to_resolve)
+        if self.block_nodelist:
+            resolved_args.insert(0, self.block_nodelist)
+        if self.takes_context:
+            resolved_args.insert(0, context)
+        return self.func(*resolved_args, **resolved_kwds) or ''
 
-    def tag(self, name=None, compile_function=None):
-        if name == None and compile_function == None:
-            # @register.tag()
-            return self.tag_function
-        elif name != None and compile_function == None:
-            if(callable(name)):
-                # @register.tag
-                return self.tag_function(name)
-            else:
-                # @register.tag('somename') or @register.tag(name='somename')
-                def dec(func):
-                    return self.tag(name, func)
-                return dec
-        elif name != None and compile_function != 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)
+class SimpleNodeFactory(object):
+    def __init__(self, func, takes_context=False, takes_block=False):
+        self.func = func
+        self.__doc__ = func.__doc__
+        self.takes_context = takes_context
+        self.takes_block = takes_block
 
-    def tag_function(self,func):
-        self.tags[func.__name__] = func
-        return func
+    def __call__(self, parser, token):
+        tag_name, args_to_resolve, kwds_to_resolve = tokenize_tag_contents(token.contents)
+        block_nodelist = None
+        if self.takes_block:
+            block_nodelist = parser.parse(('end' +  tag_name,))
+            parser.delete_first_token()
+        return SimpleNode(self.func, self.takes_context, block_nodelist, args_to_resolve, kwds_to_resolve)
 
-    def filter(self, name=None, filter_func=None):
-        if name == None and filter_func == None:
-            # @register.filter()
-            return self.filter_function
-        elif filter_func == None:
-            if(callable(name)):
-                # @register.filter
-                return self.filter_function(name)
-            else:
-                # @register.filter('somename') or @register.filter(name='somename')
-                def dec(func):
-                    return self.filter(name, func)
-                return dec
-        elif name != None and filter_func != None:
-            # register.filter('somename', somefunc)
-            self.filters[name] = filter_func
-            return filter_func
-        else:
-            raise InvalidTemplateLibrary, "Unsupported arguments to Library.filter: (%r, %r, %r)", (name, compile_function, has_arg)
+class InclusionNode(Node):
+    def __init__(self, func, template, context_class, takes_context, args_to_resolve, kwds_to_resolve):
+        self.func = func
+        self.template = template
+        self.context_class = context_class
+        self.takes_context = takes_context
+        self.args_to_resolve = args_to_resolve
+        self.kwds_to_resolve = kwds_to_resolve
 
-    def filter_function(self, func):
-        self.filters[func.__name__] = func
-        return func
+    def render(self, context):
+        resolved_args, resolved_kwds = resolve_tag_arguments(context, self.args_to_resolve, self.kwds_to_resolve)
+        if self.takes_context:
+            resolved_args.insert(0, context)
+        if not getattr(self, 'nodelist', None):
+            from django.template.loader import get_template
+            t = get_template(self.template)
+            self.nodelist = t.nodelist
+        context = self.context_class(self.func(*resolved_args, **resolved_kwds))
+        return self.nodelist.render(context)
 
-    def simple_tag(self,func):
-        (params, xx, xxx, defaults) = getargspec(func)
+class InclusionNodeFactory(object):
+    def __init__(self, func, template, context_class=Context, takes_context=False):
+        self.func = func
+        self.__doc__ = func.__doc__
+        self.template = template
+        self.context_class = context_class
+        self.takes_context = takes_context
 
-        class SimpleNode(Node):
-            def __init__(self, vars_to_resolve):
-                self.vars_to_resolve = vars_to_resolve
+    def __call__(self, parser, token):
+        tag_name, args_to_resolve, kwds_to_resolve = tokenize_tag_contents(token.contents)
+        return InclusionNode(self.func, self.template, self.context_class, self.takes_context, args_to_resolve, kwds_to_resolve)
 
-            def render(self, context):
-                resolved_vars = [resolve_variable(var, context) for var in self.vars_to_resolve]
-                return func(*resolved_vars)
+class Library(object):
+    def __init__(self):
+        self.filters = {}
+        self.tags = {}
 
-        compile_func = curry(generic_tag_compiler, params, defaults, func.__name__, SimpleNode)
-        compile_func.__doc__ = func.__doc__
-        self.tag(func.__name__, compile_func)
-        return func
+    def tag(self, func=None, name=None):
+        if callable(func):
+            self.tags[name or func.__name__] = func
+            return func
+        elif func is None:
+            return curry(self.tag, name=name)
+        raise InvalidTemplateLibrary("Unsupported argument to Library.tag: (%r)" % (func,))
 
-    def inclusion_tag(self, file_name, context_class=Context, takes_context=False):
-        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'"
+    def filter(self, func=None, name=None):
+        if callable(func):
+            self.filters[name or func.__name__] = func
+            return func
+        elif func is None:
+            return curry(self.filter, name=name)
+        raise InvalidTemplateLibrary("Unsupported argument to Library.filter: (%r)" % (func,))
 
-            class InclusionNode(Node):
-                def __init__(self, vars_to_resolve):
-                    self.vars_to_resolve = vars_to_resolve
+    def simple_tag(self, func=None, takes_context=False, takes_block=False, name=None):
+        if callable(func):
+            self.tags[name or func.__name__] = SimpleNodeFactory(func, takes_context, takes_block)
+            return func
+        elif func is None:
+            return curry(self.simple_tag, takes_context=takes_context, takes_block=takes_block, name=name)
+        raise InvalidTemplateLibrary("Unsupported argument to Library.simple_tag: (%r)" % (func,))
 
-                def render(self, context):
-                    resolved_vars = [resolve_variable(var, context) for var in self.vars_to_resolve]
-                    if takes_context:
-                        args = [context] + resolved_vars
-                    else:
-                        args = resolved_vars
-
-                    dict = func(*args)
-
-                    if not getattr(self, 'nodelist', False):
-                        from django.template.loader import get_template
-                        t = get_template(file_name)
-                        self.nodelist = t.nodelist
-                    return self.nodelist.render(context_class(dict))
-
-            compile_func = curry(generic_tag_compiler, params, defaults, func.__name__, InclusionNode)
-            compile_func.__doc__ = func.__doc__
-            self.tag(func.__name__, compile_func)
+    def inclusion_tag(self, func=None, template=None, context_class=Context, takes_context=False, name=None):
+        if callable(func):
+            self.tags[name or func.__name__] = InclusionNodeFactory(func, template, context_class, takes_context)
             return func
-        return dec
+        elif func is None:
+            return curry(self.inclusion_tag, template=template, context_class=context_class, takes_context=takes_context, name=name)
+        raise InvalidTemplateLibrary("Unsupported argument to Library.inclusion_tag: (%r)" % (func,))
 
 def get_library(module_name):
     lib = libraries.get(module_name, None)
=== django/template/context.py
==================================================================
--- django/template/context.py	(revision 2312)
+++ django/template/context.py	(local)
@@ -33,7 +33,7 @@
         for dict in self.dicts:
             if dict.has_key(key):
                 return dict[key]
-        return settings.TEMPLATE_STRING_IF_INVALID
+        raise KeyError("Context key not found: %s" % key)
 
     def __delitem__(self, key):
         "Delete a variable from the current context"
@@ -45,7 +45,7 @@
                 return True
         return False
 
-    def get(self, key, otherwise):
+    def get(self, key, otherwise=None):
         for dict in self.dicts:
             if dict.has_key(key):
                 return dict[key]
=== django/template/defaultfilters.py
==================================================================
--- django/template/defaultfilters.py	(revision 2312)
+++ django/template/defaultfilters.py	(local)
@@ -469,7 +469,7 @@
 register.filter(removetags)
 register.filter(random)
 register.filter(rjust)
-register.filter('slice', slice_)
+register.filter(slice_, name='slice')
 register.filter(slugify)
 register.filter(stringformat)
 register.filter(striptags)
=== django/template/defaulttags.py
==================================================================
--- django/template/defaulttags.py	(revision 2312)
+++ django/template/defaulttags.py	(local)
@@ -45,7 +45,10 @@
 
     def render(self, context):
         for var in self.vars:
-            value = resolve_variable(var, context)
+            try:
+                value = resolve_variable(var, context)
+            except VariableDoesNotExist:
+                continue
             if value:
                 return str(value)
         return ''
@@ -142,8 +145,14 @@
         return "<IfEqualNode>"
 
     def render(self, context):
-        val1 = resolve_variable(self.var1, context)
-        val2 = resolve_variable(self.var2, context)
+        try:
+            val1 = resolve_variable(self.var1, context)
+        except VariableDoesNotExist:
+            val1 = None
+        try:
+            val2 = resolve_variable(self.var2, context)
+        except VariableDoesNotExist:
+            val2 = None
         if (self.negate and val1 != val2) or (not self.negate and val1 == val2):
             return self.nodelist_true.render(context)
         return self.nodelist_false.render(context)
@@ -187,7 +196,7 @@
 
     def render(self, context):
         obj_list = self.target.resolve(context)
-        if obj_list == '': # target_var wasn't found in context; fail silently
+        if obj_list is None:
             context[self.var_name] = []
             return ''
         output = [] # list of dictionaries in the format {'grouper': 'key', 'list': [list of contents]}
@@ -391,7 +400,7 @@
     nodelist = parser.parse(('endfilter',))
     parser.delete_first_token()
     return FilterNode(filter_expr, nodelist)
-filter = register.tag("filter", do_filter)
+filter = register.tag(do_filter, name='filter')
 
 #@register.tag
 def firstof(parser, token):
@@ -469,7 +478,7 @@
     nodelist_loop = parser.parse(('endfor',))
     parser.delete_first_token()
     return ForNode(loopvar, sequence, reversed, nodelist_loop)
-do_for = register.tag("for", do_for)
+do_for = register.tag(do_for, name='for')
 
 def do_ifequal(parser, token, negate):
     """
@@ -579,7 +588,7 @@
     else:
         nodelist_false = NodeList()
     return IfNode(boolvars, nodelist_true, nodelist_false)
-do_if = register.tag("if", do_if)
+do_if = register.tag(do_if, name='if')
 
 #@register.tag
 def ifchanged(parser, token):
=== django/template/loader_tags.py
==================================================================
--- django/template/loader_tags.py	(revision 2312)
+++ django/template/loader_tags.py	(local)
@@ -167,6 +167,6 @@
         return ConstantIncludeNode(path[1:-1])
     return IncludeNode(bits[1])
 
-register.tag('block', do_block)
-register.tag('extends', do_extends)
-register.tag('include', do_include)
+register.tag(do_block, name='block')
+register.tag(do_extends, name='extends')
+register.tag(do_include, name='include')
=== django/templatetags/i18n.py
==================================================================
--- django/templatetags/i18n.py	(revision 2312)
+++ django/templatetags/i18n.py	(local)
@@ -215,7 +215,7 @@
 
     return BlockTranslateNode(extra_context, singular, plural, countervar, counter)
 
-register.tag('get_available_languages', do_get_available_languages)
-register.tag('get_current_language', do_get_current_language)
-register.tag('trans', do_translate)
-register.tag('blocktrans', do_block_translate)
+register.tag(do_get_available_languages, name='get_available_languages')
+register.tag(do_get_current_language, name='get_current_language')
+register.tag(do_translate, name='trans')
+register.tag(do_block_translate, name='blocktrans')
=== docs/templates_python.txt
==================================================================
--- docs/templates_python.txt	(revision 2312)
+++ docs/templates_python.txt	(local)
@@ -566,35 +566,36 @@
 Most filters don't take arguments. In this case, just leave the argument out of
 your function. Example::
 
-    def lower(value): # Only one argument.
+    def do_lower(value): # Only one argument.
         "Converts a string into all lowercase"
         return value.lower()
 
 When you've written your filter definition, you need to register it with
 your ``Library`` instance, to make it available to Django's template language::
 
-    register.filter('cut', cut)
-    register.filter('lower', lower)
+    register.filter(cut)
+    register.filter(do_lower, name='lower')
 
-The ``Library.filter()`` method takes two arguments:
+The ``Library.filter()`` method takes the following arguments:
 
-    1. The name of the filter -- a string.
-    2. The compilation function -- a Python function (not the name of the
+    1. The filter function -- a Python function (not the name of the
        function as a string).
+    2. An optional alternative name for the filter -- a string).  By default
+       it is the same as the function name.
 
 If you're using Python 2.4 or above, you can use ``register.filter()`` as a
 decorator instead::
 
-    @register.filter(name='cut')
+    @register.filter
     def cut(value, arg):
         return value.replace(arg, '')
 
-    @register.filter
-    def lower(value):
+    @register.filter(name=lower)
+    def do_lower(value):
         return value.lower()
 
-If you leave off the ``name`` argument, as in the second example above, Django
-will use the function's name as the filter name.
+If you use the filter decorator without the ``name`` argument, as in the first
+example above, Django will use the function's name as the filter name.
 
 Writing custom template tags
 ----------------------------
@@ -714,14 +715,14 @@
 Finally, register the tag with your module's ``Library`` instance, as explained
 in "Writing custom template filters" above. Example::
 
-    register.tag('current_time', do_current_time)
+    register.tag(do_current_time, name='current_time')
 
 The ``tag()`` method takes two arguments:
 
-    1. The name of the template tag -- a string. If this is left out, the
-       name of the compilation function will be used.
-    2. The compilation function -- a Python function (not the name of the
+    1. The tag function -- a Python function (not the name of the
        function as a string).
+    2. An optional alternative name for the template tag -- a string. If this
+       is left out, the name of the tag function will be used.
 
 As with filter registration, it is also possible to use this as a decorator, in
 Python 2.4 and above::
=== tests/othertests/templates.py
==================================================================
--- tests/othertests/templates.py	(revision 2312)
+++ tests/othertests/templates.py	(local)
@@ -1,6 +1,6 @@
 from django.conf import settings
+settings.TEMPLATE_STRING_IF_INVALID = "[undefined]"
 
-
 from django import template
 from django.template import loader
 from django.utils.translation import activate, deactivate, install
@@ -22,7 +22,7 @@
 def do_echo(parser, token):
     return EchoNode(token.contents.split()[1:])
 
-register.tag("echo", do_echo)
+register.tag(do_echo, name='echo')
 
 template.libraries['django.templatetags.testtags'] = register
 
@@ -72,7 +72,7 @@
     'basic-syntax03': ("{{ first }} --- {{ second }}", {"first" : 1, "second" : 2}, "1 --- 2"),
 
     # Fail silently when a variable is not found in the current context
-    'basic-syntax04': ("as{{ missing }}df", {}, "asdf"),
+    'basic-syntax04': ("as{{ missing }}df", {}, "as%sdf" % settings.TEMPLATE_STRING_IF_INVALID),
 
     # A variable may not contain more than one word
     'basic-syntax06': ("{{ multi word variable }}", {}, template.TemplateSyntaxError),
@@ -88,7 +88,7 @@
     'basic-syntax10': ("{{ var.otherclass.method }}", {"var": SomeClass()}, "OtherClass.method"),
 
     # Fail silently when a variable's attribute isn't found
-    'basic-syntax11': ("{{ var.blech }}", {"var": SomeClass()}, ""),
+    'basic-syntax11': ("{{ var.blech }}", {"var": SomeClass()}, "%s" % settings.TEMPLATE_STRING_IF_INVALID),
 
     # Raise TemplateSyntaxError when trying to access a variable beginning with an underscore
     'basic-syntax12': ("{{ var.__dict__ }}", {"var": SomeClass()}, template.TemplateSyntaxError),
@@ -104,10 +104,10 @@
     'basic-syntax18': ("{{ foo.bar }}", {"foo" : {"bar" : "baz"}}, "baz"),
 
     # Fail silently when a variable's dictionary key isn't found
-    'basic-syntax19': ("{{ foo.spam }}", {"foo" : {"bar" : "baz"}}, ""),
+    'basic-syntax19': ("{{ foo.spam }}", {"foo" : {"bar" : "baz"}}, "%s" % settings.TEMPLATE_STRING_IF_INVALID),
 
     # Fail silently when accessing a non-simple method
-    'basic-syntax20': ("{{ var.method2 }}", {"var": SomeClass()}, ""),
+    'basic-syntax20': ("{{ var.method2 }}", {"var": SomeClass()}, "%s" % settings.TEMPLATE_STRING_IF_INVALID),
 
     # Basic filter usage
     'basic-syntax21': ("{{ var|upper }}", {"var": "Django is the greatest!"}, "DJANGO IS THE GREATEST!"),
@@ -146,7 +146,7 @@
     'basic-syntax32': (r'{{ var|yesno:"yup,nup,mup" }} {{ var|yesno }}', {"var": True}, 'yup yes'),
 
     # Fail silently for methods that raise an exception with a "silent_variable_failure" attribute
-    'basic-syntax33': (r'1{{ var.method3 }}2', {"var": SomeClass()}, "12"),
+    'basic-syntax33': (r'1{{ var.method3 }}2', {"var": SomeClass()}, "1%s2" % settings.TEMPLATE_STRING_IF_INVALID),
 
     # In methods that raise an exception without a "silent_variable_attribute" set to True,
     # the exception propogates
=== tests/othertests/tokenize_tag_contents.py
==================================================================
--- tests/othertests/tokenize_tag_contents.py	(revision 2312)
+++ tests/othertests/tokenize_tag_contents.py	(local)
@@ -0,0 +1,62 @@
+"""
+# positional argument
+>>> tok('tag arg1')
+('tag', ['arg1'], {})
+
+# single-quoted positional argument
+>>> tok("tag 'arg1 with spaces'")
+('tag', ["'arg1 with spaces'"], {})
+
+# double-quoted positional argument
+>>> tok('tag "arg1 with spaces"')
+('tag', ['"arg1 with spaces"'], {})
+
+# named argument
+>>> tok('tag name1=arg1')
+('tag', [], {'name1': 'arg1'})
+
+# single-quoted named argument
+>>> tok("tag name1='arg1 with spaces'")
+('tag', [], {'name1': "'arg1 with spaces'"})
+
+# double-quoted named argument
+>>> tok('tag name1="arg1 with spaces"')
+('tag', [], {'name1': '"arg1 with spaces"'})
+
+# positional argument and named argument
+>>> tok('tag arg1 name1=arg2')
+('tag', ['arg1'], {'name1': 'arg2'})
+
+# mixed arguments
+>>> tok('tag arg1 "arg2 with spaces" name1=arg3 name2="arg4 with spaces"')
+('tag', ['arg1', '"arg2 with spaces"'], {'name2': '"arg4 with spaces"', 'name1': 'arg3'})
+
+# double quotes in single-quoted string
+>>> tok("tag 'arg1 \\"with quotes\\"'")
+('tag', ['\\'arg1 "with quotes"\\''], {})
+
+# single quotes in double-quoted string
+>>> tok('tag "arg1 \\'with quotes\\'"')
+('tag', ['"arg1 \\'with quotes\\'"'], {})
+
+# single quotes in single-quoted string
+>>> tok("tag 'arg1 \\\\'with quotes\\\\''")
+('tag', ["'arg1 'with quotes''"], {})
+
+# double quotes in double-quoted string
+>>> tok('tag "arg1 \\\\"with quotes\\\\""')
+('tag', ['"arg1 "with quotes""'], {})
+
+# positional argument after named argument not allowed
+>>> tok('tag name1=arg1 arg2')
+Traceback (most recent call last):
+...
+SyntaxError: template tag syntax error: non-keyword arguments must come before keyword arguments
+
+"""
+
+from django.template import tokenize_tag_contents as tok
+
+if __name__ == "__main__":
+    import doctest
+    doctest.testmod()

