diff --git a/django/template/__init__.py b/django/template/__init__.py
index 5c4ab30..1f91d33 100644
--- a/django/template/__init__.py
+++ b/django/template/__init__.py
@@ -49,6 +49,7 @@ u'<html><h1>Hello</h1></html>'
 u'<html></html>'
 """
 import re
+import imp
 from inspect import getargspec
 from django.conf import settings
 from django.template.context import Context, RequestContext, ContextPopException
@@ -59,6 +60,7 @@ from django.utils.encoding import smart_unicode, force_unicode
 from django.utils.translation import ugettext as _
 from django.utils.safestring import SafeData, EscapeData, mark_safe, mark_for_escaping
 from django.utils.html import escape
+from django.templatetags import get_templatetags_modules 
 
 __all__ = ('Template', 'Context', 'RequestContext', 'compile_string')
 
@@ -913,22 +915,48 @@ class Library(object):
             return func
         return dec
 
-def get_library(module_name):
-    lib = libraries.get(module_name, None)
+def import_library(templatetag_module, library_name):
+    try:
+        components = templatetag_module.split('.')
+        mod = __import__(templatetag_module, {}, {}, components[-1])
+        imp.find_module(library_name, mod.__path__)
+    except ImportError:
+        return None 
+    library_module = '%s.%s' % (templatetag_module, library_name)
+    components = library_module.split('.')
+    mod = __import__(library_module, {}, {}, components[-1])
+    try:
+        return mod.register
+    except AttributeError:
+        raise InvalidTemplateLibrary("Template library %s does not have a variable named 'register'" % module_name)
+
+def get_library(library_name):
+    lib = libraries.get(library_name, None)
     if not lib:
-        try:
-            mod = __import__(module_name, {}, {}, [''])
-        except ImportError, e:
-            raise InvalidTemplateLibrary("Could not load template library from %s, %s" % (module_name, e))
-        try:
-            lib = mod.register
-            libraries[module_name] = lib
-        except AttributeError:
-            raise InvalidTemplateLibrary("Template library %s does not have a variable named 'register'" % module_name)
+
+        """ 
+        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 thread will grab the cached
+        module from libraries.
+        """
+        templatetags_modules = get_templatetags_modules()
+        tried_modules = []
+        for module in templatetags_modules:
+            taglib_module = '%s.%s' % (module, library_name) 
+            tried_modules.append(taglib_module)
+            lib = import_library(module, library_name)
+            if lib:
+                libraries[library_name] = lib
+                break
+        if not lib:
+            raise InvalidTemplateLibrary("Template library %s not found, tried %s" % (library_name, ','.join(tried_modules))) 
     return lib
 
-def add_to_builtins(module_name):
-    builtins.append(get_library(module_name))
+def add_to_builtins(module, library_name):
+    builtins.append(import_library(module, library_name))
 
-add_to_builtins('django.template.defaulttags')
-add_to_builtins('django.template.defaultfilters')
+add_to_builtins('django.templatetags', 'defaulttags')
+add_to_builtins('django.templatetags', 'defaultfilters')
diff --git a/django/template/defaultfilters.py b/django/template/defaultfilters.py
deleted file mode 100644
index cef3143..0000000
--- a/django/template/defaultfilters.py
+++ /dev/null
@@ -1,851 +0,0 @@
-"""Default variable filters."""
-
-import re
-import random as random_module
-try:
-    from functools import wraps
-except ImportError:
-    from django.utils.functional import wraps  # Python 2.3, 2.4 fallback.
-
-from django.template import Variable, Library
-from django.conf import settings
-from django.utils.translation import ugettext, ungettext
-from django.utils.encoding import force_unicode, iri_to_uri
-from django.utils.safestring import mark_safe, SafeData
-
-register = Library()
-
-#######################
-# STRING DECORATOR    #
-#######################
-
-def stringfilter(func):
-    """
-    Decorator for filters which should only receive unicode objects. The object
-    passed as the first positional argument will be converted to a unicode
-    object.
-    """
-    def _dec(*args, **kwargs):
-        if args:
-            args = list(args)
-            args[0] = force_unicode(args[0])
-            if isinstance(args[0], SafeData) and getattr(func, 'is_safe', False):
-                return mark_safe(func(*args, **kwargs))
-        return func(*args, **kwargs)
-
-    # Include a reference to the real function (used to check original
-    # arguments by the template parser).
-    _dec._decorated_function = getattr(func, '_decorated_function', func)
-    for attr in ('is_safe', 'needs_autoescape'):
-        if hasattr(func, attr):
-            setattr(_dec, attr, getattr(func, attr))
-    return wraps(func)(_dec)
-
-###################
-# STRINGS         #
-###################
-
-
-def addslashes(value):
-    """
-    Adds slashes before quotes. Useful for escaping strings in CSV, for
-    example. Less useful for escaping JavaScript; use the ``escapejs``
-    filter instead.
-    """
-    return value.replace('\\', '\\\\').replace('"', '\\"').replace("'", "\\'")
-addslashes.is_safe = True
-addslashes = stringfilter(addslashes)
-
-def capfirst(value):
-    """Capitalizes the first character of the value."""
-    return value and value[0].upper() + value[1:]
-capfirst.is_safe=True
-capfirst = stringfilter(capfirst)
-
-_js_escapes = (
-    ('\\', '\\\\'),
-    ('"', '\\"'),
-    ("'", "\\'"),
-    ('\n', '\\n'),
-    ('\r', '\\r'),
-    ('\b', '\\b'),
-    ('\f', '\\f'),
-    ('\t', '\\t'),
-    ('\v', '\\v'),
-    ('</', '<\\/'),
-)
-def escapejs(value):
-    """Backslash-escapes characters for use in JavaScript strings."""
-    for bad, good in _js_escapes:
-        value = value.replace(bad, good)
-    return value
-escapejs = stringfilter(escapejs)
-
-def fix_ampersands(value):
-    """Replaces ampersands with ``&amp;`` entities."""
-    from django.utils.html import fix_ampersands
-    return fix_ampersands(value)
-fix_ampersands.is_safe=True
-fix_ampersands = stringfilter(fix_ampersands)
-
-def floatformat(text, arg=-1):
-    """
-    Displays a float to a specified number of decimal places.
-
-    If called without an argument, it displays the floating point number with
-    one decimal place -- but only if there's a decimal place to be displayed:
-
-    * num1 = 34.23234
-    * num2 = 34.00000
-    * num3 = 34.26000
-    * {{ num1|floatformat }} displays "34.2"
-    * {{ num2|floatformat }} displays "34"
-    * {{ num3|floatformat }} displays "34.3"
-
-    If arg is positive, it will always display exactly arg number of decimal
-    places:
-
-    * {{ num1|floatformat:3 }} displays "34.232"
-    * {{ num2|floatformat:3 }} displays "34.000"
-    * {{ num3|floatformat:3 }} displays "34.260"
-
-    If arg is negative, it will display arg number of decimal places -- but
-    only if there are places to be displayed:
-
-    * {{ num1|floatformat:"-3" }} displays "34.232"
-    * {{ num2|floatformat:"-3" }} displays "34"
-    * {{ num3|floatformat:"-3" }} displays "34.260"
-    """
-    try:
-        f = float(text)
-    except (ValueError, TypeError):
-        return u''
-    try:
-        d = int(arg)
-    except ValueError:
-        return force_unicode(f)
-    try:
-        m = f - int(f)
-    except OverflowError:
-        return force_unicode(f)
-    if not m and d < 0:
-        return mark_safe(u'%d' % int(f))
-    else:
-        formatstr = u'%%.%df' % abs(d)
-        return mark_safe(formatstr % f)
-floatformat.is_safe = True
-
-def iriencode(value):
-    """Escapes an IRI value for use in a URL."""
-    return force_unicode(iri_to_uri(value))
-iriencode.is_safe = True
-iriencode = stringfilter(iriencode)
-
-def linenumbers(value, autoescape=None):
-    """Displays text with line numbers."""
-    from django.utils.html import escape
-    lines = value.split(u'\n')
-    # Find the maximum width of the line count, for use with zero padding
-    # string format command
-    width = unicode(len(unicode(len(lines))))
-    if not autoescape or isinstance(value, SafeData):
-        for i, line in enumerate(lines):
-            lines[i] = (u"%0" + width  + u"d. %s") % (i + 1, line)
-    else:
-        for i, line in enumerate(lines):
-            lines[i] = (u"%0" + width  + u"d. %s") % (i + 1, escape(line))
-    return mark_safe(u'\n'.join(lines))
-linenumbers.is_safe = True
-linenumbers.needs_autoescape = True
-linenumbers = stringfilter(linenumbers)
-
-def lower(value):
-    """Converts a string into all lowercase."""
-    return value.lower()
-lower.is_safe = True
-lower = stringfilter(lower)
-
-def make_list(value):
-    """
-    Returns the value turned into a list.
-
-    For an integer, it's a list of digits.
-    For a string, it's a list of characters.
-    """
-    return list(value)
-make_list.is_safe = False
-make_list = stringfilter(make_list)
-
-def slugify(value):
-    """
-    Normalizes string, converts to lowercase, removes non-alpha characters,
-    and converts spaces to hyphens.
-    """
-    import unicodedata
-    value = unicodedata.normalize('NFKD', value).encode('ascii', 'ignore')
-    value = unicode(re.sub('[^\w\s-]', '', value).strip().lower())
-    return mark_safe(re.sub('[-\s]+', '-', value))
-slugify.is_safe = True
-slugify = stringfilter(slugify)
-
-def stringformat(value, arg):
-    """
-    Formats the variable according to the arg, a string formatting specifier.
-
-    This specifier uses Python string formating syntax, with the exception that
-    the leading "%" is dropped.
-
-    See http://docs.python.org/lib/typesseq-strings.html for documentation
-    of Python string formatting
-    """
-    try:
-        return (u"%" + unicode(arg)) % value
-    except (ValueError, TypeError):
-        return u""
-stringformat.is_safe = True
-
-def title(value):
-    """Converts a string into titlecase."""
-    return re.sub("([a-z])'([A-Z])", lambda m: m.group(0).lower(), value.title())
-title.is_safe = True
-title = stringfilter(title)
-
-def truncatewords(value, arg):
-    """
-    Truncates a string after a certain number of words.
-
-    Argument: Number of words to truncate after.
-    """
-    from django.utils.text import truncate_words
-    try:
-        length = int(arg)
-    except ValueError: # Invalid literal for int().
-        return value # Fail silently.
-    return truncate_words(value, length)
-truncatewords.is_safe = True
-truncatewords = stringfilter(truncatewords)
-
-def truncatewords_html(value, arg):
-    """
-    Truncates HTML after a certain number of words.
-
-    Argument: Number of words to truncate after.
-    """
-    from django.utils.text import truncate_html_words
-    try:
-        length = int(arg)
-    except ValueError: # invalid literal for int()
-        return value # Fail silently.
-    return truncate_html_words(value, length)
-truncatewords_html.is_safe = True
-truncatewords_html = stringfilter(truncatewords_html)
-
-def upper(value):
-    """Converts a string into all uppercase."""
-    return value.upper()
-upper.is_safe = False
-upper = stringfilter(upper)
-
-def urlencode(value):
-    """Escapes a value for use in a URL."""
-    from django.utils.http import urlquote
-    return urlquote(value)
-urlencode.is_safe = False
-urlencode = stringfilter(urlencode)
-
-def urlize(value, autoescape=None):
-    """Converts URLs in plain text into clickable links."""
-    from django.utils.html import urlize
-    return mark_safe(urlize(value, nofollow=True, autoescape=autoescape))
-urlize.is_safe=True
-urlize.needs_autoescape = True
-urlize = stringfilter(urlize)
-
-def urlizetrunc(value, limit, autoescape=None):
-    """
-    Converts URLs into clickable links, truncating URLs to the given character
-    limit, and adding 'rel=nofollow' attribute to discourage spamming.
-
-    Argument: Length to truncate URLs to.
-    """
-    from django.utils.html import urlize
-    return mark_safe(urlize(value, trim_url_limit=int(limit), nofollow=True,
-                            autoescape=autoescape))
-urlizetrunc.is_safe = True
-urlizetrunc.needs_autoescape = True
-urlizetrunc = stringfilter(urlizetrunc)
-
-def wordcount(value):
-    """Returns the number of words."""
-    return len(value.split())
-wordcount.is_safe = False
-wordcount = stringfilter(wordcount)
-
-def wordwrap(value, arg):
-    """
-    Wraps words at specified line length.
-
-    Argument: number of characters to wrap the text at.
-    """
-    from django.utils.text import wrap
-    return wrap(value, int(arg))
-wordwrap.is_safe = True
-wordwrap = stringfilter(wordwrap)
-
-def ljust(value, arg):
-    """
-    Left-aligns the value in a field of a given width.
-
-    Argument: field size.
-    """
-    return value.ljust(int(arg))
-ljust.is_safe = True
-ljust = stringfilter(ljust)
-
-def rjust(value, arg):
-    """
-    Right-aligns the value in a field of a given width.
-
-    Argument: field size.
-    """
-    return value.rjust(int(arg))
-rjust.is_safe = True
-rjust = stringfilter(rjust)
-
-def center(value, arg):
-    """Centers the value in a field of a given width."""
-    return value.center(int(arg))
-center.is_safe = True
-center = stringfilter(center)
-
-def cut(value, arg):
-    """
-    Removes all values of arg from the given string.
-    """
-    safe = isinstance(value, SafeData)
-    value = value.replace(arg, u'')
-    if safe and arg != ';':
-        return mark_safe(value)
-    return value
-cut = stringfilter(cut)
-
-###################
-# HTML STRINGS    #
-###################
-
-def escape(value):
-    """
-    Marks the value as a string that should not be auto-escaped.
-    """
-    from django.utils.safestring import mark_for_escaping
-    return mark_for_escaping(value)
-escape.is_safe = True
-escape = stringfilter(escape)
-
-def force_escape(value):
-    """
-    Escapes a string's HTML. This returns a new string containing the escaped
-    characters (as opposed to "escape", which marks the content for later
-    possible escaping).
-    """
-    from django.utils.html import escape
-    return mark_safe(escape(value))
-force_escape = stringfilter(force_escape)
-force_escape.is_safe = True
-
-def linebreaks(value, autoescape=None):
-    """
-    Replaces line breaks in plain text with appropriate HTML; a single
-    newline becomes an HTML line break (``<br />``) and a new line
-    followed by a blank line becomes a paragraph break (``</p>``).
-    """
-    from django.utils.html import linebreaks
-    autoescape = autoescape and not isinstance(value, SafeData)
-    return mark_safe(linebreaks(value, autoescape))
-linebreaks.is_safe = True
-linebreaks.needs_autoescape = True
-linebreaks = stringfilter(linebreaks)
-
-def linebreaksbr(value, autoescape=None):
-    """
-    Converts all newlines in a piece of plain text to HTML line breaks
-    (``<br />``).
-    """
-    if autoescape and not isinstance(value, SafeData):
-        from django.utils.html import escape
-        value = escape(value)
-    return mark_safe(value.replace('\n', '<br />'))
-linebreaksbr.is_safe = True
-linebreaksbr.needs_autoescape = True
-linebreaksbr = stringfilter(linebreaksbr)
-
-def safe(value):
-    """
-    Marks the value as a string that should not be auto-escaped.
-    """
-    from django.utils.safestring import mark_safe
-    return mark_safe(value)
-safe.is_safe = True
-safe = stringfilter(safe)
-
-def removetags(value, tags):
-    """Removes a space separated list of [X]HTML tags from the output."""
-    tags = [re.escape(tag) for tag in tags.split()]
-    tags_re = u'(%s)' % u'|'.join(tags)
-    starttag_re = re.compile(ur'<%s(/?>|(\s+[^>]*>))' % tags_re, re.U)
-    endtag_re = re.compile(u'</%s>' % tags_re)
-    value = starttag_re.sub(u'', value)
-    value = endtag_re.sub(u'', value)
-    return value
-removetags.is_safe = True
-removetags = stringfilter(removetags)
-
-def striptags(value):
-    """Strips all [X]HTML tags."""
-    from django.utils.html import strip_tags
-    return strip_tags(value)
-striptags.is_safe = True
-striptags = stringfilter(striptags)
-
-###################
-# LISTS           #
-###################
-
-def dictsort(value, arg):
-    """
-    Takes a list of dicts, returns that list sorted by the property given in
-    the argument.
-    """
-    var_resolve = Variable(arg).resolve
-    decorated = [(var_resolve(item), item) for item in value]
-    decorated.sort()
-    return [item[1] for item in decorated]
-dictsort.is_safe = False
-
-def dictsortreversed(value, arg):
-    """
-    Takes a list of dicts, returns that list sorted in reverse order by the
-    property given in the argument.
-    """
-    var_resolve = Variable(arg).resolve
-    decorated = [(var_resolve(item), item) for item in value]
-    decorated.sort()
-    decorated.reverse()
-    return [item[1] for item in decorated]
-dictsortreversed.is_safe = False
-
-def first(value):
-    """Returns the first item in a list."""
-    try:
-        return value[0]
-    except IndexError:
-        return u''
-first.is_safe = False
-
-def join(value, arg):
-    """Joins a list with a string, like Python's ``str.join(list)``."""
-    try:
-        data = arg.join(map(force_unicode, value))
-    except AttributeError: # fail silently but nicely
-        return value
-    safe_args = reduce(lambda lhs, rhs: lhs and isinstance(rhs, SafeData),
-            value, True)
-    if safe_args:
-        return mark_safe(data)
-    else:
-        return data
-join.is_safe = True
-
-def last(value):
-    "Returns the last item in a list"
-    try:
-        return value[-1]
-    except IndexError:
-        return u''
-last.is_safe = True
-
-def length(value):
-    """Returns the length of the value - useful for lists."""
-    return len(value)
-length.is_safe = True
-
-def length_is(value, arg):
-    """Returns a boolean of whether the value's length is the argument."""
-    return len(value) == int(arg)
-length_is.is_safe = True
-
-def random(value):
-    """Returns a random item from the list."""
-    return random_module.choice(value)
-random.is_safe = True
-
-def slice_(value, arg):
-    """
-    Returns a slice of the list.
-
-    Uses the same syntax as Python's list slicing; see
-    http://diveintopython.org/native_data_types/lists.html#odbchelper.list.slice
-    for an introduction.
-    """
-    try:
-        bits = []
-        for x in arg.split(u':'):
-            if len(x) == 0:
-                bits.append(None)
-            else:
-                bits.append(int(x))
-        return value[slice(*bits)]
-
-    except (ValueError, TypeError):
-        return value # Fail silently.
-slice_.is_safe = True
-
-def unordered_list(value, autoescape=None):
-    """
-    Recursively takes a self-nested list and returns an HTML unordered list --
-    WITHOUT opening and closing <ul> tags.
-
-    The list is assumed to be in the proper format. For example, if ``var``
-    contains: ``['States', ['Kansas', ['Lawrence', 'Topeka'], 'Illinois']]``,
-    then ``{{ var|unordered_list }}`` would return::
-
-        <li>States
-        <ul>
-                <li>Kansas
-                <ul>
-                        <li>Lawrence</li>
-                        <li>Topeka</li>
-                </ul>
-                </li>
-                <li>Illinois</li>
-        </ul>
-        </li>
-    """
-    if autoescape:
-        from django.utils.html import conditional_escape
-        escaper = conditional_escape
-    else:
-        escaper = lambda x: x
-    def convert_old_style_list(list_):
-        """
-        Converts old style lists to the new easier to understand format.
-
-        The old list format looked like:
-            ['Item 1', [['Item 1.1', []], ['Item 1.2', []]]
-
-        And it is converted to:
-            ['Item 1', ['Item 1.1', 'Item 1.2]]
-        """
-        if not isinstance(list_, (tuple, list)) or len(list_) != 2:
-            return list_, False
-        first_item, second_item = list_
-        if second_item == []:
-            return [first_item], True
-        old_style_list = True
-        new_second_item = []
-        for sublist in second_item:
-            item, old_style_list = convert_old_style_list(sublist)
-            if not old_style_list:
-                break
-            new_second_item.extend(item)
-        if old_style_list:
-            second_item = new_second_item
-        return [first_item, second_item], old_style_list
-    def _helper(list_, tabs=1):
-        indent = u'\t' * tabs
-        output = []
-
-        list_length = len(list_)
-        i = 0
-        while i < list_length:
-            title = list_[i]
-            sublist = ''
-            sublist_item = None
-            if isinstance(title, (list, tuple)):
-                sublist_item = title
-                title = ''
-            elif i < list_length - 1:
-                next_item = list_[i+1]
-                if next_item and isinstance(next_item, (list, tuple)):
-                    # The next item is a sub-list.
-                    sublist_item = next_item
-                    # We've processed the next item now too.
-                    i += 1
-            if sublist_item:
-                sublist = _helper(sublist_item, tabs+1)
-                sublist = '\n%s<ul>\n%s\n%s</ul>\n%s' % (indent, sublist,
-                                                         indent, indent)
-            output.append('%s<li>%s%s</li>' % (indent,
-                    escaper(force_unicode(title)), sublist))
-            i += 1
-        return '\n'.join(output)
-    value, converted = convert_old_style_list(value)
-    return mark_safe(_helper(value))
-unordered_list.is_safe = True
-unordered_list.needs_autoescape = True
-
-###################
-# INTEGERS        #
-###################
-
-def add(value, arg):
-    """Adds the arg to the value."""
-    return int(value) + int(arg)
-add.is_safe = False
-
-def get_digit(value, arg):
-    """
-    Given a whole number, returns the requested digit of it, where 1 is the
-    right-most digit, 2 is the second-right-most digit, etc. Returns the
-    original value for invalid input (if input or argument is not an integer,
-    or if argument is less than 1). Otherwise, output is always an integer.
-    """
-    try:
-        arg = int(arg)
-        value = int(value)
-    except ValueError:
-        return value # Fail silently for an invalid argument
-    if arg < 1:
-        return value
-    try:
-        return int(str(value)[-arg])
-    except IndexError:
-        return 0
-get_digit.is_safe = False
-
-###################
-# DATES           #
-###################
-
-def date(value, arg=None):
-    """Formats a date according to the given format."""
-    from django.utils.dateformat import format
-    if not value:
-        return u''
-    if arg is None:
-        arg = settings.DATE_FORMAT
-    return format(value, arg)
-date.is_safe = False
-
-def time(value, arg=None):
-    """Formats a time according to the given format."""
-    from django.utils.dateformat import time_format
-    if value in (None, u''):
-        return u''
-    if arg is None:
-        arg = settings.TIME_FORMAT
-    return time_format(value, arg)
-time.is_safe = False
-
-def timesince(value, arg=None):
-    """Formats a date as the time since that date (i.e. "4 days, 6 hours")."""
-    from django.utils.timesince import timesince
-    if not value:
-        return u''
-    if arg:
-        return timesince(arg, value)
-    return timesince(value)
-timesince.is_safe = False
-
-def timeuntil(value, arg=None):
-    """Formats a date as the time until that date (i.e. "4 days, 6 hours")."""
-    from django.utils.timesince import timesince
-    from datetime import datetime
-    if not value:
-        return u''
-    if arg:
-        return timesince(arg, value)
-    return timesince(datetime.now(), value)
-timeuntil.is_safe = False
-
-###################
-# LOGIC           #
-###################
-
-def default(value, arg):
-    """If value is unavailable, use given default."""
-    return value or arg
-default.is_safe = False
-
-def default_if_none(value, arg):
-    """If value is None, use given default."""
-    if value is None:
-        return arg
-    return value
-default_if_none.is_safe = False
-
-def divisibleby(value, arg):
-    """Returns True if the value is devisible by the argument."""
-    return int(value) % int(arg) == 0
-divisibleby.is_safe = False
-
-def yesno(value, arg=None):
-    """
-    Given a string mapping values for true, false and (optionally) None,
-    returns one of those strings accoding to the value:
-
-    ==========  ======================  ==================================
-    Value       Argument                Outputs
-    ==========  ======================  ==================================
-    ``True``    ``"yeah,no,maybe"``     ``yeah``
-    ``False``   ``"yeah,no,maybe"``     ``no``
-    ``None``    ``"yeah,no,maybe"``     ``maybe``
-    ``None``    ``"yeah,no"``           ``"no"`` (converts None to False
-                                        if no mapping for None is given.
-    ==========  ======================  ==================================
-    """
-    if arg is None:
-        arg = ugettext('yes,no,maybe')
-    bits = arg.split(u',')
-    if len(bits) < 2:
-        return value # Invalid arg.
-    try:
-        yes, no, maybe = bits
-    except ValueError:
-        # Unpack list of wrong size (no "maybe" value provided).
-        yes, no, maybe = bits[0], bits[1], bits[1]
-    if value is None:
-        return maybe
-    if value:
-        return yes
-    return no
-yesno.is_safe = False
-
-###################
-# MISC            #
-###################
-
-def filesizeformat(bytes):
-    """
-    Formats the value like a 'human-readable' file size (i.e. 13 KB, 4.1 MB,
-    102 bytes, etc).
-    """
-    try:
-        bytes = float(bytes)
-    except TypeError:
-        return u"0 bytes"
-
-    if bytes < 1024:
-        return ungettext("%(size)d byte", "%(size)d bytes", bytes) % {'size': bytes}
-    if bytes < 1024 * 1024:
-        return ugettext("%.1f KB") % (bytes / 1024)
-    if bytes < 1024 * 1024 * 1024:
-        return ugettext("%.1f MB") % (bytes / (1024 * 1024))
-    return ugettext("%.1f GB") % (bytes / (1024 * 1024 * 1024))
-filesizeformat.is_safe = True
-
-def pluralize(value, arg=u's'):
-    """
-    Returns a plural suffix if the value is not 1. By default, 's' is used as
-    the suffix:
-
-    * If value is 0, vote{{ value|pluralize }} displays "0 votes".
-    * If value is 1, vote{{ value|pluralize }} displays "1 vote".
-    * If value is 2, vote{{ value|pluralize }} displays "2 votes".
-
-    If an argument is provided, that string is used instead:
-
-    * If value is 0, class{{ value|pluralize:"es" }} displays "0 classes".
-    * If value is 1, class{{ value|pluralize:"es" }} displays "1 class".
-    * If value is 2, class{{ value|pluralize:"es" }} displays "2 classes".
-
-    If the provided argument contains a comma, the text before the comma is
-    used for the singular case and the text after the comma is used for the
-    plural case:
-
-    * If value is 0, cand{{ value|pluralize:"y,ies" }} displays "0 candies".
-    * If value is 1, cand{{ value|pluralize:"y,ies" }} displays "1 candy".
-    * If value is 2, cand{{ value|pluralize:"y,ies" }} displays "2 candies".
-    """
-    if not u',' in arg:
-        arg = u',' + arg
-    bits = arg.split(u',')
-    if len(bits) > 2:
-        return u''
-    singular_suffix, plural_suffix = bits[:2]
-
-    try:
-        if int(value) != 1:
-            return plural_suffix
-    except ValueError: # Invalid string that's not a number.
-        pass
-    except TypeError: # Value isn't a string or a number; maybe it's a list?
-        try:
-            if len(value) != 1:
-                return plural_suffix
-        except TypeError: # len() of unsized object.
-            pass
-    return singular_suffix
-pluralize.is_safe = False
-
-def phone2numeric(value):
-    """Takes a phone number and converts it in to its numerical equivalent."""
-    from django.utils.text import phone2numeric
-    return phone2numeric(value)
-phone2numeric.is_safe = True
-
-def pprint(value):
-    """A wrapper around pprint.pprint -- for debugging, really."""
-    from pprint import pformat
-    try:
-        return pformat(value)
-    except Exception, e:
-        return u"Error in formatting: %s" % force_unicode(e, errors="replace")
-pprint.is_safe = True
-
-# Syntax: register.filter(name of filter, callback)
-register.filter(add)
-register.filter(addslashes)
-register.filter(capfirst)
-register.filter(center)
-register.filter(cut)
-register.filter(date)
-register.filter(default)
-register.filter(default_if_none)
-register.filter(dictsort)
-register.filter(dictsortreversed)
-register.filter(divisibleby)
-register.filter(escape)
-register.filter(escapejs)
-register.filter(filesizeformat)
-register.filter(first)
-register.filter(fix_ampersands)
-register.filter(floatformat)
-register.filter(force_escape)
-register.filter(get_digit)
-register.filter(iriencode)
-register.filter(join)
-register.filter(last)
-register.filter(length)
-register.filter(length_is)
-register.filter(linebreaks)
-register.filter(linebreaksbr)
-register.filter(linenumbers)
-register.filter(ljust)
-register.filter(lower)
-register.filter(make_list)
-register.filter(phone2numeric)
-register.filter(pluralize)
-register.filter(pprint)
-register.filter(removetags)
-register.filter(random)
-register.filter(rjust)
-register.filter(safe)
-register.filter('slice', slice_)
-register.filter(slugify)
-register.filter(stringformat)
-register.filter(striptags)
-register.filter(time)
-register.filter(timesince)
-register.filter(timeuntil)
-register.filter(title)
-register.filter(truncatewords)
-register.filter(truncatewords_html)
-register.filter(unordered_list)
-register.filter(upper)
-register.filter(urlencode)
-register.filter(urlize)
-register.filter(urlizetrunc)
-register.filter(wordcount)
-register.filter(wordwrap)
-register.filter(yesno)
diff --git a/django/template/defaulttags.py b/django/template/defaulttags.py
deleted file mode 100644
index 01c43ee..0000000
--- a/django/template/defaulttags.py
+++ /dev/null
@@ -1,1105 +0,0 @@
-"""Default tags used by the template system, available to all templates."""
-
-import sys
-import re
-from itertools import cycle as itertools_cycle
-try:
-    reversed
-except NameError:
-    from django.utils.itercompat import reversed     # Python 2.3 fallback
-
-from django.template import Node, NodeList, Template, Context, Variable
-from django.template import TemplateSyntaxError, VariableDoesNotExist, BLOCK_TAG_START, BLOCK_TAG_END, VARIABLE_TAG_START, VARIABLE_TAG_END, SINGLE_BRACE_START, SINGLE_BRACE_END, COMMENT_TAG_START, COMMENT_TAG_END
-from django.template import get_library, Library, InvalidTemplateLibrary
-from django.conf import settings
-from django.utils.encoding import smart_str, smart_unicode
-from django.utils.itercompat import groupby
-from django.utils.safestring import mark_safe
-
-register = Library()
-
-class AutoEscapeControlNode(Node):
-    """Implements the actions of the autoescape tag."""
-    def __init__(self, setting, nodelist):
-        self.setting, self.nodelist = setting, nodelist
-
-    def render(self, context):
-        old_setting = context.autoescape
-        context.autoescape = self.setting
-        output = self.nodelist.render(context)
-        context.autoescape = old_setting
-        if self.setting:
-            return mark_safe(output)
-        else:
-            return output
-
-class CommentNode(Node):
-    def render(self, context):
-        return ''
-
-class CycleNode(Node):
-    def __init__(self, cyclevars, variable_name=None):
-        self.cycle_iter = itertools_cycle([Variable(v) for v in cyclevars])
-        self.variable_name = variable_name
-
-    def render(self, context):
-        value = self.cycle_iter.next().resolve(context)
-        if self.variable_name:
-            context[self.variable_name] = value
-        return value
-
-class DebugNode(Node):
-    def render(self, context):
-        from pprint import pformat
-        output = [pformat(val) for val in context]
-        output.append('\n\n')
-        output.append(pformat(sys.modules))
-        return ''.join(output)
-
-class FilterNode(Node):
-    def __init__(self, filter_expr, nodelist):
-        self.filter_expr, self.nodelist = filter_expr, nodelist
-
-    def render(self, context):
-        output = self.nodelist.render(context)
-        # Apply filters.
-        context.update({'var': output})
-        filtered = self.filter_expr.resolve(context)
-        context.pop()
-        return filtered
-
-class FirstOfNode(Node):
-    def __init__(self, vars):
-        self.vars = map(Variable, vars)
-
-    def render(self, context):
-        for var in self.vars:
-            try:
-                value = var.resolve(context)
-            except VariableDoesNotExist:
-                continue
-            if value:
-                return smart_unicode(value)
-        return u''
-
-class ForNode(Node):
-    def __init__(self, loopvars, sequence, is_reversed, nodelist_loop):
-        self.loopvars, self.sequence = loopvars, sequence
-        self.is_reversed = is_reversed
-        self.nodelist_loop = nodelist_loop
-
-    def __repr__(self):
-        reversed_text = self.is_reversed and ' reversed' or ''
-        return "<For Node: for %s in %s, tail_len: %d%s>" % \
-            (', '.join(self.loopvars), self.sequence, len(self.nodelist_loop),
-             reversed_text)
-
-    def __iter__(self):
-        for node in self.nodelist_loop:
-            yield node
-
-    def get_nodes_by_type(self, nodetype):
-        nodes = []
-        if isinstance(self, nodetype):
-            nodes.append(self)
-        nodes.extend(self.nodelist_loop.get_nodes_by_type(nodetype))
-        return nodes
-
-    def render(self, context):
-        nodelist = NodeList()
-        if 'forloop' in context:
-            parentloop = context['forloop']
-        else:
-            parentloop = {}
-        context.push()
-        try:
-            values = self.sequence.resolve(context, True)
-        except VariableDoesNotExist:
-            values = []
-        if values is None:
-            values = []
-        if not hasattr(values, '__len__'):
-            values = list(values)
-        len_values = len(values)
-        if self.is_reversed:
-            values = reversed(values)
-        unpack = len(self.loopvars) > 1
-        # Create a forloop value in the context.  We'll update counters on each
-        # iteration just below.
-        loop_dict = context['forloop'] = {'parentloop': parentloop}
-        for i, item in enumerate(values):
-            # Shortcuts for current loop iteration number.
-            loop_dict['counter0'] = i
-            loop_dict['counter'] = i+1
-            # Reverse counter iteration numbers.
-            loop_dict['revcounter'] = len_values - i
-            loop_dict['revcounter0'] = len_values - i - 1
-            # Boolean values designating first and last times through loop.
-            loop_dict['first'] = (i == 0)
-            loop_dict['last'] = (i == len_values - 1)
-
-            if unpack:
-                # If there are multiple loop variables, unpack the item into
-                # them.
-                context.update(dict(zip(self.loopvars, item)))
-            else:
-                context[self.loopvars[0]] = item
-            for node in self.nodelist_loop:
-                nodelist.append(node.render(context))
-            if unpack:
-                # The loop variables were pushed on to the context so pop them
-                # off again. This is necessary because the tag lets the length
-                # of loopvars differ to the length of each set of items and we
-                # don't want to leave any vars from the previous loop on the
-                # context.
-                context.pop()
-        context.pop()
-        return nodelist.render(context)
-
-class IfChangedNode(Node):
-    def __init__(self, nodelist, *varlist):
-        self.nodelist = nodelist
-        self._last_seen = None
-        self._varlist = map(Variable, varlist)
-        self._id = str(id(self))
-
-    def render(self, context):
-        if 'forloop' in context and self._id not in context['forloop']:
-            self._last_seen = None
-            context['forloop'][self._id] = 1
-        try:
-            if self._varlist:
-                # Consider multiple parameters.  This automatically behaves
-                # like an OR evaluation of the multiple variables.
-                compare_to = [var.resolve(context) for var in self._varlist]
-            else:
-                compare_to = self.nodelist.render(context)
-        except VariableDoesNotExist:
-            compare_to = None
-
-        if  compare_to != self._last_seen:
-            firstloop = (self._last_seen == None)
-            self._last_seen = compare_to
-            context.push()
-            context['ifchanged'] = {'firstloop': firstloop}
-            content = self.nodelist.render(context)
-            context.pop()
-            return content
-        else:
-            return ''
-
-class IfEqualNode(Node):
-    def __init__(self, var1, var2, nodelist_true, nodelist_false, negate):
-        self.var1, self.var2 = Variable(var1), Variable(var2)
-        self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false
-        self.negate = negate
-
-    def __repr__(self):
-        return "<IfEqualNode>"
-
-    def render(self, context):
-        try:
-            val1 = self.var1.resolve(context)
-        except VariableDoesNotExist:
-            val1 = None
-        try:
-            val2 = self.var2.resolve(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)
-
-class IfNode(Node):
-    def __init__(self, bool_exprs, nodelist_true, nodelist_false, link_type):
-        self.bool_exprs = bool_exprs
-        self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false
-        self.link_type = link_type
-
-    def __repr__(self):
-        return "<If node>"
-
-    def __iter__(self):
-        for node in self.nodelist_true:
-            yield node
-        for node in self.nodelist_false:
-            yield node
-
-    def get_nodes_by_type(self, nodetype):
-        nodes = []
-        if isinstance(self, nodetype):
-            nodes.append(self)
-        nodes.extend(self.nodelist_true.get_nodes_by_type(nodetype))
-        nodes.extend(self.nodelist_false.get_nodes_by_type(nodetype))
-        return nodes
-
-    def render(self, context):
-        if self.link_type == IfNode.LinkTypes.or_:
-            for ifnot, bool_expr in self.bool_exprs:
-                try:
-                    value = bool_expr.resolve(context, True)
-                except VariableDoesNotExist:
-                    value = None
-                if (value and not ifnot) or (ifnot and not value):
-                    return self.nodelist_true.render(context)
-            return self.nodelist_false.render(context)
-        else:
-            for ifnot, bool_expr in self.bool_exprs:
-                try:
-                    value = bool_expr.resolve(context, True)
-                except VariableDoesNotExist:
-                    value = None
-                if not ((value and not ifnot) or (ifnot and not value)):
-                    return self.nodelist_false.render(context)
-            return self.nodelist_true.render(context)
-
-    class LinkTypes:
-        and_ = 0,
-        or_ = 1
-
-class RegroupNode(Node):
-    def __init__(self, target, expression, var_name):
-        self.target, self.expression = target, expression
-        self.var_name = var_name
-
-    def render(self, context):
-        obj_list = self.target.resolve(context, True)
-        if obj_list == None:
-            # target variable wasn't found in context; fail silently.
-            context[self.var_name] = []
-            return ''
-        # List of dictionaries in the format:
-        # {'grouper': 'key', 'list': [list of contents]}.
-        context[self.var_name] = [
-            {'grouper': key, 'list': list(val)}
-            for key, val in
-            groupby(obj_list, lambda v, f=self.expression.resolve: f(v, True))
-        ]
-        return ''
-
-def include_is_allowed(filepath):
-    for root in settings.ALLOWED_INCLUDE_ROOTS:
-        if filepath.startswith(root):
-            return True
-    return False
-
-class SsiNode(Node):
-    def __init__(self, filepath, parsed):
-        self.filepath, self.parsed = filepath, parsed
-
-    def render(self, context):
-        if not include_is_allowed(self.filepath):
-            if settings.DEBUG:
-                return "[Didn't have permission to include file]"
-            else:
-                return '' # Fail silently for invalid includes.
-        try:
-            fp = open(self.filepath, 'r')
-            output = fp.read()
-            fp.close()
-        except IOError:
-            output = ''
-        if self.parsed:
-            try:
-                t = Template(output, name=self.filepath)
-                return t.render(context)
-            except TemplateSyntaxError, e:
-                if settings.DEBUG:
-                    return "[Included template had syntax error: %s]" % e
-                else:
-                    return '' # Fail silently for invalid included templates.
-        return output
-
-class LoadNode(Node):
-    def render(self, context):
-        return ''
-
-class NowNode(Node):
-    def __init__(self, format_string):
-        self.format_string = format_string
-
-    def render(self, context):
-        from datetime import datetime
-        from django.utils.dateformat import DateFormat
-        df = DateFormat(datetime.now())
-        return df.format(self.format_string)
-
-class SpacelessNode(Node):
-    def __init__(self, nodelist):
-        self.nodelist = nodelist
-
-    def render(self, context):
-        from django.utils.html import strip_spaces_between_tags
-        return strip_spaces_between_tags(self.nodelist.render(context).strip())
-
-class TemplateTagNode(Node):
-    mapping = {'openblock': BLOCK_TAG_START,
-               'closeblock': BLOCK_TAG_END,
-               'openvariable': VARIABLE_TAG_START,
-               'closevariable': VARIABLE_TAG_END,
-               'openbrace': SINGLE_BRACE_START,
-               'closebrace': SINGLE_BRACE_END,
-               'opencomment': COMMENT_TAG_START,
-               'closecomment': COMMENT_TAG_END,
-               }
-
-    def __init__(self, tagtype):
-        self.tagtype = tagtype
-
-    def render(self, context):
-        return self.mapping.get(self.tagtype, '')
-
-class URLNode(Node):
-    def __init__(self, view_name, args, kwargs):
-        self.view_name = view_name
-        self.args = args
-        self.kwargs = kwargs
-
-    def render(self, context):
-        from django.core.urlresolvers import reverse, NoReverseMatch
-        args = [arg.resolve(context) for arg in self.args]
-        kwargs = dict([(smart_str(k,'ascii'), v.resolve(context))
-                       for k, v in self.kwargs.items()])
-        try:
-            return reverse(self.view_name, args=args, kwargs=kwargs)
-        except NoReverseMatch:
-            try:
-                project_name = settings.SETTINGS_MODULE.split('.')[0]
-                return reverse(project_name + '.' + self.view_name,
-                               args=args, kwargs=kwargs)
-            except NoReverseMatch:
-                return ''
-
-class WidthRatioNode(Node):
-    def __init__(self, val_expr, max_expr, max_width):
-        self.val_expr = val_expr
-        self.max_expr = max_expr
-        self.max_width = max_width
-
-    def render(self, context):
-        try:
-            value = self.val_expr.resolve(context)
-            maxvalue = self.max_expr.resolve(context)
-        except VariableDoesNotExist:
-            return ''
-        try:
-            value = float(value)
-            maxvalue = float(maxvalue)
-            ratio = (value / maxvalue) * int(self.max_width)
-        except (ValueError, ZeroDivisionError):
-            return ''
-        return str(int(round(ratio)))
-
-class WithNode(Node):
-    def __init__(self, var, name, nodelist):
-        self.var = var
-        self.name = name
-        self.nodelist = nodelist
-
-    def __repr__(self):
-        return "<WithNode>"
-
-    def render(self, context):
-        val = self.var.resolve(context)
-        context.push()
-        context[self.name] = val
-        output = self.nodelist.render(context)
-        context.pop()
-        return output
-
-#@register.tag
-def autoescape(parser, token):
-    """
-    Force autoescape behaviour for this block.
-    """
-    args = token.contents.split()
-    if len(args) != 2:
-        raise TemplateSyntaxError("'Autoescape' tag requires exactly one argument.")
-    arg = args[1]
-    if arg not in (u'on', u'off'):
-        raise TemplateSyntaxError("'Autoescape' argument should be 'on' or 'off'")
-    nodelist = parser.parse(('endautoescape',))
-    parser.delete_first_token()
-    return AutoEscapeControlNode((arg == 'on'), nodelist)
-autoescape = register.tag(autoescape)
-
-#@register.tag
-def comment(parser, token):
-    """
-    Ignores everything between ``{% comment %}`` and ``{% endcomment %}``.
-    """
-    parser.skip_past('endcomment')
-    return CommentNode()
-comment = register.tag(comment)
-
-#@register.tag
-def cycle(parser, token):
-    """
-    Cycles among the given strings each time this tag is encountered.
-
-    Within a loop, cycles among the given strings each time through
-    the loop::
-
-        {% for o in some_list %}
-            <tr class="{% cycle 'row1' 'row2' %}">
-                ...
-            </tr>
-        {% endfor %}
-
-    Outside of a loop, give the values a unique name the first time you call
-    it, then use that name each sucessive time through::
-
-            <tr class="{% cycle 'row1' 'row2' 'row3' as rowcolors %}">...</tr>
-            <tr class="{% cycle rowcolors %}">...</tr>
-            <tr class="{% cycle rowcolors %}">...</tr>
-
-    You can use any number of values, separated by spaces. Commas can also
-    be used to separate values; if a comma is used, the cycle values are
-    interpreted as literal strings.
-    """
-
-    # Note: This returns the exact same node on each {% cycle name %} call;
-    # that is, the node object returned from {% cycle a b c as name %} and the
-    # one returned from {% cycle name %} are the exact same object. This
-    # shouldn't cause problems (heh), but if it does, now you know.
-    #
-    # Ugly hack warning: This stuffs the named template dict into parser so
-    # that names are only unique within each template (as opposed to using
-    # a global variable, which would make cycle names have to be unique across
-    # *all* templates.
-
-    args = token.split_contents()
-
-    if len(args) < 2:
-        raise TemplateSyntaxError("'cycle' tag requires at least two arguments")
-
-    if ',' in args[1]:
-        # Backwards compatibility: {% cycle a,b %} or {% cycle a,b as foo %}
-        # case.
-        args[1:2] = ['"%s"' % arg for arg in args[1].split(",")]
-
-    if len(args) == 2:
-        # {% cycle foo %} case.
-        name = args[1]
-        if not hasattr(parser, '_namedCycleNodes'):
-            raise TemplateSyntaxError("No named cycles in template. '%s' is not defined" % name)
-        if not name in parser._namedCycleNodes:
-            raise TemplateSyntaxError("Named cycle '%s' does not exist" % name)
-        return parser._namedCycleNodes[name]
-
-    if len(args) > 4 and args[-2] == 'as':
-        name = args[-1]
-        node = CycleNode(args[1:-2], name)
-        if not hasattr(parser, '_namedCycleNodes'):
-            parser._namedCycleNodes = {}
-        parser._namedCycleNodes[name] = node
-    else:
-        node = CycleNode(args[1:])
-    return node
-cycle = register.tag(cycle)
-
-def debug(parser, token):
-    """
-    Outputs a whole load of debugging information, including the current
-    context and imported modules.
-
-    Sample usage::
-
-        <pre>
-            {% debug %}
-        </pre>
-    """
-    return DebugNode()
-debug = register.tag(debug)
-
-#@register.tag(name="filter")
-def do_filter(parser, token):
-    """
-    Filters the contents of the block through variable filters.
-
-    Filters can also be piped through each other, and they can have
-    arguments -- just like in variable syntax.
-
-    Sample usage::
-
-        {% filter force_escape|lower %}
-            This text will be HTML-escaped, and will appear in lowercase.
-        {% endfilter %}
-    """
-    _, rest = token.contents.split(None, 1)
-    filter_expr = parser.compile_filter("var|%s" % (rest))
-    for func, unused in filter_expr.filters:
-        if getattr(func, '_decorated_function', func).__name__ in ('escape', 'safe'):
-            raise TemplateSyntaxError('"filter %s" is not permitted.  Use the "autoescape" tag instead.' % func.__name__)
-    nodelist = parser.parse(('endfilter',))
-    parser.delete_first_token()
-    return FilterNode(filter_expr, nodelist)
-do_filter = register.tag("filter", do_filter)
-
-#@register.tag
-def firstof(parser, token):
-    """
-    Outputs the first variable passed that is not False.
-
-    Outputs nothing if all the passed variables are False.
-
-    Sample usage::
-
-        {% firstof var1 var2 var3 %}
-
-    This is equivalent to::
-
-        {% if var1 %}
-            {{ var1 }}
-        {% else %}{% if var2 %}
-            {{ var2 }}
-        {% else %}{% if var3 %}
-            {{ var3 }}
-        {% endif %}{% endif %}{% endif %}
-
-    but obviously much cleaner!
-
-    You can also use a literal string as a fallback value in case all
-    passed variables are False::
-
-        {% firstof var1 var2 var3 "fallback value" %}
-
-    """
-    bits = token.split_contents()[1:]
-    if len(bits) < 1:
-        raise TemplateSyntaxError("'firstof' statement requires at least one"
-                                  " argument")
-    return FirstOfNode(bits)
-firstof = register.tag(firstof)
-
-#@register.tag(name="for")
-def do_for(parser, token):
-    """
-    Loops over each item in an array.
-
-    For example, to display a list of athletes given ``athlete_list``::
-
-        <ul>
-        {% for athlete in athlete_list %}
-            <li>{{ athlete.name }}</li>
-        {% endfor %}
-        </ul>
-
-    You can loop over a list in reverse by using
-    ``{% for obj in list reversed %}``.
-
-    You can also unpack multiple values from a two-dimensional array::
-
-        {% for key,value in dict.items %}
-            {{ key }}: {{ value }}
-        {% endfor %}
-
-    The for loop sets a number of variables available within the loop:
-
-        ==========================  ================================================
-        Variable                    Description
-        ==========================  ================================================
-        ``forloop.counter``         The current iteration of the loop (1-indexed)
-        ``forloop.counter0``        The current iteration of the loop (0-indexed)
-        ``forloop.revcounter``      The number of iterations from the end of the
-                                    loop (1-indexed)
-        ``forloop.revcounter0``     The number of iterations from the end of the
-                                    loop (0-indexed)
-        ``forloop.first``           True if this is the first time through the loop
-        ``forloop.last``            True if this is the last time through the loop
-        ``forloop.parentloop``      For nested loops, this is the loop "above" the
-                                    current one
-        ==========================  ================================================
-
-    """
-    bits = token.contents.split()
-    if len(bits) < 4:
-        raise TemplateSyntaxError("'for' statements should have at least four"
-                                  " words: %s" % token.contents)
-
-    is_reversed = bits[-1] == 'reversed'
-    in_index = is_reversed and -3 or -2
-    if bits[in_index] != 'in':
-        raise TemplateSyntaxError("'for' statements should use the format"
-                                  " 'for x in y': %s" % token.contents)
-
-    loopvars = re.sub(r' *, *', ',', ' '.join(bits[1:in_index])).split(',')
-    for var in loopvars:
-        if not var or ' ' in var:
-            raise TemplateSyntaxError("'for' tag received an invalid argument:"
-                                      " %s" % token.contents)
-
-    sequence = parser.compile_filter(bits[in_index+1])
-    nodelist_loop = parser.parse(('endfor',))
-    parser.delete_first_token()
-    return ForNode(loopvars, sequence, is_reversed, nodelist_loop)
-do_for = register.tag("for", do_for)
-
-def do_ifequal(parser, token, negate):
-    bits = list(token.split_contents())
-    if len(bits) != 3:
-        raise TemplateSyntaxError, "%r takes two arguments" % bits[0]
-    end_tag = 'end' + bits[0]
-    nodelist_true = parser.parse(('else', end_tag))
-    token = parser.next_token()
-    if token.contents == 'else':
-        nodelist_false = parser.parse((end_tag,))
-        parser.delete_first_token()
-    else:
-        nodelist_false = NodeList()
-    return IfEqualNode(bits[1], bits[2], nodelist_true, nodelist_false, negate)
-
-#@register.tag
-def ifequal(parser, token):
-    """
-    Outputs the contents of the block if the two arguments equal each other.
-
-    Examples::
-
-        {% ifequal user.id comment.user_id %}
-            ...
-        {% endifequal %}
-
-        {% ifnotequal user.id comment.user_id %}
-            ...
-        {% else %}
-            ...
-        {% endifnotequal %}
-    """
-    return do_ifequal(parser, token, False)
-ifequal = register.tag(ifequal)
-
-#@register.tag
-def ifnotequal(parser, token):
-    """
-    Outputs the contents of the block if the two arguments are not equal.
-    See ifequal.
-    """
-    return do_ifequal(parser, token, True)
-ifnotequal = register.tag(ifnotequal)
-
-#@register.tag(name="if")
-def do_if(parser, token):
-    """
-    The ``{% if %}`` tag evaluates a variable, and if that variable is "true"
-    (i.e., exists, is not empty, and is not a false boolean value), the
-    contents of the block are output:
-
-    ::
-
-        {% if athlete_list %}
-            Number of athletes: {{ athlete_list|count }}
-        {% else %}
-            No athletes.
-        {% endif %}
-
-    In the above, if ``athlete_list`` is not empty, the number of athletes will
-    be displayed by the ``{{ athlete_list|count }}`` variable.
-
-    As you can see, the ``if`` tag can take an option ``{% else %}`` clause
-    that will be displayed if the test fails.
-
-    ``if`` tags may use ``or``, ``and`` or ``not`` to test a number of
-    variables or to negate a given variable::
-
-        {% if not athlete_list %}
-            There are no athletes.
-        {% endif %}
-
-        {% if athlete_list or coach_list %}
-            There are some athletes or some coaches.
-        {% endif %}
-
-        {% if athlete_list and coach_list %}
-            Both atheletes and coaches are available.
-        {% endif %}
-
-        {% if not athlete_list or coach_list %}
-            There are no athletes, or there are some coaches.
-        {% endif %}
-
-        {% if athlete_list and not coach_list %}
-            There are some athletes and absolutely no coaches.
-        {% endif %}
-
-    ``if`` tags do not allow ``and`` and ``or`` clauses with the same tag,
-    because the order of logic would be ambigous. For example, this is
-    invalid::
-
-        {% if athlete_list and coach_list or cheerleader_list %}
-
-    If you need to combine ``and`` and ``or`` to do advanced logic, just use
-    nested if tags. For example::
-
-        {% if athlete_list %}
-            {% if coach_list or cheerleader_list %}
-                We have athletes, and either coaches or cheerleaders!
-            {% endif %}
-        {% endif %}
-    """
-    bits = token.contents.split()
-    del bits[0]
-    if not bits:
-        raise TemplateSyntaxError("'if' statement requires at least one argument")
-    # Bits now looks something like this: ['a', 'or', 'not', 'b', 'or', 'c.d']
-    bitstr = ' '.join(bits)
-    boolpairs = bitstr.split(' and ')
-    boolvars = []
-    if len(boolpairs) == 1:
-        link_type = IfNode.LinkTypes.or_
-        boolpairs = bitstr.split(' or ')
-    else:
-        link_type = IfNode.LinkTypes.and_
-        if ' or ' in bitstr:
-            raise TemplateSyntaxError, "'if' tags can't mix 'and' and 'or'"
-    for boolpair in boolpairs:
-        if ' ' in boolpair:
-            try:
-                not_, boolvar = boolpair.split()
-            except ValueError:
-                raise TemplateSyntaxError, "'if' statement improperly formatted"
-            if not_ != 'not':
-                raise TemplateSyntaxError, "Expected 'not' in if statement"
-            boolvars.append((True, parser.compile_filter(boolvar)))
-        else:
-            boolvars.append((False, parser.compile_filter(boolpair)))
-    nodelist_true = parser.parse(('else', 'endif'))
-    token = parser.next_token()
-    if token.contents == 'else':
-        nodelist_false = parser.parse(('endif',))
-        parser.delete_first_token()
-    else:
-        nodelist_false = NodeList()
-    return IfNode(boolvars, nodelist_true, nodelist_false, link_type)
-do_if = register.tag("if", do_if)
-
-#@register.tag
-def ifchanged(parser, token):
-    """
-    Checks if a value has changed from the last iteration of a loop.
-
-    The 'ifchanged' block tag is used within a loop. It has two possible uses.
-
-    1. Checks its own rendered contents against its previous state and only
-       displays the content if it has changed. For example, this displays a
-       list of days, only displaying the month if it changes::
-
-            <h1>Archive for {{ year }}</h1>
-
-            {% for date in days %}
-                {% ifchanged %}<h3>{{ date|date:"F" }}</h3>{% endifchanged %}
-                <a href="{{ date|date:"M/d"|lower }}/">{{ date|date:"j" }}</a>
-            {% endfor %}
-
-    2. If given a variable, check whether that variable has changed.
-       For example, the following shows the date every time it changes, but
-       only shows the hour if both the hour and the date have changed::
-
-            {% for date in days %}
-                {% ifchanged date.date %} {{ date.date }} {% endifchanged %}
-                {% ifchanged date.hour date.date %}
-                    {{ date.hour }}
-                {% endifchanged %}
-            {% endfor %}
-    """
-    bits = token.contents.split()
-    nodelist = parser.parse(('endifchanged',))
-    parser.delete_first_token()
-    return IfChangedNode(nodelist, *bits[1:])
-ifchanged = register.tag(ifchanged)
-
-#@register.tag
-def ssi(parser, token):
-    """
-    Outputs the contents of a given file into the page.
-
-    Like a simple "include" tag, the ``ssi`` tag includes the contents
-    of another file -- which must be specified using an absolute path --
-    in the current page::
-
-        {% ssi /home/html/ljworld.com/includes/right_generic.html %}
-
-    If the optional "parsed" parameter is given, the contents of the included
-    file are evaluated as template code, with the current context::
-
-        {% ssi /home/html/ljworld.com/includes/right_generic.html parsed %}
-    """
-    bits = token.contents.split()
-    parsed = False
-    if len(bits) not in (2, 3):
-        raise TemplateSyntaxError("'ssi' tag takes one argument: the path to"
-                                  " the file to be included")
-    if len(bits) == 3:
-        if bits[2] == 'parsed':
-            parsed = True
-        else:
-            raise TemplateSyntaxError("Second (optional) argument to %s tag"
-                                      " must be 'parsed'" % bits[0])
-    return SsiNode(bits[1], parsed)
-ssi = register.tag(ssi)
-
-#@register.tag
-def load(parser, token):
-    """
-    Loads a custom template tag set.
-
-    For example, to load the template tags in
-    ``django/templatetags/news/photos.py``::
-
-        {% load news.photos %}
-    """
-    bits = token.contents.split()
-    for taglib in bits[1:]:
-        # add the library to the parser
-        try:
-            lib = get_library("django.templatetags.%s" % taglib)
-            parser.add_library(lib)
-        except InvalidTemplateLibrary, e:
-            raise TemplateSyntaxError("'%s' is not a valid tag library: %s" %
-                                      (taglib, e))
-    return LoadNode()
-load = register.tag(load)
-
-#@register.tag
-def now(parser, token):
-    """
-    Displays the date, formatted according to the given string.
-
-    Uses the same format as PHP's ``date()`` function; see http://php.net/date
-    for all the possible values.
-
-    Sample usage::
-
-        It is {% now "jS F Y H:i" %}
-    """
-    bits = token.contents.split('"')
-    if len(bits) != 3:
-        raise TemplateSyntaxError, "'now' statement takes one argument"
-    format_string = bits[1]
-    return NowNode(format_string)
-now = register.tag(now)
-
-#@register.tag
-def regroup(parser, token):
-    """
-    Regroups a list of alike objects by a common attribute.
-
-    This complex tag is best illustrated by use of an example:  say that
-    ``people`` is a list of ``Person`` objects that have ``first_name``,
-    ``last_name``, and ``gender`` attributes, and you'd like to display a list
-    that looks like:
-
-        * Male:
-            * George Bush
-            * Bill Clinton
-        * Female:
-            * Margaret Thatcher
-            * Colendeeza Rice
-        * Unknown:
-            * Pat Smith
-
-    The following snippet of template code would accomplish this dubious task::
-
-        {% regroup people by gender as grouped %}
-        <ul>
-        {% for group in grouped %}
-            <li>{{ group.grouper }}
-            <ul>
-                {% for item in group.list %}
-                <li>{{ item }}</li>
-                {% endfor %}
-            </ul>
-        {% endfor %}
-        </ul>
-
-    As you can see, ``{% regroup %}`` populates a variable with a list of
-    objects with ``grouper`` and ``list`` attributes.  ``grouper`` contains the
-    item that was grouped by; ``list`` contains the list of objects that share
-    that ``grouper``.  In this case, ``grouper`` would be ``Male``, ``Female``
-    and ``Unknown``, and ``list`` is the list of people with those genders.
-
-    Note that `{% regroup %}`` does not work when the list to be grouped is not
-    sorted by the key you are grouping by!  This means that if your list of
-    people was not sorted by gender, you'd need to make sure it is sorted
-    before using it, i.e.::
-
-        {% regroup people|dictsort:"gender" by gender as grouped %}
-
-    """
-    firstbits = token.contents.split(None, 3)
-    if len(firstbits) != 4:
-        raise TemplateSyntaxError, "'regroup' tag takes five arguments"
-    target = parser.compile_filter(firstbits[1])
-    if firstbits[2] != 'by':
-        raise TemplateSyntaxError("second argument to 'regroup' tag must be 'by'")
-    lastbits_reversed = firstbits[3][::-1].split(None, 2)
-    if lastbits_reversed[1][::-1] != 'as':
-        raise TemplateSyntaxError("next-to-last argument to 'regroup' tag must"
-                                  " be 'as'")
-
-    expression = parser.compile_filter(lastbits_reversed[2][::-1])
-
-    var_name = lastbits_reversed[0][::-1]
-    return RegroupNode(target, expression, var_name)
-regroup = register.tag(regroup)
-
-def spaceless(parser, token):
-    """
-    Removes whitespace between HTML tags, including tab and newline characters.
-
-    Example usage::
-
-        {% spaceless %}
-            <p>
-                <a href="foo/">Foo</a>
-            </p>
-        {% endspaceless %}
-
-    This example would return this HTML::
-
-        <p><a href="foo/">Foo</a></p>
-
-    Only space between *tags* is normalized -- not space between tags and text.
-    In this example, the space around ``Hello`` won't be stripped::
-
-        {% spaceless %}
-            <strong>
-                Hello
-            </strong>
-        {% endspaceless %}
-    """
-    nodelist = parser.parse(('endspaceless',))
-    parser.delete_first_token()
-    return SpacelessNode(nodelist)
-spaceless = register.tag(spaceless)
-
-#@register.tag
-def templatetag(parser, token):
-    """
-    Outputs one of the bits used to compose template tags.
-
-    Since the template system has no concept of "escaping", to display one of
-    the bits used in template tags, you must use the ``{% templatetag %}`` tag.
-
-    The argument tells which template bit to output:
-
-        ==================  =======
-        Argument            Outputs
-        ==================  =======
-        ``openblock``       ``{%``
-        ``closeblock``      ``%}``
-        ``openvariable``    ``{{``
-        ``closevariable``   ``}}``
-        ``openbrace``       ``{``
-        ``closebrace``      ``}``
-        ``opencomment``     ``{#``
-        ``closecomment``    ``#}``
-        ==================  =======
-    """
-    bits = token.contents.split()
-    if len(bits) != 2:
-        raise TemplateSyntaxError, "'templatetag' statement takes one argument"
-    tag = bits[1]
-    if tag not in TemplateTagNode.mapping:
-        raise TemplateSyntaxError("Invalid templatetag argument: '%s'."
-                                  " Must be one of: %s" %
-                                  (tag, TemplateTagNode.mapping.keys()))
-    return TemplateTagNode(tag)
-templatetag = register.tag(templatetag)
-
-def url(parser, token):
-    """
-    Returns an absolute URL matching given view with its parameters.
-
-    This is a way to define links that aren't tied to a particular URL
-    configuration::
-
-        {% url path.to.some_view arg1,arg2,name1=value1 %}
-
-    The first argument is a path to a view. It can be an absolute python path
-    or just ``app_name.view_name`` without the project name if the view is
-    located inside the project.  Other arguments are comma-separated values
-    that will be filled in place of positional and keyword arguments in the
-    URL. All arguments for the URL should be present.
-
-    For example if you have a view ``app_name.client`` taking client's id and
-    the corresponding line in a URLconf looks like this::
-
-        ('^client/(\d+)/$', 'app_name.client')
-
-    and this app's URLconf is included into the project's URLconf under some
-    path::
-
-        ('^clients/', include('project_name.app_name.urls'))
-
-    then in a template you can create a link for a certain client like this::
-
-        {% url app_name.client client.id %}
-
-    The URL will look like ``/clients/client/123/``.
-    """
-    bits = token.contents.split(' ', 2)
-    if len(bits) < 2:
-        raise TemplateSyntaxError("'%s' takes at least one argument"
-                                  " (path to a view)" % bits[0])
-    args = []
-    kwargs = {}
-    if len(bits) > 2:
-        for arg in bits[2].split(','):
-            if '=' in arg:
-                k, v = arg.split('=', 1)
-                k = k.strip()
-                kwargs[k] = parser.compile_filter(v)
-            else:
-                args.append(parser.compile_filter(arg))
-    return URLNode(bits[1], args, kwargs)
-url = register.tag(url)
-
-#@register.tag
-def widthratio(parser, token):
-    """
-    For creating bar charts and such, this tag calculates the ratio of a given
-    value to a maximum value, and then applies that ratio to a constant.
-
-    For example::
-
-        <img src='bar.gif' height='10' width='{% widthratio this_value max_value 100 %}' />
-
-    Above, if ``this_value`` is 175 and ``max_value`` is 200, the the image in
-    the above example will be 88 pixels wide (because 175/200 = .875;
-    .875 * 100 = 87.5 which is rounded up to 88).
-    """
-    bits = token.contents.split()
-    if len(bits) != 4:
-        raise TemplateSyntaxError("widthratio takes three arguments")
-    tag, this_value_expr, max_value_expr, max_width = bits
-    try:
-        max_width = int(max_width)
-    except ValueError:
-        raise TemplateSyntaxError("widthratio final argument must be an integer")
-    return WidthRatioNode(parser.compile_filter(this_value_expr),
-                          parser.compile_filter(max_value_expr), max_width)
-widthratio = register.tag(widthratio)
-
-#@register.tag
-def do_with(parser, token):
-    """
-    Adds a value to the context (inside of this block) for caching and easy
-    access.
-
-    For example::
-
-        {% with person.some_sql_method as total %}
-            {{ total }} object{{ total|pluralize }}
-        {% endwith %}
-    """
-    bits = list(token.split_contents())
-    if len(bits) != 4 or bits[2] != "as":
-        raise TemplateSyntaxError("%r expected format is 'value as name'" %
-                                  bits[0])
-    var = parser.compile_filter(bits[1])
-    name = bits[3]
-    nodelist = parser.parse(('endwith',))
-    parser.delete_first_token()
-    return WithNode(var, name, nodelist)
-do_with = register.tag('with', do_with)
diff --git a/django/template/loader.py b/django/template/loader.py
index 1d7d945..8f0d55a 100644
--- a/django/template/loader.py
+++ b/django/template/loader.py
@@ -116,4 +116,4 @@ def select_template(template_name_list):
     # If we get here, none of the templates could be loaded
     raise TemplateDoesNotExist, ', '.join(template_name_list)
 
-add_to_builtins('django.template.loader_tags')
+add_to_builtins('django.template', 'loader_tags')
diff --git a/django/templatetags/__init__.py b/django/templatetags/__init__.py
index 9204535..37b3dc4 100644
--- a/django/templatetags/__init__.py
+++ b/django/templatetags/__init__.py
@@ -1,7 +1,19 @@
+import imp
 from django.conf import settings
 
-for a in settings.INSTALLED_APPS:
-    try:
-        __path__.extend(__import__(a + '.templatetags', {}, {}, ['']).__path__)
-    except ImportError:
-        pass
+templatetags_modules= [] 
+
+def get_templatetags_modules():
+    if not templatetags_modules:
+        """ Populate list once per thread. """
+        for app_module in ['django'] + list(settings.INSTALLED_APPS):
+            try:
+                components = app_module.split('.')
+                mod = __import__(app_module, {}, {}, components[-1])
+                imp.find_module('templatetags', mod.__path__)
+                templatetag_module = '%s.templatetags' % app_module
+                templatetags_modules.append(templatetag_module)
+            except ImportError:
+                continue
+            __import__(templatetag_module) 
+    return templatetags_modules 
diff --git a/django/templatetags/defaultfilters.py b/django/templatetags/defaultfilters.py
new file mode 100644
index 0000000..cef3143
--- /dev/null
+++ b/django/templatetags/defaultfilters.py
@@ -0,0 +1,851 @@
+"""Default variable filters."""
+
+import re
+import random as random_module
+try:
+    from functools import wraps
+except ImportError:
+    from django.utils.functional import wraps  # Python 2.3, 2.4 fallback.
+
+from django.template import Variable, Library
+from django.conf import settings
+from django.utils.translation import ugettext, ungettext
+from django.utils.encoding import force_unicode, iri_to_uri
+from django.utils.safestring import mark_safe, SafeData
+
+register = Library()
+
+#######################
+# STRING DECORATOR    #
+#######################
+
+def stringfilter(func):
+    """
+    Decorator for filters which should only receive unicode objects. The object
+    passed as the first positional argument will be converted to a unicode
+    object.
+    """
+    def _dec(*args, **kwargs):
+        if args:
+            args = list(args)
+            args[0] = force_unicode(args[0])
+            if isinstance(args[0], SafeData) and getattr(func, 'is_safe', False):
+                return mark_safe(func(*args, **kwargs))
+        return func(*args, **kwargs)
+
+    # Include a reference to the real function (used to check original
+    # arguments by the template parser).
+    _dec._decorated_function = getattr(func, '_decorated_function', func)
+    for attr in ('is_safe', 'needs_autoescape'):
+        if hasattr(func, attr):
+            setattr(_dec, attr, getattr(func, attr))
+    return wraps(func)(_dec)
+
+###################
+# STRINGS         #
+###################
+
+
+def addslashes(value):
+    """
+    Adds slashes before quotes. Useful for escaping strings in CSV, for
+    example. Less useful for escaping JavaScript; use the ``escapejs``
+    filter instead.
+    """
+    return value.replace('\\', '\\\\').replace('"', '\\"').replace("'", "\\'")
+addslashes.is_safe = True
+addslashes = stringfilter(addslashes)
+
+def capfirst(value):
+    """Capitalizes the first character of the value."""
+    return value and value[0].upper() + value[1:]
+capfirst.is_safe=True
+capfirst = stringfilter(capfirst)
+
+_js_escapes = (
+    ('\\', '\\\\'),
+    ('"', '\\"'),
+    ("'", "\\'"),
+    ('\n', '\\n'),
+    ('\r', '\\r'),
+    ('\b', '\\b'),
+    ('\f', '\\f'),
+    ('\t', '\\t'),
+    ('\v', '\\v'),
+    ('</', '<\\/'),
+)
+def escapejs(value):
+    """Backslash-escapes characters for use in JavaScript strings."""
+    for bad, good in _js_escapes:
+        value = value.replace(bad, good)
+    return value
+escapejs = stringfilter(escapejs)
+
+def fix_ampersands(value):
+    """Replaces ampersands with ``&amp;`` entities."""
+    from django.utils.html import fix_ampersands
+    return fix_ampersands(value)
+fix_ampersands.is_safe=True
+fix_ampersands = stringfilter(fix_ampersands)
+
+def floatformat(text, arg=-1):
+    """
+    Displays a float to a specified number of decimal places.
+
+    If called without an argument, it displays the floating point number with
+    one decimal place -- but only if there's a decimal place to be displayed:
+
+    * num1 = 34.23234
+    * num2 = 34.00000
+    * num3 = 34.26000
+    * {{ num1|floatformat }} displays "34.2"
+    * {{ num2|floatformat }} displays "34"
+    * {{ num3|floatformat }} displays "34.3"
+
+    If arg is positive, it will always display exactly arg number of decimal
+    places:
+
+    * {{ num1|floatformat:3 }} displays "34.232"
+    * {{ num2|floatformat:3 }} displays "34.000"
+    * {{ num3|floatformat:3 }} displays "34.260"
+
+    If arg is negative, it will display arg number of decimal places -- but
+    only if there are places to be displayed:
+
+    * {{ num1|floatformat:"-3" }} displays "34.232"
+    * {{ num2|floatformat:"-3" }} displays "34"
+    * {{ num3|floatformat:"-3" }} displays "34.260"
+    """
+    try:
+        f = float(text)
+    except (ValueError, TypeError):
+        return u''
+    try:
+        d = int(arg)
+    except ValueError:
+        return force_unicode(f)
+    try:
+        m = f - int(f)
+    except OverflowError:
+        return force_unicode(f)
+    if not m and d < 0:
+        return mark_safe(u'%d' % int(f))
+    else:
+        formatstr = u'%%.%df' % abs(d)
+        return mark_safe(formatstr % f)
+floatformat.is_safe = True
+
+def iriencode(value):
+    """Escapes an IRI value for use in a URL."""
+    return force_unicode(iri_to_uri(value))
+iriencode.is_safe = True
+iriencode = stringfilter(iriencode)
+
+def linenumbers(value, autoescape=None):
+    """Displays text with line numbers."""
+    from django.utils.html import escape
+    lines = value.split(u'\n')
+    # Find the maximum width of the line count, for use with zero padding
+    # string format command
+    width = unicode(len(unicode(len(lines))))
+    if not autoescape or isinstance(value, SafeData):
+        for i, line in enumerate(lines):
+            lines[i] = (u"%0" + width  + u"d. %s") % (i + 1, line)
+    else:
+        for i, line in enumerate(lines):
+            lines[i] = (u"%0" + width  + u"d. %s") % (i + 1, escape(line))
+    return mark_safe(u'\n'.join(lines))
+linenumbers.is_safe = True
+linenumbers.needs_autoescape = True
+linenumbers = stringfilter(linenumbers)
+
+def lower(value):
+    """Converts a string into all lowercase."""
+    return value.lower()
+lower.is_safe = True
+lower = stringfilter(lower)
+
+def make_list(value):
+    """
+    Returns the value turned into a list.
+
+    For an integer, it's a list of digits.
+    For a string, it's a list of characters.
+    """
+    return list(value)
+make_list.is_safe = False
+make_list = stringfilter(make_list)
+
+def slugify(value):
+    """
+    Normalizes string, converts to lowercase, removes non-alpha characters,
+    and converts spaces to hyphens.
+    """
+    import unicodedata
+    value = unicodedata.normalize('NFKD', value).encode('ascii', 'ignore')
+    value = unicode(re.sub('[^\w\s-]', '', value).strip().lower())
+    return mark_safe(re.sub('[-\s]+', '-', value))
+slugify.is_safe = True
+slugify = stringfilter(slugify)
+
+def stringformat(value, arg):
+    """
+    Formats the variable according to the arg, a string formatting specifier.
+
+    This specifier uses Python string formating syntax, with the exception that
+    the leading "%" is dropped.
+
+    See http://docs.python.org/lib/typesseq-strings.html for documentation
+    of Python string formatting
+    """
+    try:
+        return (u"%" + unicode(arg)) % value
+    except (ValueError, TypeError):
+        return u""
+stringformat.is_safe = True
+
+def title(value):
+    """Converts a string into titlecase."""
+    return re.sub("([a-z])'([A-Z])", lambda m: m.group(0).lower(), value.title())
+title.is_safe = True
+title = stringfilter(title)
+
+def truncatewords(value, arg):
+    """
+    Truncates a string after a certain number of words.
+
+    Argument: Number of words to truncate after.
+    """
+    from django.utils.text import truncate_words
+    try:
+        length = int(arg)
+    except ValueError: # Invalid literal for int().
+        return value # Fail silently.
+    return truncate_words(value, length)
+truncatewords.is_safe = True
+truncatewords = stringfilter(truncatewords)
+
+def truncatewords_html(value, arg):
+    """
+    Truncates HTML after a certain number of words.
+
+    Argument: Number of words to truncate after.
+    """
+    from django.utils.text import truncate_html_words
+    try:
+        length = int(arg)
+    except ValueError: # invalid literal for int()
+        return value # Fail silently.
+    return truncate_html_words(value, length)
+truncatewords_html.is_safe = True
+truncatewords_html = stringfilter(truncatewords_html)
+
+def upper(value):
+    """Converts a string into all uppercase."""
+    return value.upper()
+upper.is_safe = False
+upper = stringfilter(upper)
+
+def urlencode(value):
+    """Escapes a value for use in a URL."""
+    from django.utils.http import urlquote
+    return urlquote(value)
+urlencode.is_safe = False
+urlencode = stringfilter(urlencode)
+
+def urlize(value, autoescape=None):
+    """Converts URLs in plain text into clickable links."""
+    from django.utils.html import urlize
+    return mark_safe(urlize(value, nofollow=True, autoescape=autoescape))
+urlize.is_safe=True
+urlize.needs_autoescape = True
+urlize = stringfilter(urlize)
+
+def urlizetrunc(value, limit, autoescape=None):
+    """
+    Converts URLs into clickable links, truncating URLs to the given character
+    limit, and adding 'rel=nofollow' attribute to discourage spamming.
+
+    Argument: Length to truncate URLs to.
+    """
+    from django.utils.html import urlize
+    return mark_safe(urlize(value, trim_url_limit=int(limit), nofollow=True,
+                            autoescape=autoescape))
+urlizetrunc.is_safe = True
+urlizetrunc.needs_autoescape = True
+urlizetrunc = stringfilter(urlizetrunc)
+
+def wordcount(value):
+    """Returns the number of words."""
+    return len(value.split())
+wordcount.is_safe = False
+wordcount = stringfilter(wordcount)
+
+def wordwrap(value, arg):
+    """
+    Wraps words at specified line length.
+
+    Argument: number of characters to wrap the text at.
+    """
+    from django.utils.text import wrap
+    return wrap(value, int(arg))
+wordwrap.is_safe = True
+wordwrap = stringfilter(wordwrap)
+
+def ljust(value, arg):
+    """
+    Left-aligns the value in a field of a given width.
+
+    Argument: field size.
+    """
+    return value.ljust(int(arg))
+ljust.is_safe = True
+ljust = stringfilter(ljust)
+
+def rjust(value, arg):
+    """
+    Right-aligns the value in a field of a given width.
+
+    Argument: field size.
+    """
+    return value.rjust(int(arg))
+rjust.is_safe = True
+rjust = stringfilter(rjust)
+
+def center(value, arg):
+    """Centers the value in a field of a given width."""
+    return value.center(int(arg))
+center.is_safe = True
+center = stringfilter(center)
+
+def cut(value, arg):
+    """
+    Removes all values of arg from the given string.
+    """
+    safe = isinstance(value, SafeData)
+    value = value.replace(arg, u'')
+    if safe and arg != ';':
+        return mark_safe(value)
+    return value
+cut = stringfilter(cut)
+
+###################
+# HTML STRINGS    #
+###################
+
+def escape(value):
+    """
+    Marks the value as a string that should not be auto-escaped.
+    """
+    from django.utils.safestring import mark_for_escaping
+    return mark_for_escaping(value)
+escape.is_safe = True
+escape = stringfilter(escape)
+
+def force_escape(value):
+    """
+    Escapes a string's HTML. This returns a new string containing the escaped
+    characters (as opposed to "escape", which marks the content for later
+    possible escaping).
+    """
+    from django.utils.html import escape
+    return mark_safe(escape(value))
+force_escape = stringfilter(force_escape)
+force_escape.is_safe = True
+
+def linebreaks(value, autoescape=None):
+    """
+    Replaces line breaks in plain text with appropriate HTML; a single
+    newline becomes an HTML line break (``<br />``) and a new line
+    followed by a blank line becomes a paragraph break (``</p>``).
+    """
+    from django.utils.html import linebreaks
+    autoescape = autoescape and not isinstance(value, SafeData)
+    return mark_safe(linebreaks(value, autoescape))
+linebreaks.is_safe = True
+linebreaks.needs_autoescape = True
+linebreaks = stringfilter(linebreaks)
+
+def linebreaksbr(value, autoescape=None):
+    """
+    Converts all newlines in a piece of plain text to HTML line breaks
+    (``<br />``).
+    """
+    if autoescape and not isinstance(value, SafeData):
+        from django.utils.html import escape
+        value = escape(value)
+    return mark_safe(value.replace('\n', '<br />'))
+linebreaksbr.is_safe = True
+linebreaksbr.needs_autoescape = True
+linebreaksbr = stringfilter(linebreaksbr)
+
+def safe(value):
+    """
+    Marks the value as a string that should not be auto-escaped.
+    """
+    from django.utils.safestring import mark_safe
+    return mark_safe(value)
+safe.is_safe = True
+safe = stringfilter(safe)
+
+def removetags(value, tags):
+    """Removes a space separated list of [X]HTML tags from the output."""
+    tags = [re.escape(tag) for tag in tags.split()]
+    tags_re = u'(%s)' % u'|'.join(tags)
+    starttag_re = re.compile(ur'<%s(/?>|(\s+[^>]*>))' % tags_re, re.U)
+    endtag_re = re.compile(u'</%s>' % tags_re)
+    value = starttag_re.sub(u'', value)
+    value = endtag_re.sub(u'', value)
+    return value
+removetags.is_safe = True
+removetags = stringfilter(removetags)
+
+def striptags(value):
+    """Strips all [X]HTML tags."""
+    from django.utils.html import strip_tags
+    return strip_tags(value)
+striptags.is_safe = True
+striptags = stringfilter(striptags)
+
+###################
+# LISTS           #
+###################
+
+def dictsort(value, arg):
+    """
+    Takes a list of dicts, returns that list sorted by the property given in
+    the argument.
+    """
+    var_resolve = Variable(arg).resolve
+    decorated = [(var_resolve(item), item) for item in value]
+    decorated.sort()
+    return [item[1] for item in decorated]
+dictsort.is_safe = False
+
+def dictsortreversed(value, arg):
+    """
+    Takes a list of dicts, returns that list sorted in reverse order by the
+    property given in the argument.
+    """
+    var_resolve = Variable(arg).resolve
+    decorated = [(var_resolve(item), item) for item in value]
+    decorated.sort()
+    decorated.reverse()
+    return [item[1] for item in decorated]
+dictsortreversed.is_safe = False
+
+def first(value):
+    """Returns the first item in a list."""
+    try:
+        return value[0]
+    except IndexError:
+        return u''
+first.is_safe = False
+
+def join(value, arg):
+    """Joins a list with a string, like Python's ``str.join(list)``."""
+    try:
+        data = arg.join(map(force_unicode, value))
+    except AttributeError: # fail silently but nicely
+        return value
+    safe_args = reduce(lambda lhs, rhs: lhs and isinstance(rhs, SafeData),
+            value, True)
+    if safe_args:
+        return mark_safe(data)
+    else:
+        return data
+join.is_safe = True
+
+def last(value):
+    "Returns the last item in a list"
+    try:
+        return value[-1]
+    except IndexError:
+        return u''
+last.is_safe = True
+
+def length(value):
+    """Returns the length of the value - useful for lists."""
+    return len(value)
+length.is_safe = True
+
+def length_is(value, arg):
+    """Returns a boolean of whether the value's length is the argument."""
+    return len(value) == int(arg)
+length_is.is_safe = True
+
+def random(value):
+    """Returns a random item from the list."""
+    return random_module.choice(value)
+random.is_safe = True
+
+def slice_(value, arg):
+    """
+    Returns a slice of the list.
+
+    Uses the same syntax as Python's list slicing; see
+    http://diveintopython.org/native_data_types/lists.html#odbchelper.list.slice
+    for an introduction.
+    """
+    try:
+        bits = []
+        for x in arg.split(u':'):
+            if len(x) == 0:
+                bits.append(None)
+            else:
+                bits.append(int(x))
+        return value[slice(*bits)]
+
+    except (ValueError, TypeError):
+        return value # Fail silently.
+slice_.is_safe = True
+
+def unordered_list(value, autoescape=None):
+    """
+    Recursively takes a self-nested list and returns an HTML unordered list --
+    WITHOUT opening and closing <ul> tags.
+
+    The list is assumed to be in the proper format. For example, if ``var``
+    contains: ``['States', ['Kansas', ['Lawrence', 'Topeka'], 'Illinois']]``,
+    then ``{{ var|unordered_list }}`` would return::
+
+        <li>States
+        <ul>
+                <li>Kansas
+                <ul>
+                        <li>Lawrence</li>
+                        <li>Topeka</li>
+                </ul>
+                </li>
+                <li>Illinois</li>
+        </ul>
+        </li>
+    """
+    if autoescape:
+        from django.utils.html import conditional_escape
+        escaper = conditional_escape
+    else:
+        escaper = lambda x: x
+    def convert_old_style_list(list_):
+        """
+        Converts old style lists to the new easier to understand format.
+
+        The old list format looked like:
+            ['Item 1', [['Item 1.1', []], ['Item 1.2', []]]
+
+        And it is converted to:
+            ['Item 1', ['Item 1.1', 'Item 1.2]]
+        """
+        if not isinstance(list_, (tuple, list)) or len(list_) != 2:
+            return list_, False
+        first_item, second_item = list_
+        if second_item == []:
+            return [first_item], True
+        old_style_list = True
+        new_second_item = []
+        for sublist in second_item:
+            item, old_style_list = convert_old_style_list(sublist)
+            if not old_style_list:
+                break
+            new_second_item.extend(item)
+        if old_style_list:
+            second_item = new_second_item
+        return [first_item, second_item], old_style_list
+    def _helper(list_, tabs=1):
+        indent = u'\t' * tabs
+        output = []
+
+        list_length = len(list_)
+        i = 0
+        while i < list_length:
+            title = list_[i]
+            sublist = ''
+            sublist_item = None
+            if isinstance(title, (list, tuple)):
+                sublist_item = title
+                title = ''
+            elif i < list_length - 1:
+                next_item = list_[i+1]
+                if next_item and isinstance(next_item, (list, tuple)):
+                    # The next item is a sub-list.
+                    sublist_item = next_item
+                    # We've processed the next item now too.
+                    i += 1
+            if sublist_item:
+                sublist = _helper(sublist_item, tabs+1)
+                sublist = '\n%s<ul>\n%s\n%s</ul>\n%s' % (indent, sublist,
+                                                         indent, indent)
+            output.append('%s<li>%s%s</li>' % (indent,
+                    escaper(force_unicode(title)), sublist))
+            i += 1
+        return '\n'.join(output)
+    value, converted = convert_old_style_list(value)
+    return mark_safe(_helper(value))
+unordered_list.is_safe = True
+unordered_list.needs_autoescape = True
+
+###################
+# INTEGERS        #
+###################
+
+def add(value, arg):
+    """Adds the arg to the value."""
+    return int(value) + int(arg)
+add.is_safe = False
+
+def get_digit(value, arg):
+    """
+    Given a whole number, returns the requested digit of it, where 1 is the
+    right-most digit, 2 is the second-right-most digit, etc. Returns the
+    original value for invalid input (if input or argument is not an integer,
+    or if argument is less than 1). Otherwise, output is always an integer.
+    """
+    try:
+        arg = int(arg)
+        value = int(value)
+    except ValueError:
+        return value # Fail silently for an invalid argument
+    if arg < 1:
+        return value
+    try:
+        return int(str(value)[-arg])
+    except IndexError:
+        return 0
+get_digit.is_safe = False
+
+###################
+# DATES           #
+###################
+
+def date(value, arg=None):
+    """Formats a date according to the given format."""
+    from django.utils.dateformat import format
+    if not value:
+        return u''
+    if arg is None:
+        arg = settings.DATE_FORMAT
+    return format(value, arg)
+date.is_safe = False
+
+def time(value, arg=None):
+    """Formats a time according to the given format."""
+    from django.utils.dateformat import time_format
+    if value in (None, u''):
+        return u''
+    if arg is None:
+        arg = settings.TIME_FORMAT
+    return time_format(value, arg)
+time.is_safe = False
+
+def timesince(value, arg=None):
+    """Formats a date as the time since that date (i.e. "4 days, 6 hours")."""
+    from django.utils.timesince import timesince
+    if not value:
+        return u''
+    if arg:
+        return timesince(arg, value)
+    return timesince(value)
+timesince.is_safe = False
+
+def timeuntil(value, arg=None):
+    """Formats a date as the time until that date (i.e. "4 days, 6 hours")."""
+    from django.utils.timesince import timesince
+    from datetime import datetime
+    if not value:
+        return u''
+    if arg:
+        return timesince(arg, value)
+    return timesince(datetime.now(), value)
+timeuntil.is_safe = False
+
+###################
+# LOGIC           #
+###################
+
+def default(value, arg):
+    """If value is unavailable, use given default."""
+    return value or arg
+default.is_safe = False
+
+def default_if_none(value, arg):
+    """If value is None, use given default."""
+    if value is None:
+        return arg
+    return value
+default_if_none.is_safe = False
+
+def divisibleby(value, arg):
+    """Returns True if the value is devisible by the argument."""
+    return int(value) % int(arg) == 0
+divisibleby.is_safe = False
+
+def yesno(value, arg=None):
+    """
+    Given a string mapping values for true, false and (optionally) None,
+    returns one of those strings accoding to the value:
+
+    ==========  ======================  ==================================
+    Value       Argument                Outputs
+    ==========  ======================  ==================================
+    ``True``    ``"yeah,no,maybe"``     ``yeah``
+    ``False``   ``"yeah,no,maybe"``     ``no``
+    ``None``    ``"yeah,no,maybe"``     ``maybe``
+    ``None``    ``"yeah,no"``           ``"no"`` (converts None to False
+                                        if no mapping for None is given.
+    ==========  ======================  ==================================
+    """
+    if arg is None:
+        arg = ugettext('yes,no,maybe')
+    bits = arg.split(u',')
+    if len(bits) < 2:
+        return value # Invalid arg.
+    try:
+        yes, no, maybe = bits
+    except ValueError:
+        # Unpack list of wrong size (no "maybe" value provided).
+        yes, no, maybe = bits[0], bits[1], bits[1]
+    if value is None:
+        return maybe
+    if value:
+        return yes
+    return no
+yesno.is_safe = False
+
+###################
+# MISC            #
+###################
+
+def filesizeformat(bytes):
+    """
+    Formats the value like a 'human-readable' file size (i.e. 13 KB, 4.1 MB,
+    102 bytes, etc).
+    """
+    try:
+        bytes = float(bytes)
+    except TypeError:
+        return u"0 bytes"
+
+    if bytes < 1024:
+        return ungettext("%(size)d byte", "%(size)d bytes", bytes) % {'size': bytes}
+    if bytes < 1024 * 1024:
+        return ugettext("%.1f KB") % (bytes / 1024)
+    if bytes < 1024 * 1024 * 1024:
+        return ugettext("%.1f MB") % (bytes / (1024 * 1024))
+    return ugettext("%.1f GB") % (bytes / (1024 * 1024 * 1024))
+filesizeformat.is_safe = True
+
+def pluralize(value, arg=u's'):
+    """
+    Returns a plural suffix if the value is not 1. By default, 's' is used as
+    the suffix:
+
+    * If value is 0, vote{{ value|pluralize }} displays "0 votes".
+    * If value is 1, vote{{ value|pluralize }} displays "1 vote".
+    * If value is 2, vote{{ value|pluralize }} displays "2 votes".
+
+    If an argument is provided, that string is used instead:
+
+    * If value is 0, class{{ value|pluralize:"es" }} displays "0 classes".
+    * If value is 1, class{{ value|pluralize:"es" }} displays "1 class".
+    * If value is 2, class{{ value|pluralize:"es" }} displays "2 classes".
+
+    If the provided argument contains a comma, the text before the comma is
+    used for the singular case and the text after the comma is used for the
+    plural case:
+
+    * If value is 0, cand{{ value|pluralize:"y,ies" }} displays "0 candies".
+    * If value is 1, cand{{ value|pluralize:"y,ies" }} displays "1 candy".
+    * If value is 2, cand{{ value|pluralize:"y,ies" }} displays "2 candies".
+    """
+    if not u',' in arg:
+        arg = u',' + arg
+    bits = arg.split(u',')
+    if len(bits) > 2:
+        return u''
+    singular_suffix, plural_suffix = bits[:2]
+
+    try:
+        if int(value) != 1:
+            return plural_suffix
+    except ValueError: # Invalid string that's not a number.
+        pass
+    except TypeError: # Value isn't a string or a number; maybe it's a list?
+        try:
+            if len(value) != 1:
+                return plural_suffix
+        except TypeError: # len() of unsized object.
+            pass
+    return singular_suffix
+pluralize.is_safe = False
+
+def phone2numeric(value):
+    """Takes a phone number and converts it in to its numerical equivalent."""
+    from django.utils.text import phone2numeric
+    return phone2numeric(value)
+phone2numeric.is_safe = True
+
+def pprint(value):
+    """A wrapper around pprint.pprint -- for debugging, really."""
+    from pprint import pformat
+    try:
+        return pformat(value)
+    except Exception, e:
+        return u"Error in formatting: %s" % force_unicode(e, errors="replace")
+pprint.is_safe = True
+
+# Syntax: register.filter(name of filter, callback)
+register.filter(add)
+register.filter(addslashes)
+register.filter(capfirst)
+register.filter(center)
+register.filter(cut)
+register.filter(date)
+register.filter(default)
+register.filter(default_if_none)
+register.filter(dictsort)
+register.filter(dictsortreversed)
+register.filter(divisibleby)
+register.filter(escape)
+register.filter(escapejs)
+register.filter(filesizeformat)
+register.filter(first)
+register.filter(fix_ampersands)
+register.filter(floatformat)
+register.filter(force_escape)
+register.filter(get_digit)
+register.filter(iriencode)
+register.filter(join)
+register.filter(last)
+register.filter(length)
+register.filter(length_is)
+register.filter(linebreaks)
+register.filter(linebreaksbr)
+register.filter(linenumbers)
+register.filter(ljust)
+register.filter(lower)
+register.filter(make_list)
+register.filter(phone2numeric)
+register.filter(pluralize)
+register.filter(pprint)
+register.filter(removetags)
+register.filter(random)
+register.filter(rjust)
+register.filter(safe)
+register.filter('slice', slice_)
+register.filter(slugify)
+register.filter(stringformat)
+register.filter(striptags)
+register.filter(time)
+register.filter(timesince)
+register.filter(timeuntil)
+register.filter(title)
+register.filter(truncatewords)
+register.filter(truncatewords_html)
+register.filter(unordered_list)
+register.filter(upper)
+register.filter(urlencode)
+register.filter(urlize)
+register.filter(urlizetrunc)
+register.filter(wordcount)
+register.filter(wordwrap)
+register.filter(yesno)
diff --git a/django/templatetags/defaulttags.py b/django/templatetags/defaulttags.py
new file mode 100644
index 0000000..535457a
--- /dev/null
+++ b/django/templatetags/defaulttags.py
@@ -0,0 +1,1105 @@
+"""Default tags used by the template system, available to all templates."""
+
+import sys
+import re
+from itertools import cycle as itertools_cycle
+try:
+    reversed
+except NameError:
+    from django.utils.itercompat import reversed     # Python 2.3 fallback
+
+from django.template import Node, NodeList, Template, Context, Variable
+from django.template import TemplateSyntaxError, VariableDoesNotExist, BLOCK_TAG_START, BLOCK_TAG_END, VARIABLE_TAG_START, VARIABLE_TAG_END, SINGLE_BRACE_START, SINGLE_BRACE_END, COMMENT_TAG_START, COMMENT_TAG_END
+from django.template import get_library, Library, InvalidTemplateLibrary
+from django.conf import settings
+from django.utils.encoding import smart_str, smart_unicode
+from django.utils.itercompat import groupby
+from django.utils.safestring import mark_safe
+
+register = Library()
+
+class AutoEscapeControlNode(Node):
+    """Implements the actions of the autoescape tag."""
+    def __init__(self, setting, nodelist):
+        self.setting, self.nodelist = setting, nodelist
+
+    def render(self, context):
+        old_setting = context.autoescape
+        context.autoescape = self.setting
+        output = self.nodelist.render(context)
+        context.autoescape = old_setting
+        if self.setting:
+            return mark_safe(output)
+        else:
+            return output
+
+class CommentNode(Node):
+    def render(self, context):
+        return ''
+
+class CycleNode(Node):
+    def __init__(self, cyclevars, variable_name=None):
+        self.cycle_iter = itertools_cycle([Variable(v) for v in cyclevars])
+        self.variable_name = variable_name
+
+    def render(self, context):
+        value = self.cycle_iter.next().resolve(context)
+        if self.variable_name:
+            context[self.variable_name] = value
+        return value
+
+class DebugNode(Node):
+    def render(self, context):
+        from pprint import pformat
+        output = [pformat(val) for val in context]
+        output.append('\n\n')
+        output.append(pformat(sys.modules))
+        return ''.join(output)
+
+class FilterNode(Node):
+    def __init__(self, filter_expr, nodelist):
+        self.filter_expr, self.nodelist = filter_expr, nodelist
+
+    def render(self, context):
+        output = self.nodelist.render(context)
+        # Apply filters.
+        context.update({'var': output})
+        filtered = self.filter_expr.resolve(context)
+        context.pop()
+        return filtered
+
+class FirstOfNode(Node):
+    def __init__(self, vars):
+        self.vars = map(Variable, vars)
+
+    def render(self, context):
+        for var in self.vars:
+            try:
+                value = var.resolve(context)
+            except VariableDoesNotExist:
+                continue
+            if value:
+                return smart_unicode(value)
+        return u''
+
+class ForNode(Node):
+    def __init__(self, loopvars, sequence, is_reversed, nodelist_loop):
+        self.loopvars, self.sequence = loopvars, sequence
+        self.is_reversed = is_reversed
+        self.nodelist_loop = nodelist_loop
+
+    def __repr__(self):
+        reversed_text = self.is_reversed and ' reversed' or ''
+        return "<For Node: for %s in %s, tail_len: %d%s>" % \
+            (', '.join(self.loopvars), self.sequence, len(self.nodelist_loop),
+             reversed_text)
+
+    def __iter__(self):
+        for node in self.nodelist_loop:
+            yield node
+
+    def get_nodes_by_type(self, nodetype):
+        nodes = []
+        if isinstance(self, nodetype):
+            nodes.append(self)
+        nodes.extend(self.nodelist_loop.get_nodes_by_type(nodetype))
+        return nodes
+
+    def render(self, context):
+        nodelist = NodeList()
+        if 'forloop' in context:
+            parentloop = context['forloop']
+        else:
+            parentloop = {}
+        context.push()
+        try:
+            values = self.sequence.resolve(context, True)
+        except VariableDoesNotExist:
+            values = []
+        if values is None:
+            values = []
+        if not hasattr(values, '__len__'):
+            values = list(values)
+        len_values = len(values)
+        if self.is_reversed:
+            values = reversed(values)
+        unpack = len(self.loopvars) > 1
+        # Create a forloop value in the context.  We'll update counters on each
+        # iteration just below.
+        loop_dict = context['forloop'] = {'parentloop': parentloop}
+        for i, item in enumerate(values):
+            # Shortcuts for current loop iteration number.
+            loop_dict['counter0'] = i
+            loop_dict['counter'] = i+1
+            # Reverse counter iteration numbers.
+            loop_dict['revcounter'] = len_values - i
+            loop_dict['revcounter0'] = len_values - i - 1
+            # Boolean values designating first and last times through loop.
+            loop_dict['first'] = (i == 0)
+            loop_dict['last'] = (i == len_values - 1)
+
+            if unpack:
+                # If there are multiple loop variables, unpack the item into
+                # them.
+                context.update(dict(zip(self.loopvars, item)))
+            else:
+                context[self.loopvars[0]] = item
+            for node in self.nodelist_loop:
+                nodelist.append(node.render(context))
+            if unpack:
+                # The loop variables were pushed on to the context so pop them
+                # off again. This is necessary because the tag lets the length
+                # of loopvars differ to the length of each set of items and we
+                # don't want to leave any vars from the previous loop on the
+                # context.
+                context.pop()
+        context.pop()
+        return nodelist.render(context)
+
+class IfChangedNode(Node):
+    def __init__(self, nodelist, *varlist):
+        self.nodelist = nodelist
+        self._last_seen = None
+        self._varlist = map(Variable, varlist)
+        self._id = str(id(self))
+
+    def render(self, context):
+        if 'forloop' in context and self._id not in context['forloop']:
+            self._last_seen = None
+            context['forloop'][self._id] = 1
+        try:
+            if self._varlist:
+                # Consider multiple parameters.  This automatically behaves
+                # like an OR evaluation of the multiple variables.
+                compare_to = [var.resolve(context) for var in self._varlist]
+            else:
+                compare_to = self.nodelist.render(context)
+        except VariableDoesNotExist:
+            compare_to = None
+
+        if  compare_to != self._last_seen:
+            firstloop = (self._last_seen == None)
+            self._last_seen = compare_to
+            context.push()
+            context['ifchanged'] = {'firstloop': firstloop}
+            content = self.nodelist.render(context)
+            context.pop()
+            return content
+        else:
+            return ''
+
+class IfEqualNode(Node):
+    def __init__(self, var1, var2, nodelist_true, nodelist_false, negate):
+        self.var1, self.var2 = Variable(var1), Variable(var2)
+        self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false
+        self.negate = negate
+
+    def __repr__(self):
+        return "<IfEqualNode>"
+
+    def render(self, context):
+        try:
+            val1 = self.var1.resolve(context)
+        except VariableDoesNotExist:
+            val1 = None
+        try:
+            val2 = self.var2.resolve(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)
+
+class IfNode(Node):
+    def __init__(self, bool_exprs, nodelist_true, nodelist_false, link_type):
+        self.bool_exprs = bool_exprs
+        self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false
+        self.link_type = link_type
+
+    def __repr__(self):
+        return "<If node>"
+
+    def __iter__(self):
+        for node in self.nodelist_true:
+            yield node
+        for node in self.nodelist_false:
+            yield node
+
+    def get_nodes_by_type(self, nodetype):
+        nodes = []
+        if isinstance(self, nodetype):
+            nodes.append(self)
+        nodes.extend(self.nodelist_true.get_nodes_by_type(nodetype))
+        nodes.extend(self.nodelist_false.get_nodes_by_type(nodetype))
+        return nodes
+
+    def render(self, context):
+        if self.link_type == IfNode.LinkTypes.or_:
+            for ifnot, bool_expr in self.bool_exprs:
+                try:
+                    value = bool_expr.resolve(context, True)
+                except VariableDoesNotExist:
+                    value = None
+                if (value and not ifnot) or (ifnot and not value):
+                    return self.nodelist_true.render(context)
+            return self.nodelist_false.render(context)
+        else:
+            for ifnot, bool_expr in self.bool_exprs:
+                try:
+                    value = bool_expr.resolve(context, True)
+                except VariableDoesNotExist:
+                    value = None
+                if not ((value and not ifnot) or (ifnot and not value)):
+                    return self.nodelist_false.render(context)
+            return self.nodelist_true.render(context)
+
+    class LinkTypes:
+        and_ = 0,
+        or_ = 1
+
+class RegroupNode(Node):
+    def __init__(self, target, expression, var_name):
+        self.target, self.expression = target, expression
+        self.var_name = var_name
+
+    def render(self, context):
+        obj_list = self.target.resolve(context, True)
+        if obj_list == None:
+            # target variable wasn't found in context; fail silently.
+            context[self.var_name] = []
+            return ''
+        # List of dictionaries in the format:
+        # {'grouper': 'key', 'list': [list of contents]}.
+        context[self.var_name] = [
+            {'grouper': key, 'list': list(val)}
+            for key, val in
+            groupby(obj_list, lambda v, f=self.expression.resolve: f(v, True))
+        ]
+        return ''
+
+def include_is_allowed(filepath):
+    for root in settings.ALLOWED_INCLUDE_ROOTS:
+        if filepath.startswith(root):
+            return True
+    return False
+
+class SsiNode(Node):
+    def __init__(self, filepath, parsed):
+        self.filepath, self.parsed = filepath, parsed
+
+    def render(self, context):
+        if not include_is_allowed(self.filepath):
+            if settings.DEBUG:
+                return "[Didn't have permission to include file]"
+            else:
+                return '' # Fail silently for invalid includes.
+        try:
+            fp = open(self.filepath, 'r')
+            output = fp.read()
+            fp.close()
+        except IOError:
+            output = ''
+        if self.parsed:
+            try:
+                t = Template(output, name=self.filepath)
+                return t.render(context)
+            except TemplateSyntaxError, e:
+                if settings.DEBUG:
+                    return "[Included template had syntax error: %s]" % e
+                else:
+                    return '' # Fail silently for invalid included templates.
+        return output
+
+class LoadNode(Node):
+    def render(self, context):
+        return ''
+
+class NowNode(Node):
+    def __init__(self, format_string):
+        self.format_string = format_string
+
+    def render(self, context):
+        from datetime import datetime
+        from django.utils.dateformat import DateFormat
+        df = DateFormat(datetime.now())
+        return df.format(self.format_string)
+
+class SpacelessNode(Node):
+    def __init__(self, nodelist):
+        self.nodelist = nodelist
+
+    def render(self, context):
+        from django.utils.html import strip_spaces_between_tags
+        return strip_spaces_between_tags(self.nodelist.render(context).strip())
+
+class TemplateTagNode(Node):
+    mapping = {'openblock': BLOCK_TAG_START,
+               'closeblock': BLOCK_TAG_END,
+               'openvariable': VARIABLE_TAG_START,
+               'closevariable': VARIABLE_TAG_END,
+               'openbrace': SINGLE_BRACE_START,
+               'closebrace': SINGLE_BRACE_END,
+               'opencomment': COMMENT_TAG_START,
+               'closecomment': COMMENT_TAG_END,
+               }
+
+    def __init__(self, tagtype):
+        self.tagtype = tagtype
+
+    def render(self, context):
+        return self.mapping.get(self.tagtype, '')
+
+class URLNode(Node):
+    def __init__(self, view_name, args, kwargs):
+        self.view_name = view_name
+        self.args = args
+        self.kwargs = kwargs
+
+    def render(self, context):
+        from django.core.urlresolvers import reverse, NoReverseMatch
+        args = [arg.resolve(context) for arg in self.args]
+        kwargs = dict([(smart_str(k,'ascii'), v.resolve(context))
+                       for k, v in self.kwargs.items()])
+        try:
+            return reverse(self.view_name, args=args, kwargs=kwargs)
+        except NoReverseMatch:
+            try:
+                project_name = settings.SETTINGS_MODULE.split('.')[0]
+                return reverse(project_name + '.' + self.view_name,
+                               args=args, kwargs=kwargs)
+            except NoReverseMatch:
+                return ''
+
+class WidthRatioNode(Node):
+    def __init__(self, val_expr, max_expr, max_width):
+        self.val_expr = val_expr
+        self.max_expr = max_expr
+        self.max_width = max_width
+
+    def render(self, context):
+        try:
+            value = self.val_expr.resolve(context)
+            maxvalue = self.max_expr.resolve(context)
+        except VariableDoesNotExist:
+            return ''
+        try:
+            value = float(value)
+            maxvalue = float(maxvalue)
+            ratio = (value / maxvalue) * int(self.max_width)
+        except (ValueError, ZeroDivisionError):
+            return ''
+        return str(int(round(ratio)))
+
+class WithNode(Node):
+    def __init__(self, var, name, nodelist):
+        self.var = var
+        self.name = name
+        self.nodelist = nodelist
+
+    def __repr__(self):
+        return "<WithNode>"
+
+    def render(self, context):
+        val = self.var.resolve(context)
+        context.push()
+        context[self.name] = val
+        output = self.nodelist.render(context)
+        context.pop()
+        return output
+
+#@register.tag
+def autoescape(parser, token):
+    """
+    Force autoescape behaviour for this block.
+    """
+    args = token.contents.split()
+    if len(args) != 2:
+        raise TemplateSyntaxError("'Autoescape' tag requires exactly one argument.")
+    arg = args[1]
+    if arg not in (u'on', u'off'):
+        raise TemplateSyntaxError("'Autoescape' argument should be 'on' or 'off'")
+    nodelist = parser.parse(('endautoescape',))
+    parser.delete_first_token()
+    return AutoEscapeControlNode((arg == 'on'), nodelist)
+autoescape = register.tag(autoescape)
+
+#@register.tag
+def comment(parser, token):
+    """
+    Ignores everything between ``{% comment %}`` and ``{% endcomment %}``.
+    """
+    parser.skip_past('endcomment')
+    return CommentNode()
+comment = register.tag(comment)
+
+#@register.tag
+def cycle(parser, token):
+    """
+    Cycles among the given strings each time this tag is encountered.
+
+    Within a loop, cycles among the given strings each time through
+    the loop::
+
+        {% for o in some_list %}
+            <tr class="{% cycle 'row1' 'row2' %}">
+                ...
+            </tr>
+        {% endfor %}
+
+    Outside of a loop, give the values a unique name the first time you call
+    it, then use that name each sucessive time through::
+
+            <tr class="{% cycle 'row1' 'row2' 'row3' as rowcolors %}">...</tr>
+            <tr class="{% cycle rowcolors %}">...</tr>
+            <tr class="{% cycle rowcolors %}">...</tr>
+
+    You can use any number of values, separated by spaces. Commas can also
+    be used to separate values; if a comma is used, the cycle values are
+    interpreted as literal strings.
+    """
+
+    # Note: This returns the exact same node on each {% cycle name %} call;
+    # that is, the node object returned from {% cycle a b c as name %} and the
+    # one returned from {% cycle name %} are the exact same object. This
+    # shouldn't cause problems (heh), but if it does, now you know.
+    #
+    # Ugly hack warning: This stuffs the named template dict into parser so
+    # that names are only unique within each template (as opposed to using
+    # a global variable, which would make cycle names have to be unique across
+    # *all* templates.
+
+    args = token.split_contents()
+
+    if len(args) < 2:
+        raise TemplateSyntaxError("'cycle' tag requires at least two arguments")
+
+    if ',' in args[1]:
+        # Backwards compatibility: {% cycle a,b %} or {% cycle a,b as foo %}
+        # case.
+        args[1:2] = ['"%s"' % arg for arg in args[1].split(",")]
+
+    if len(args) == 2:
+        # {% cycle foo %} case.
+        name = args[1]
+        if not hasattr(parser, '_namedCycleNodes'):
+            raise TemplateSyntaxError("No named cycles in template. '%s' is not defined" % name)
+        if not name in parser._namedCycleNodes:
+            raise TemplateSyntaxError("Named cycle '%s' does not exist" % name)
+        return parser._namedCycleNodes[name]
+
+    if len(args) > 4 and args[-2] == 'as':
+        name = args[-1]
+        node = CycleNode(args[1:-2], name)
+        if not hasattr(parser, '_namedCycleNodes'):
+            parser._namedCycleNodes = {}
+        parser._namedCycleNodes[name] = node
+    else:
+        node = CycleNode(args[1:])
+    return node
+cycle = register.tag(cycle)
+
+def debug(parser, token):
+    """
+    Outputs a whole load of debugging information, including the current
+    context and imported modules.
+
+    Sample usage::
+
+        <pre>
+            {% debug %}
+        </pre>
+    """
+    return DebugNode()
+debug = register.tag(debug)
+
+#@register.tag(name="filter")
+def do_filter(parser, token):
+    """
+    Filters the contents of the block through variable filters.
+
+    Filters can also be piped through each other, and they can have
+    arguments -- just like in variable syntax.
+
+    Sample usage::
+
+        {% filter force_escape|lower %}
+            This text will be HTML-escaped, and will appear in lowercase.
+        {% endfilter %}
+    """
+    _, rest = token.contents.split(None, 1)
+    filter_expr = parser.compile_filter("var|%s" % (rest))
+    for func, unused in filter_expr.filters:
+        if getattr(func, '_decorated_function', func).__name__ in ('escape', 'safe'):
+            raise TemplateSyntaxError('"filter %s" is not permitted.  Use the "autoescape" tag instead.' % func.__name__)
+    nodelist = parser.parse(('endfilter',))
+    parser.delete_first_token()
+    return FilterNode(filter_expr, nodelist)
+do_filter = register.tag("filter", do_filter)
+
+#@register.tag
+def firstof(parser, token):
+    """
+    Outputs the first variable passed that is not False.
+
+    Outputs nothing if all the passed variables are False.
+
+    Sample usage::
+
+        {% firstof var1 var2 var3 %}
+
+    This is equivalent to::
+
+        {% if var1 %}
+            {{ var1 }}
+        {% else %}{% if var2 %}
+            {{ var2 }}
+        {% else %}{% if var3 %}
+            {{ var3 }}
+        {% endif %}{% endif %}{% endif %}
+
+    but obviously much cleaner!
+
+    You can also use a literal string as a fallback value in case all
+    passed variables are False::
+
+        {% firstof var1 var2 var3 "fallback value" %}
+
+    """
+    bits = token.split_contents()[1:]
+    if len(bits) < 1:
+        raise TemplateSyntaxError("'firstof' statement requires at least one"
+                                  " argument")
+    return FirstOfNode(bits)
+firstof = register.tag(firstof)
+
+#@register.tag(name="for")
+def do_for(parser, token):
+    """
+    Loops over each item in an array.
+
+    For example, to display a list of athletes given ``athlete_list``::
+
+        <ul>
+        {% for athlete in athlete_list %}
+            <li>{{ athlete.name }}</li>
+        {% endfor %}
+        </ul>
+
+    You can loop over a list in reverse by using
+    ``{% for obj in list reversed %}``.
+
+    You can also unpack multiple values from a two-dimensional array::
+
+        {% for key,value in dict.items %}
+            {{ key }}: {{ value }}
+        {% endfor %}
+
+    The for loop sets a number of variables available within the loop:
+
+        ==========================  ================================================
+        Variable                    Description
+        ==========================  ================================================
+        ``forloop.counter``         The current iteration of the loop (1-indexed)
+        ``forloop.counter0``        The current iteration of the loop (0-indexed)
+        ``forloop.revcounter``      The number of iterations from the end of the
+                                    loop (1-indexed)
+        ``forloop.revcounter0``     The number of iterations from the end of the
+                                    loop (0-indexed)
+        ``forloop.first``           True if this is the first time through the loop
+        ``forloop.last``            True if this is the last time through the loop
+        ``forloop.parentloop``      For nested loops, this is the loop "above" the
+                                    current one
+        ==========================  ================================================
+
+    """
+    bits = token.contents.split()
+    if len(bits) < 4:
+        raise TemplateSyntaxError("'for' statements should have at least four"
+                                  " words: %s" % token.contents)
+
+    is_reversed = bits[-1] == 'reversed'
+    in_index = is_reversed and -3 or -2
+    if bits[in_index] != 'in':
+        raise TemplateSyntaxError("'for' statements should use the format"
+                                  " 'for x in y': %s" % token.contents)
+
+    loopvars = re.sub(r' *, *', ',', ' '.join(bits[1:in_index])).split(',')
+    for var in loopvars:
+        if not var or ' ' in var:
+            raise TemplateSyntaxError("'for' tag received an invalid argument:"
+                                      " %s" % token.contents)
+
+    sequence = parser.compile_filter(bits[in_index+1])
+    nodelist_loop = parser.parse(('endfor',))
+    parser.delete_first_token()
+    return ForNode(loopvars, sequence, is_reversed, nodelist_loop)
+do_for = register.tag("for", do_for)
+
+def do_ifequal(parser, token, negate):
+    bits = list(token.split_contents())
+    if len(bits) != 3:
+        raise TemplateSyntaxError, "%r takes two arguments" % bits[0]
+    end_tag = 'end' + bits[0]
+    nodelist_true = parser.parse(('else', end_tag))
+    token = parser.next_token()
+    if token.contents == 'else':
+        nodelist_false = parser.parse((end_tag,))
+        parser.delete_first_token()
+    else:
+        nodelist_false = NodeList()
+    return IfEqualNode(bits[1], bits[2], nodelist_true, nodelist_false, negate)
+
+#@register.tag
+def ifequal(parser, token):
+    """
+    Outputs the contents of the block if the two arguments equal each other.
+
+    Examples::
+
+        {% ifequal user.id comment.user_id %}
+            ...
+        {% endifequal %}
+
+        {% ifnotequal user.id comment.user_id %}
+            ...
+        {% else %}
+            ...
+        {% endifnotequal %}
+    """
+    return do_ifequal(parser, token, False)
+ifequal = register.tag(ifequal)
+
+#@register.tag
+def ifnotequal(parser, token):
+    """
+    Outputs the contents of the block if the two arguments are not equal.
+    See ifequal.
+    """
+    return do_ifequal(parser, token, True)
+ifnotequal = register.tag(ifnotequal)
+
+#@register.tag(name="if")
+def do_if(parser, token):
+    """
+    The ``{% if %}`` tag evaluates a variable, and if that variable is "true"
+    (i.e., exists, is not empty, and is not a false boolean value), the
+    contents of the block are output:
+
+    ::
+
+        {% if athlete_list %}
+            Number of athletes: {{ athlete_list|count }}
+        {% else %}
+            No athletes.
+        {% endif %}
+
+    In the above, if ``athlete_list`` is not empty, the number of athletes will
+    be displayed by the ``{{ athlete_list|count }}`` variable.
+
+    As you can see, the ``if`` tag can take an option ``{% else %}`` clause
+    that will be displayed if the test fails.
+
+    ``if`` tags may use ``or``, ``and`` or ``not`` to test a number of
+    variables or to negate a given variable::
+
+        {% if not athlete_list %}
+            There are no athletes.
+        {% endif %}
+
+        {% if athlete_list or coach_list %}
+            There are some athletes or some coaches.
+        {% endif %}
+
+        {% if athlete_list and coach_list %}
+            Both atheletes and coaches are available.
+        {% endif %}
+
+        {% if not athlete_list or coach_list %}
+            There are no athletes, or there are some coaches.
+        {% endif %}
+
+        {% if athlete_list and not coach_list %}
+            There are some athletes and absolutely no coaches.
+        {% endif %}
+
+    ``if`` tags do not allow ``and`` and ``or`` clauses with the same tag,
+    because the order of logic would be ambigous. For example, this is
+    invalid::
+
+        {% if athlete_list and coach_list or cheerleader_list %}
+
+    If you need to combine ``and`` and ``or`` to do advanced logic, just use
+    nested if tags. For example::
+
+        {% if athlete_list %}
+            {% if coach_list or cheerleader_list %}
+                We have athletes, and either coaches or cheerleaders!
+            {% endif %}
+        {% endif %}
+    """
+    bits = token.contents.split()
+    del bits[0]
+    if not bits:
+        raise TemplateSyntaxError("'if' statement requires at least one argument")
+    # Bits now looks something like this: ['a', 'or', 'not', 'b', 'or', 'c.d']
+    bitstr = ' '.join(bits)
+    boolpairs = bitstr.split(' and ')
+    boolvars = []
+    if len(boolpairs) == 1:
+        link_type = IfNode.LinkTypes.or_
+        boolpairs = bitstr.split(' or ')
+    else:
+        link_type = IfNode.LinkTypes.and_
+        if ' or ' in bitstr:
+            raise TemplateSyntaxError, "'if' tags can't mix 'and' and 'or'"
+    for boolpair in boolpairs:
+        if ' ' in boolpair:
+            try:
+                not_, boolvar = boolpair.split()
+            except ValueError:
+                raise TemplateSyntaxError, "'if' statement improperly formatted"
+            if not_ != 'not':
+                raise TemplateSyntaxError, "Expected 'not' in if statement"
+            boolvars.append((True, parser.compile_filter(boolvar)))
+        else:
+            boolvars.append((False, parser.compile_filter(boolpair)))
+    nodelist_true = parser.parse(('else', 'endif'))
+    token = parser.next_token()
+    if token.contents == 'else':
+        nodelist_false = parser.parse(('endif',))
+        parser.delete_first_token()
+    else:
+        nodelist_false = NodeList()
+    return IfNode(boolvars, nodelist_true, nodelist_false, link_type)
+do_if = register.tag("if", do_if)
+
+#@register.tag
+def ifchanged(parser, token):
+    """
+    Checks if a value has changed from the last iteration of a loop.
+
+    The 'ifchanged' block tag is used within a loop. It has two possible uses.
+
+    1. Checks its own rendered contents against its previous state and only
+       displays the content if it has changed. For example, this displays a
+       list of days, only displaying the month if it changes::
+
+            <h1>Archive for {{ year }}</h1>
+
+            {% for date in days %}
+                {% ifchanged %}<h3>{{ date|date:"F" }}</h3>{% endifchanged %}
+                <a href="{{ date|date:"M/d"|lower }}/">{{ date|date:"j" }}</a>
+            {% endfor %}
+
+    2. If given a variable, check whether that variable has changed.
+       For example, the following shows the date every time it changes, but
+       only shows the hour if both the hour and the date have changed::
+
+            {% for date in days %}
+                {% ifchanged date.date %} {{ date.date }} {% endifchanged %}
+                {% ifchanged date.hour date.date %}
+                    {{ date.hour }}
+                {% endifchanged %}
+            {% endfor %}
+    """
+    bits = token.contents.split()
+    nodelist = parser.parse(('endifchanged',))
+    parser.delete_first_token()
+    return IfChangedNode(nodelist, *bits[1:])
+ifchanged = register.tag(ifchanged)
+
+#@register.tag
+def ssi(parser, token):
+    """
+    Outputs the contents of a given file into the page.
+
+    Like a simple "include" tag, the ``ssi`` tag includes the contents
+    of another file -- which must be specified using an absolute path --
+    in the current page::
+
+        {% ssi /home/html/ljworld.com/includes/right_generic.html %}
+
+    If the optional "parsed" parameter is given, the contents of the included
+    file are evaluated as template code, with the current context::
+
+        {% ssi /home/html/ljworld.com/includes/right_generic.html parsed %}
+    """
+    bits = token.contents.split()
+    parsed = False
+    if len(bits) not in (2, 3):
+        raise TemplateSyntaxError("'ssi' tag takes one argument: the path to"
+                                  " the file to be included")
+    if len(bits) == 3:
+        if bits[2] == 'parsed':
+            parsed = True
+        else:
+            raise TemplateSyntaxError("Second (optional) argument to %s tag"
+                                      " must be 'parsed'" % bits[0])
+    return SsiNode(bits[1], parsed)
+ssi = register.tag(ssi)
+
+#@register.tag
+def load(parser, token):
+    """
+    Loads a custom template tag set.
+
+    For example, to load the template tags in
+    ``django/templatetags/news/photos.py``::
+
+        {% load news.photos %}
+    """
+    bits = token.contents.split()
+    for taglib in bits[1:]:
+        # add the library to the parser
+        try:
+            lib = get_library(taglib)
+            parser.add_library(lib)
+        except InvalidTemplateLibrary, e:
+            raise TemplateSyntaxError("'%s' is not a valid tag library: %s" %
+                                      (taglib, e))
+    return LoadNode()
+load = register.tag(load)
+
+#@register.tag
+def now(parser, token):
+    """
+    Displays the date, formatted according to the given string.
+
+    Uses the same format as PHP's ``date()`` function; see http://php.net/date
+    for all the possible values.
+
+    Sample usage::
+
+        It is {% now "jS F Y H:i" %}
+    """
+    bits = token.contents.split('"')
+    if len(bits) != 3:
+        raise TemplateSyntaxError, "'now' statement takes one argument"
+    format_string = bits[1]
+    return NowNode(format_string)
+now = register.tag(now)
+
+#@register.tag
+def regroup(parser, token):
+    """
+    Regroups a list of alike objects by a common attribute.
+
+    This complex tag is best illustrated by use of an example:  say that
+    ``people`` is a list of ``Person`` objects that have ``first_name``,
+    ``last_name``, and ``gender`` attributes, and you'd like to display a list
+    that looks like:
+
+        * Male:
+            * George Bush
+            * Bill Clinton
+        * Female:
+            * Margaret Thatcher
+            * Colendeeza Rice
+        * Unknown:
+            * Pat Smith
+
+    The following snippet of template code would accomplish this dubious task::
+
+        {% regroup people by gender as grouped %}
+        <ul>
+        {% for group in grouped %}
+            <li>{{ group.grouper }}
+            <ul>
+                {% for item in group.list %}
+                <li>{{ item }}</li>
+                {% endfor %}
+            </ul>
+        {% endfor %}
+        </ul>
+
+    As you can see, ``{% regroup %}`` populates a variable with a list of
+    objects with ``grouper`` and ``list`` attributes.  ``grouper`` contains the
+    item that was grouped by; ``list`` contains the list of objects that share
+    that ``grouper``.  In this case, ``grouper`` would be ``Male``, ``Female``
+    and ``Unknown``, and ``list`` is the list of people with those genders.
+
+    Note that `{% regroup %}`` does not work when the list to be grouped is not
+    sorted by the key you are grouping by!  This means that if your list of
+    people was not sorted by gender, you'd need to make sure it is sorted
+    before using it, i.e.::
+
+        {% regroup people|dictsort:"gender" by gender as grouped %}
+
+    """
+    firstbits = token.contents.split(None, 3)
+    if len(firstbits) != 4:
+        raise TemplateSyntaxError, "'regroup' tag takes five arguments"
+    target = parser.compile_filter(firstbits[1])
+    if firstbits[2] != 'by':
+        raise TemplateSyntaxError("second argument to 'regroup' tag must be 'by'")
+    lastbits_reversed = firstbits[3][::-1].split(None, 2)
+    if lastbits_reversed[1][::-1] != 'as':
+        raise TemplateSyntaxError("next-to-last argument to 'regroup' tag must"
+                                  " be 'as'")
+
+    expression = parser.compile_filter(lastbits_reversed[2][::-1])
+
+    var_name = lastbits_reversed[0][::-1]
+    return RegroupNode(target, expression, var_name)
+regroup = register.tag(regroup)
+
+def spaceless(parser, token):
+    """
+    Removes whitespace between HTML tags, including tab and newline characters.
+
+    Example usage::
+
+        {% spaceless %}
+            <p>
+                <a href="foo/">Foo</a>
+            </p>
+        {% endspaceless %}
+
+    This example would return this HTML::
+
+        <p><a href="foo/">Foo</a></p>
+
+    Only space between *tags* is normalized -- not space between tags and text.
+    In this example, the space around ``Hello`` won't be stripped::
+
+        {% spaceless %}
+            <strong>
+                Hello
+            </strong>
+        {% endspaceless %}
+    """
+    nodelist = parser.parse(('endspaceless',))
+    parser.delete_first_token()
+    return SpacelessNode(nodelist)
+spaceless = register.tag(spaceless)
+
+#@register.tag
+def templatetag(parser, token):
+    """
+    Outputs one of the bits used to compose template tags.
+
+    Since the template system has no concept of "escaping", to display one of
+    the bits used in template tags, you must use the ``{% templatetag %}`` tag.
+
+    The argument tells which template bit to output:
+
+        ==================  =======
+        Argument            Outputs
+        ==================  =======
+        ``openblock``       ``{%``
+        ``closeblock``      ``%}``
+        ``openvariable``    ``{{``
+        ``closevariable``   ``}}``
+        ``openbrace``       ``{``
+        ``closebrace``      ``}``
+        ``opencomment``     ``{#``
+        ``closecomment``    ``#}``
+        ==================  =======
+    """
+    bits = token.contents.split()
+    if len(bits) != 2:
+        raise TemplateSyntaxError, "'templatetag' statement takes one argument"
+    tag = bits[1]
+    if tag not in TemplateTagNode.mapping:
+        raise TemplateSyntaxError("Invalid templatetag argument: '%s'."
+                                  " Must be one of: %s" %
+                                  (tag, TemplateTagNode.mapping.keys()))
+    return TemplateTagNode(tag)
+templatetag = register.tag(templatetag)
+
+def url(parser, token):
+    """
+    Returns an absolute URL matching given view with its parameters.
+
+    This is a way to define links that aren't tied to a particular URL
+    configuration::
+
+        {% url path.to.some_view arg1,arg2,name1=value1 %}
+
+    The first argument is a path to a view. It can be an absolute python path
+    or just ``app_name.view_name`` without the project name if the view is
+    located inside the project.  Other arguments are comma-separated values
+    that will be filled in place of positional and keyword arguments in the
+    URL. All arguments for the URL should be present.
+
+    For example if you have a view ``app_name.client`` taking client's id and
+    the corresponding line in a URLconf looks like this::
+
+        ('^client/(\d+)/$', 'app_name.client')
+
+    and this app's URLconf is included into the project's URLconf under some
+    path::
+
+        ('^clients/', include('project_name.app_name.urls'))
+
+    then in a template you can create a link for a certain client like this::
+
+        {% url app_name.client client.id %}
+
+    The URL will look like ``/clients/client/123/``.
+    """
+    bits = token.contents.split(' ', 2)
+    if len(bits) < 2:
+        raise TemplateSyntaxError("'%s' takes at least one argument"
+                                  " (path to a view)" % bits[0])
+    args = []
+    kwargs = {}
+    if len(bits) > 2:
+        for arg in bits[2].split(','):
+            if '=' in arg:
+                k, v = arg.split('=', 1)
+                k = k.strip()
+                kwargs[k] = parser.compile_filter(v)
+            else:
+                args.append(parser.compile_filter(arg))
+    return URLNode(bits[1], args, kwargs)
+url = register.tag(url)
+
+#@register.tag
+def widthratio(parser, token):
+    """
+    For creating bar charts and such, this tag calculates the ratio of a given
+    value to a maximum value, and then applies that ratio to a constant.
+
+    For example::
+
+        <img src='bar.gif' height='10' width='{% widthratio this_value max_value 100 %}' />
+
+    Above, if ``this_value`` is 175 and ``max_value`` is 200, the the image in
+    the above example will be 88 pixels wide (because 175/200 = .875;
+    .875 * 100 = 87.5 which is rounded up to 88).
+    """
+    bits = token.contents.split()
+    if len(bits) != 4:
+        raise TemplateSyntaxError("widthratio takes three arguments")
+    tag, this_value_expr, max_value_expr, max_width = bits
+    try:
+        max_width = int(max_width)
+    except ValueError:
+        raise TemplateSyntaxError("widthratio final argument must be an integer")
+    return WidthRatioNode(parser.compile_filter(this_value_expr),
+                          parser.compile_filter(max_value_expr), max_width)
+widthratio = register.tag(widthratio)
+
+#@register.tag
+def do_with(parser, token):
+    """
+    Adds a value to the context (inside of this block) for caching and easy
+    access.
+
+    For example::
+
+        {% with person.some_sql_method as total %}
+            {{ total }} object{{ total|pluralize }}
+        {% endwith %}
+    """
+    bits = list(token.split_contents())
+    if len(bits) != 4 or bits[2] != "as":
+        raise TemplateSyntaxError("%r expected format is 'value as name'" %
+                                  bits[0])
+    var = parser.compile_filter(bits[1])
+    name = bits[3]
+    nodelist = parser.parse(('endwith',))
+    parser.delete_first_token()
+    return WithNode(var, name, nodelist)
+do_with = register.tag('with', do_with)
diff --git a/tests/regressiontests/templates/tests.py b/tests/regressiontests/templates/tests.py
index 186b8aa..ac0912d 100644
--- a/tests/regressiontests/templates/tests.py
+++ b/tests/regressiontests/templates/tests.py
@@ -51,7 +51,7 @@ def do_echo(parser, token):
 
 register.tag("echo", do_echo)
 
-template.libraries['django.templatetags.testtags'] = register
+template.libraries['testtags'] = register
 
 #####################################
 # Helper objects for template tests #
