Code

Ticket #6587: new_template_lib_loader_13.diff

File new_template_lib_loader_13.diff, 135.6 KB (added by oyvind, 6 years ago)

removed import using getattr

Line 
1diff --git a/django/template/__init__.py b/django/template/__init__.py
2index 5c4ab30..1f91d33 100644
3--- a/django/template/__init__.py
4+++ b/django/template/__init__.py
5@@ -49,6 +49,7 @@ u'<html><h1>Hello</h1></html>'
6 u'<html></html>'
7 """
8 import re
9+import imp
10 from inspect import getargspec
11 from django.conf import settings
12 from django.template.context import Context, RequestContext, ContextPopException
13@@ -59,6 +60,7 @@ from django.utils.encoding import smart_unicode, force_unicode
14 from django.utils.translation import ugettext as _
15 from django.utils.safestring import SafeData, EscapeData, mark_safe, mark_for_escaping
16 from django.utils.html import escape
17+from django.templatetags import get_templatetags_modules
18 
19 __all__ = ('Template', 'Context', 'RequestContext', 'compile_string')
20 
21@@ -913,22 +915,48 @@ class Library(object):
22             return func
23         return dec
24 
25-def get_library(module_name):
26-    lib = libraries.get(module_name, None)
27+def import_library(templatetag_module, library_name):
28+    try:
29+        components = templatetag_module.split('.')
30+        mod = __import__(templatetag_module, {}, {}, components[-1])
31+        imp.find_module(library_name, mod.__path__)
32+    except ImportError:
33+        return None
34+    library_module = '%s.%s' % (templatetag_module, library_name)
35+    components = library_module.split('.')
36+    mod = __import__(library_module, {}, {}, components[-1])
37+    try:
38+        return mod.register
39+    except AttributeError:
40+        raise InvalidTemplateLibrary("Template library %s does not have a variable named 'register'" % module_name)
41+
42+def get_library(library_name):
43+    lib = libraries.get(library_name, None)
44     if not lib:
45-        try:
46-            mod = __import__(module_name, {}, {}, [''])
47-        except ImportError, e:
48-            raise InvalidTemplateLibrary("Could not load template library from %s, %s" % (module_name, e))
49-        try:
50-            lib = mod.register
51-            libraries[module_name] = lib
52-        except AttributeError:
53-            raise InvalidTemplateLibrary("Template library %s does not have a variable named 'register'" % module_name)
54+
55+        """
56+        If library is not already loaded loop over all templatetags modules to locate it.
57+
58+        {% load somelib %} and {% load someotherlib %} loops twice.
59+
60+        Subsequent loads eg. {% load somelib %} in the same thread will grab the cached
61+        module from libraries.
62+        """
63+        templatetags_modules = get_templatetags_modules()
64+        tried_modules = []
65+        for module in templatetags_modules:
66+            taglib_module = '%s.%s' % (module, library_name)
67+            tried_modules.append(taglib_module)
68+            lib = import_library(module, library_name)
69+            if lib:
70+                libraries[library_name] = lib
71+                break
72+        if not lib:
73+            raise InvalidTemplateLibrary("Template library %s not found, tried %s" % (library_name, ','.join(tried_modules)))
74     return lib
75 
76-def add_to_builtins(module_name):
77-    builtins.append(get_library(module_name))
78+def add_to_builtins(module, library_name):
79+    builtins.append(import_library(module, library_name))
80 
81-add_to_builtins('django.template.defaulttags')
82-add_to_builtins('django.template.defaultfilters')
83+add_to_builtins('django.templatetags', 'defaulttags')
84+add_to_builtins('django.templatetags', 'defaultfilters')
85diff --git a/django/template/defaultfilters.py b/django/template/defaultfilters.py
86deleted file mode 100644
87index cef3143..0000000
88--- a/django/template/defaultfilters.py
89+++ /dev/null
90@@ -1,851 +0,0 @@
91-"""Default variable filters."""
92-
93-import re
94-import random as random_module
95-try:
96-    from functools import wraps
97-except ImportError:
98-    from django.utils.functional import wraps  # Python 2.3, 2.4 fallback.
99-
100-from django.template import Variable, Library
101-from django.conf import settings
102-from django.utils.translation import ugettext, ungettext
103-from django.utils.encoding import force_unicode, iri_to_uri
104-from django.utils.safestring import mark_safe, SafeData
105-
106-register = Library()
107-
108-#######################
109-# STRING DECORATOR    #
110-#######################
111-
112-def stringfilter(func):
113-    """
114-    Decorator for filters which should only receive unicode objects. The object
115-    passed as the first positional argument will be converted to a unicode
116-    object.
117-    """
118-    def _dec(*args, **kwargs):
119-        if args:
120-            args = list(args)
121-            args[0] = force_unicode(args[0])
122-            if isinstance(args[0], SafeData) and getattr(func, 'is_safe', False):
123-                return mark_safe(func(*args, **kwargs))
124-        return func(*args, **kwargs)
125-
126-    # Include a reference to the real function (used to check original
127-    # arguments by the template parser).
128-    _dec._decorated_function = getattr(func, '_decorated_function', func)
129-    for attr in ('is_safe', 'needs_autoescape'):
130-        if hasattr(func, attr):
131-            setattr(_dec, attr, getattr(func, attr))
132-    return wraps(func)(_dec)
133-
134-###################
135-# STRINGS         #
136-###################
137-
138-
139-def addslashes(value):
140-    """
141-    Adds slashes before quotes. Useful for escaping strings in CSV, for
142-    example. Less useful for escaping JavaScript; use the ``escapejs``
143-    filter instead.
144-    """
145-    return value.replace('\\', '\\\\').replace('"', '\\"').replace("'", "\\'")
146-addslashes.is_safe = True
147-addslashes = stringfilter(addslashes)
148-
149-def capfirst(value):
150-    """Capitalizes the first character of the value."""
151-    return value and value[0].upper() + value[1:]
152-capfirst.is_safe=True
153-capfirst = stringfilter(capfirst)
154-
155-_js_escapes = (
156-    ('\\', '\\\\'),
157-    ('"', '\\"'),
158-    ("'", "\\'"),
159-    ('\n', '\\n'),
160-    ('\r', '\\r'),
161-    ('\b', '\\b'),
162-    ('\f', '\\f'),
163-    ('\t', '\\t'),
164-    ('\v', '\\v'),
165-    ('</', '<\\/'),
166-)
167-def escapejs(value):
168-    """Backslash-escapes characters for use in JavaScript strings."""
169-    for bad, good in _js_escapes:
170-        value = value.replace(bad, good)
171-    return value
172-escapejs = stringfilter(escapejs)
173-
174-def fix_ampersands(value):
175-    """Replaces ampersands with ``&amp;`` entities."""
176-    from django.utils.html import fix_ampersands
177-    return fix_ampersands(value)
178-fix_ampersands.is_safe=True
179-fix_ampersands = stringfilter(fix_ampersands)
180-
181-def floatformat(text, arg=-1):
182-    """
183-    Displays a float to a specified number of decimal places.
184-
185-    If called without an argument, it displays the floating point number with
186-    one decimal place -- but only if there's a decimal place to be displayed:
187-
188-    * num1 = 34.23234
189-    * num2 = 34.00000
190-    * num3 = 34.26000
191-    * {{ num1|floatformat }} displays "34.2"
192-    * {{ num2|floatformat }} displays "34"
193-    * {{ num3|floatformat }} displays "34.3"
194-
195-    If arg is positive, it will always display exactly arg number of decimal
196-    places:
197-
198-    * {{ num1|floatformat:3 }} displays "34.232"
199-    * {{ num2|floatformat:3 }} displays "34.000"
200-    * {{ num3|floatformat:3 }} displays "34.260"
201-
202-    If arg is negative, it will display arg number of decimal places -- but
203-    only if there are places to be displayed:
204-
205-    * {{ num1|floatformat:"-3" }} displays "34.232"
206-    * {{ num2|floatformat:"-3" }} displays "34"
207-    * {{ num3|floatformat:"-3" }} displays "34.260"
208-    """
209-    try:
210-        f = float(text)
211-    except (ValueError, TypeError):
212-        return u''
213-    try:
214-        d = int(arg)
215-    except ValueError:
216-        return force_unicode(f)
217-    try:
218-        m = f - int(f)
219-    except OverflowError:
220-        return force_unicode(f)
221-    if not m and d < 0:
222-        return mark_safe(u'%d' % int(f))
223-    else:
224-        formatstr = u'%%.%df' % abs(d)
225-        return mark_safe(formatstr % f)
226-floatformat.is_safe = True
227-
228-def iriencode(value):
229-    """Escapes an IRI value for use in a URL."""
230-    return force_unicode(iri_to_uri(value))
231-iriencode.is_safe = True
232-iriencode = stringfilter(iriencode)
233-
234-def linenumbers(value, autoescape=None):
235-    """Displays text with line numbers."""
236-    from django.utils.html import escape
237-    lines = value.split(u'\n')
238-    # Find the maximum width of the line count, for use with zero padding
239-    # string format command
240-    width = unicode(len(unicode(len(lines))))
241-    if not autoescape or isinstance(value, SafeData):
242-        for i, line in enumerate(lines):
243-            lines[i] = (u"%0" + width  + u"d. %s") % (i + 1, line)
244-    else:
245-        for i, line in enumerate(lines):
246-            lines[i] = (u"%0" + width  + u"d. %s") % (i + 1, escape(line))
247-    return mark_safe(u'\n'.join(lines))
248-linenumbers.is_safe = True
249-linenumbers.needs_autoescape = True
250-linenumbers = stringfilter(linenumbers)
251-
252-def lower(value):
253-    """Converts a string into all lowercase."""
254-    return value.lower()
255-lower.is_safe = True
256-lower = stringfilter(lower)
257-
258-def make_list(value):
259-    """
260-    Returns the value turned into a list.
261-
262-    For an integer, it's a list of digits.
263-    For a string, it's a list of characters.
264-    """
265-    return list(value)
266-make_list.is_safe = False
267-make_list = stringfilter(make_list)
268-
269-def slugify(value):
270-    """
271-    Normalizes string, converts to lowercase, removes non-alpha characters,
272-    and converts spaces to hyphens.
273-    """
274-    import unicodedata
275-    value = unicodedata.normalize('NFKD', value).encode('ascii', 'ignore')
276-    value = unicode(re.sub('[^\w\s-]', '', value).strip().lower())
277-    return mark_safe(re.sub('[-\s]+', '-', value))
278-slugify.is_safe = True
279-slugify = stringfilter(slugify)
280-
281-def stringformat(value, arg):
282-    """
283-    Formats the variable according to the arg, a string formatting specifier.
284-
285-    This specifier uses Python string formating syntax, with the exception that
286-    the leading "%" is dropped.
287-
288-    See http://docs.python.org/lib/typesseq-strings.html for documentation
289-    of Python string formatting
290-    """
291-    try:
292-        return (u"%" + unicode(arg)) % value
293-    except (ValueError, TypeError):
294-        return u""
295-stringformat.is_safe = True
296-
297-def title(value):
298-    """Converts a string into titlecase."""
299-    return re.sub("([a-z])'([A-Z])", lambda m: m.group(0).lower(), value.title())
300-title.is_safe = True
301-title = stringfilter(title)
302-
303-def truncatewords(value, arg):
304-    """
305-    Truncates a string after a certain number of words.
306-
307-    Argument: Number of words to truncate after.
308-    """
309-    from django.utils.text import truncate_words
310-    try:
311-        length = int(arg)
312-    except ValueError: # Invalid literal for int().
313-        return value # Fail silently.
314-    return truncate_words(value, length)
315-truncatewords.is_safe = True
316-truncatewords = stringfilter(truncatewords)
317-
318-def truncatewords_html(value, arg):
319-    """
320-    Truncates HTML after a certain number of words.
321-
322-    Argument: Number of words to truncate after.
323-    """
324-    from django.utils.text import truncate_html_words
325-    try:
326-        length = int(arg)
327-    except ValueError: # invalid literal for int()
328-        return value # Fail silently.
329-    return truncate_html_words(value, length)
330-truncatewords_html.is_safe = True
331-truncatewords_html = stringfilter(truncatewords_html)
332-
333-def upper(value):
334-    """Converts a string into all uppercase."""
335-    return value.upper()
336-upper.is_safe = False
337-upper = stringfilter(upper)
338-
339-def urlencode(value):
340-    """Escapes a value for use in a URL."""
341-    from django.utils.http import urlquote
342-    return urlquote(value)
343-urlencode.is_safe = False
344-urlencode = stringfilter(urlencode)
345-
346-def urlize(value, autoescape=None):
347-    """Converts URLs in plain text into clickable links."""
348-    from django.utils.html import urlize
349-    return mark_safe(urlize(value, nofollow=True, autoescape=autoescape))
350-urlize.is_safe=True
351-urlize.needs_autoescape = True
352-urlize = stringfilter(urlize)
353-
354-def urlizetrunc(value, limit, autoescape=None):
355-    """
356-    Converts URLs into clickable links, truncating URLs to the given character
357-    limit, and adding 'rel=nofollow' attribute to discourage spamming.
358-
359-    Argument: Length to truncate URLs to.
360-    """
361-    from django.utils.html import urlize
362-    return mark_safe(urlize(value, trim_url_limit=int(limit), nofollow=True,
363-                            autoescape=autoescape))
364-urlizetrunc.is_safe = True
365-urlizetrunc.needs_autoescape = True
366-urlizetrunc = stringfilter(urlizetrunc)
367-
368-def wordcount(value):
369-    """Returns the number of words."""
370-    return len(value.split())
371-wordcount.is_safe = False
372-wordcount = stringfilter(wordcount)
373-
374-def wordwrap(value, arg):
375-    """
376-    Wraps words at specified line length.
377-
378-    Argument: number of characters to wrap the text at.
379-    """
380-    from django.utils.text import wrap
381-    return wrap(value, int(arg))
382-wordwrap.is_safe = True
383-wordwrap = stringfilter(wordwrap)
384-
385-def ljust(value, arg):
386-    """
387-    Left-aligns the value in a field of a given width.
388-
389-    Argument: field size.
390-    """
391-    return value.ljust(int(arg))
392-ljust.is_safe = True
393-ljust = stringfilter(ljust)
394-
395-def rjust(value, arg):
396-    """
397-    Right-aligns the value in a field of a given width.
398-
399-    Argument: field size.
400-    """
401-    return value.rjust(int(arg))
402-rjust.is_safe = True
403-rjust = stringfilter(rjust)
404-
405-def center(value, arg):
406-    """Centers the value in a field of a given width."""
407-    return value.center(int(arg))
408-center.is_safe = True
409-center = stringfilter(center)
410-
411-def cut(value, arg):
412-    """
413-    Removes all values of arg from the given string.
414-    """
415-    safe = isinstance(value, SafeData)
416-    value = value.replace(arg, u'')
417-    if safe and arg != ';':
418-        return mark_safe(value)
419-    return value
420-cut = stringfilter(cut)
421-
422-###################
423-# HTML STRINGS    #
424-###################
425-
426-def escape(value):
427-    """
428-    Marks the value as a string that should not be auto-escaped.
429-    """
430-    from django.utils.safestring import mark_for_escaping
431-    return mark_for_escaping(value)
432-escape.is_safe = True
433-escape = stringfilter(escape)
434-
435-def force_escape(value):
436-    """
437-    Escapes a string's HTML. This returns a new string containing the escaped
438-    characters (as opposed to "escape", which marks the content for later
439-    possible escaping).
440-    """
441-    from django.utils.html import escape
442-    return mark_safe(escape(value))
443-force_escape = stringfilter(force_escape)
444-force_escape.is_safe = True
445-
446-def linebreaks(value, autoescape=None):
447-    """
448-    Replaces line breaks in plain text with appropriate HTML; a single
449-    newline becomes an HTML line break (``<br />``) and a new line
450-    followed by a blank line becomes a paragraph break (``</p>``).
451-    """
452-    from django.utils.html import linebreaks
453-    autoescape = autoescape and not isinstance(value, SafeData)
454-    return mark_safe(linebreaks(value, autoescape))
455-linebreaks.is_safe = True
456-linebreaks.needs_autoescape = True
457-linebreaks = stringfilter(linebreaks)
458-
459-def linebreaksbr(value, autoescape=None):
460-    """
461-    Converts all newlines in a piece of plain text to HTML line breaks
462-    (``<br />``).
463-    """
464-    if autoescape and not isinstance(value, SafeData):
465-        from django.utils.html import escape
466-        value = escape(value)
467-    return mark_safe(value.replace('\n', '<br />'))
468-linebreaksbr.is_safe = True
469-linebreaksbr.needs_autoescape = True
470-linebreaksbr = stringfilter(linebreaksbr)
471-
472-def safe(value):
473-    """
474-    Marks the value as a string that should not be auto-escaped.
475-    """
476-    from django.utils.safestring import mark_safe
477-    return mark_safe(value)
478-safe.is_safe = True
479-safe = stringfilter(safe)
480-
481-def removetags(value, tags):
482-    """Removes a space separated list of [X]HTML tags from the output."""
483-    tags = [re.escape(tag) for tag in tags.split()]
484-    tags_re = u'(%s)' % u'|'.join(tags)
485-    starttag_re = re.compile(ur'<%s(/?>|(\s+[^>]*>))' % tags_re, re.U)
486-    endtag_re = re.compile(u'</%s>' % tags_re)
487-    value = starttag_re.sub(u'', value)
488-    value = endtag_re.sub(u'', value)
489-    return value
490-removetags.is_safe = True
491-removetags = stringfilter(removetags)
492-
493-def striptags(value):
494-    """Strips all [X]HTML tags."""
495-    from django.utils.html import strip_tags
496-    return strip_tags(value)
497-striptags.is_safe = True
498-striptags = stringfilter(striptags)
499-
500-###################
501-# LISTS           #
502-###################
503-
504-def dictsort(value, arg):
505-    """
506-    Takes a list of dicts, returns that list sorted by the property given in
507-    the argument.
508-    """
509-    var_resolve = Variable(arg).resolve
510-    decorated = [(var_resolve(item), item) for item in value]
511-    decorated.sort()
512-    return [item[1] for item in decorated]
513-dictsort.is_safe = False
514-
515-def dictsortreversed(value, arg):
516-    """
517-    Takes a list of dicts, returns that list sorted in reverse order by the
518-    property given in the argument.
519-    """
520-    var_resolve = Variable(arg).resolve
521-    decorated = [(var_resolve(item), item) for item in value]
522-    decorated.sort()
523-    decorated.reverse()
524-    return [item[1] for item in decorated]
525-dictsortreversed.is_safe = False
526-
527-def first(value):
528-    """Returns the first item in a list."""
529-    try:
530-        return value[0]
531-    except IndexError:
532-        return u''
533-first.is_safe = False
534-
535-def join(value, arg):
536-    """Joins a list with a string, like Python's ``str.join(list)``."""
537-    try:
538-        data = arg.join(map(force_unicode, value))
539-    except AttributeError: # fail silently but nicely
540-        return value
541-    safe_args = reduce(lambda lhs, rhs: lhs and isinstance(rhs, SafeData),
542-            value, True)
543-    if safe_args:
544-        return mark_safe(data)
545-    else:
546-        return data
547-join.is_safe = True
548-
549-def last(value):
550-    "Returns the last item in a list"
551-    try:
552-        return value[-1]
553-    except IndexError:
554-        return u''
555-last.is_safe = True
556-
557-def length(value):
558-    """Returns the length of the value - useful for lists."""
559-    return len(value)
560-length.is_safe = True
561-
562-def length_is(value, arg):
563-    """Returns a boolean of whether the value's length is the argument."""
564-    return len(value) == int(arg)
565-length_is.is_safe = True
566-
567-def random(value):
568-    """Returns a random item from the list."""
569-    return random_module.choice(value)
570-random.is_safe = True
571-
572-def slice_(value, arg):
573-    """
574-    Returns a slice of the list.
575-
576-    Uses the same syntax as Python's list slicing; see
577-    http://diveintopython.org/native_data_types/lists.html#odbchelper.list.slice
578-    for an introduction.
579-    """
580-    try:
581-        bits = []
582-        for x in arg.split(u':'):
583-            if len(x) == 0:
584-                bits.append(None)
585-            else:
586-                bits.append(int(x))
587-        return value[slice(*bits)]
588-
589-    except (ValueError, TypeError):
590-        return value # Fail silently.
591-slice_.is_safe = True
592-
593-def unordered_list(value, autoescape=None):
594-    """
595-    Recursively takes a self-nested list and returns an HTML unordered list --
596-    WITHOUT opening and closing <ul> tags.
597-
598-    The list is assumed to be in the proper format. For example, if ``var``
599-    contains: ``['States', ['Kansas', ['Lawrence', 'Topeka'], 'Illinois']]``,
600-    then ``{{ var|unordered_list }}`` would return::
601-
602-        <li>States
603-        <ul>
604-                <li>Kansas
605-                <ul>
606-                        <li>Lawrence</li>
607-                        <li>Topeka</li>
608-                </ul>
609-                </li>
610-                <li>Illinois</li>
611-        </ul>
612-        </li>
613-    """
614-    if autoescape:
615-        from django.utils.html import conditional_escape
616-        escaper = conditional_escape
617-    else:
618-        escaper = lambda x: x
619-    def convert_old_style_list(list_):
620-        """
621-        Converts old style lists to the new easier to understand format.
622-
623-        The old list format looked like:
624-            ['Item 1', [['Item 1.1', []], ['Item 1.2', []]]
625-
626-        And it is converted to:
627-            ['Item 1', ['Item 1.1', 'Item 1.2]]
628-        """
629-        if not isinstance(list_, (tuple, list)) or len(list_) != 2:
630-            return list_, False
631-        first_item, second_item = list_
632-        if second_item == []:
633-            return [first_item], True
634-        old_style_list = True
635-        new_second_item = []
636-        for sublist in second_item:
637-            item, old_style_list = convert_old_style_list(sublist)
638-            if not old_style_list:
639-                break
640-            new_second_item.extend(item)
641-        if old_style_list:
642-            second_item = new_second_item
643-        return [first_item, second_item], old_style_list
644-    def _helper(list_, tabs=1):
645-        indent = u'\t' * tabs
646-        output = []
647-
648-        list_length = len(list_)
649-        i = 0
650-        while i < list_length:
651-            title = list_[i]
652-            sublist = ''
653-            sublist_item = None
654-            if isinstance(title, (list, tuple)):
655-                sublist_item = title
656-                title = ''
657-            elif i < list_length - 1:
658-                next_item = list_[i+1]
659-                if next_item and isinstance(next_item, (list, tuple)):
660-                    # The next item is a sub-list.
661-                    sublist_item = next_item
662-                    # We've processed the next item now too.
663-                    i += 1
664-            if sublist_item:
665-                sublist = _helper(sublist_item, tabs+1)
666-                sublist = '\n%s<ul>\n%s\n%s</ul>\n%s' % (indent, sublist,
667-                                                         indent, indent)
668-            output.append('%s<li>%s%s</li>' % (indent,
669-                    escaper(force_unicode(title)), sublist))
670-            i += 1
671-        return '\n'.join(output)
672-    value, converted = convert_old_style_list(value)
673-    return mark_safe(_helper(value))
674-unordered_list.is_safe = True
675-unordered_list.needs_autoescape = True
676-
677-###################
678-# INTEGERS        #
679-###################
680-
681-def add(value, arg):
682-    """Adds the arg to the value."""
683-    return int(value) + int(arg)
684-add.is_safe = False
685-
686-def get_digit(value, arg):
687-    """
688-    Given a whole number, returns the requested digit of it, where 1 is the
689-    right-most digit, 2 is the second-right-most digit, etc. Returns the
690-    original value for invalid input (if input or argument is not an integer,
691-    or if argument is less than 1). Otherwise, output is always an integer.
692-    """
693-    try:
694-        arg = int(arg)
695-        value = int(value)
696-    except ValueError:
697-        return value # Fail silently for an invalid argument
698-    if arg < 1:
699-        return value
700-    try:
701-        return int(str(value)[-arg])
702-    except IndexError:
703-        return 0
704-get_digit.is_safe = False
705-
706-###################
707-# DATES           #
708-###################
709-
710-def date(value, arg=None):
711-    """Formats a date according to the given format."""
712-    from django.utils.dateformat import format
713-    if not value:
714-        return u''
715-    if arg is None:
716-        arg = settings.DATE_FORMAT
717-    return format(value, arg)
718-date.is_safe = False
719-
720-def time(value, arg=None):
721-    """Formats a time according to the given format."""
722-    from django.utils.dateformat import time_format
723-    if value in (None, u''):
724-        return u''
725-    if arg is None:
726-        arg = settings.TIME_FORMAT
727-    return time_format(value, arg)
728-time.is_safe = False
729-
730-def timesince(value, arg=None):
731-    """Formats a date as the time since that date (i.e. "4 days, 6 hours")."""
732-    from django.utils.timesince import timesince
733-    if not value:
734-        return u''
735-    if arg:
736-        return timesince(arg, value)
737-    return timesince(value)
738-timesince.is_safe = False
739-
740-def timeuntil(value, arg=None):
741-    """Formats a date as the time until that date (i.e. "4 days, 6 hours")."""
742-    from django.utils.timesince import timesince
743-    from datetime import datetime
744-    if not value:
745-        return u''
746-    if arg:
747-        return timesince(arg, value)
748-    return timesince(datetime.now(), value)
749-timeuntil.is_safe = False
750-
751-###################
752-# LOGIC           #
753-###################
754-
755-def default(value, arg):
756-    """If value is unavailable, use given default."""
757-    return value or arg
758-default.is_safe = False
759-
760-def default_if_none(value, arg):
761-    """If value is None, use given default."""
762-    if value is None:
763-        return arg
764-    return value
765-default_if_none.is_safe = False
766-
767-def divisibleby(value, arg):
768-    """Returns True if the value is devisible by the argument."""
769-    return int(value) % int(arg) == 0
770-divisibleby.is_safe = False
771-
772-def yesno(value, arg=None):
773-    """
774-    Given a string mapping values for true, false and (optionally) None,
775-    returns one of those strings accoding to the value:
776-
777-    ==========  ======================  ==================================
778-    Value       Argument                Outputs
779-    ==========  ======================  ==================================
780-    ``True``    ``"yeah,no,maybe"``     ``yeah``
781-    ``False``   ``"yeah,no,maybe"``     ``no``
782-    ``None``    ``"yeah,no,maybe"``     ``maybe``
783-    ``None``    ``"yeah,no"``           ``"no"`` (converts None to False
784-                                        if no mapping for None is given.
785-    ==========  ======================  ==================================
786-    """
787-    if arg is None:
788-        arg = ugettext('yes,no,maybe')
789-    bits = arg.split(u',')
790-    if len(bits) < 2:
791-        return value # Invalid arg.
792-    try:
793-        yes, no, maybe = bits
794-    except ValueError:
795-        # Unpack list of wrong size (no "maybe" value provided).
796-        yes, no, maybe = bits[0], bits[1], bits[1]
797-    if value is None:
798-        return maybe
799-    if value:
800-        return yes
801-    return no
802-yesno.is_safe = False
803-
804-###################
805-# MISC            #
806-###################
807-
808-def filesizeformat(bytes):
809-    """
810-    Formats the value like a 'human-readable' file size (i.e. 13 KB, 4.1 MB,
811-    102 bytes, etc).
812-    """
813-    try:
814-        bytes = float(bytes)
815-    except TypeError:
816-        return u"0 bytes"
817-
818-    if bytes < 1024:
819-        return ungettext("%(size)d byte", "%(size)d bytes", bytes) % {'size': bytes}
820-    if bytes < 1024 * 1024:
821-        return ugettext("%.1f KB") % (bytes / 1024)
822-    if bytes < 1024 * 1024 * 1024:
823-        return ugettext("%.1f MB") % (bytes / (1024 * 1024))
824-    return ugettext("%.1f GB") % (bytes / (1024 * 1024 * 1024))
825-filesizeformat.is_safe = True
826-
827-def pluralize(value, arg=u's'):
828-    """
829-    Returns a plural suffix if the value is not 1. By default, 's' is used as
830-    the suffix:
831-
832-    * If value is 0, vote{{ value|pluralize }} displays "0 votes".
833-    * If value is 1, vote{{ value|pluralize }} displays "1 vote".
834-    * If value is 2, vote{{ value|pluralize }} displays "2 votes".
835-
836-    If an argument is provided, that string is used instead:
837-
838-    * If value is 0, class{{ value|pluralize:"es" }} displays "0 classes".
839-    * If value is 1, class{{ value|pluralize:"es" }} displays "1 class".
840-    * If value is 2, class{{ value|pluralize:"es" }} displays "2 classes".
841-
842-    If the provided argument contains a comma, the text before the comma is
843-    used for the singular case and the text after the comma is used for the
844-    plural case:
845-
846-    * If value is 0, cand{{ value|pluralize:"y,ies" }} displays "0 candies".
847-    * If value is 1, cand{{ value|pluralize:"y,ies" }} displays "1 candy".
848-    * If value is 2, cand{{ value|pluralize:"y,ies" }} displays "2 candies".
849-    """
850-    if not u',' in arg:
851-        arg = u',' + arg
852-    bits = arg.split(u',')
853-    if len(bits) > 2:
854-        return u''
855-    singular_suffix, plural_suffix = bits[:2]
856-
857-    try:
858-        if int(value) != 1:
859-            return plural_suffix
860-    except ValueError: # Invalid string that's not a number.
861-        pass
862-    except TypeError: # Value isn't a string or a number; maybe it's a list?
863-        try:
864-            if len(value) != 1:
865-                return plural_suffix
866-        except TypeError: # len() of unsized object.
867-            pass
868-    return singular_suffix
869-pluralize.is_safe = False
870-
871-def phone2numeric(value):
872-    """Takes a phone number and converts it in to its numerical equivalent."""
873-    from django.utils.text import phone2numeric
874-    return phone2numeric(value)
875-phone2numeric.is_safe = True
876-
877-def pprint(value):
878-    """A wrapper around pprint.pprint -- for debugging, really."""
879-    from pprint import pformat
880-    try:
881-        return pformat(value)
882-    except Exception, e:
883-        return u"Error in formatting: %s" % force_unicode(e, errors="replace")
884-pprint.is_safe = True
885-
886-# Syntax: register.filter(name of filter, callback)
887-register.filter(add)
888-register.filter(addslashes)
889-register.filter(capfirst)
890-register.filter(center)
891-register.filter(cut)
892-register.filter(date)
893-register.filter(default)
894-register.filter(default_if_none)
895-register.filter(dictsort)
896-register.filter(dictsortreversed)
897-register.filter(divisibleby)
898-register.filter(escape)
899-register.filter(escapejs)
900-register.filter(filesizeformat)
901-register.filter(first)
902-register.filter(fix_ampersands)
903-register.filter(floatformat)
904-register.filter(force_escape)
905-register.filter(get_digit)
906-register.filter(iriencode)
907-register.filter(join)
908-register.filter(last)
909-register.filter(length)
910-register.filter(length_is)
911-register.filter(linebreaks)
912-register.filter(linebreaksbr)
913-register.filter(linenumbers)
914-register.filter(ljust)
915-register.filter(lower)
916-register.filter(make_list)
917-register.filter(phone2numeric)
918-register.filter(pluralize)
919-register.filter(pprint)
920-register.filter(removetags)
921-register.filter(random)
922-register.filter(rjust)
923-register.filter(safe)
924-register.filter('slice', slice_)
925-register.filter(slugify)
926-register.filter(stringformat)
927-register.filter(striptags)
928-register.filter(time)
929-register.filter(timesince)
930-register.filter(timeuntil)
931-register.filter(title)
932-register.filter(truncatewords)
933-register.filter(truncatewords_html)
934-register.filter(unordered_list)
935-register.filter(upper)
936-register.filter(urlencode)
937-register.filter(urlize)
938-register.filter(urlizetrunc)
939-register.filter(wordcount)
940-register.filter(wordwrap)
941-register.filter(yesno)
942diff --git a/django/template/defaulttags.py b/django/template/defaulttags.py
943deleted file mode 100644
944index 01c43ee..0000000
945--- a/django/template/defaulttags.py
946+++ /dev/null
947@@ -1,1105 +0,0 @@
948-"""Default tags used by the template system, available to all templates."""
949-
950-import sys
951-import re
952-from itertools import cycle as itertools_cycle
953-try:
954-    reversed
955-except NameError:
956-    from django.utils.itercompat import reversed     # Python 2.3 fallback
957-
958-from django.template import Node, NodeList, Template, Context, Variable
959-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
960-from django.template import get_library, Library, InvalidTemplateLibrary
961-from django.conf import settings
962-from django.utils.encoding import smart_str, smart_unicode
963-from django.utils.itercompat import groupby
964-from django.utils.safestring import mark_safe
965-
966-register = Library()
967-
968-class AutoEscapeControlNode(Node):
969-    """Implements the actions of the autoescape tag."""
970-    def __init__(self, setting, nodelist):
971-        self.setting, self.nodelist = setting, nodelist
972-
973-    def render(self, context):
974-        old_setting = context.autoescape
975-        context.autoescape = self.setting
976-        output = self.nodelist.render(context)
977-        context.autoescape = old_setting
978-        if self.setting:
979-            return mark_safe(output)
980-        else:
981-            return output
982-
983-class CommentNode(Node):
984-    def render(self, context):
985-        return ''
986-
987-class CycleNode(Node):
988-    def __init__(self, cyclevars, variable_name=None):
989-        self.cycle_iter = itertools_cycle([Variable(v) for v in cyclevars])
990-        self.variable_name = variable_name
991-
992-    def render(self, context):
993-        value = self.cycle_iter.next().resolve(context)
994-        if self.variable_name:
995-            context[self.variable_name] = value
996-        return value
997-
998-class DebugNode(Node):
999-    def render(self, context):
1000-        from pprint import pformat
1001-        output = [pformat(val) for val in context]
1002-        output.append('\n\n')
1003-        output.append(pformat(sys.modules))
1004-        return ''.join(output)
1005-
1006-class FilterNode(Node):
1007-    def __init__(self, filter_expr, nodelist):
1008-        self.filter_expr, self.nodelist = filter_expr, nodelist
1009-
1010-    def render(self, context):
1011-        output = self.nodelist.render(context)
1012-        # Apply filters.
1013-        context.update({'var': output})
1014-        filtered = self.filter_expr.resolve(context)
1015-        context.pop()
1016-        return filtered
1017-
1018-class FirstOfNode(Node):
1019-    def __init__(self, vars):
1020-        self.vars = map(Variable, vars)
1021-
1022-    def render(self, context):
1023-        for var in self.vars:
1024-            try:
1025-                value = var.resolve(context)
1026-            except VariableDoesNotExist:
1027-                continue
1028-            if value:
1029-                return smart_unicode(value)
1030-        return u''
1031-
1032-class ForNode(Node):
1033-    def __init__(self, loopvars, sequence, is_reversed, nodelist_loop):
1034-        self.loopvars, self.sequence = loopvars, sequence
1035-        self.is_reversed = is_reversed
1036-        self.nodelist_loop = nodelist_loop
1037-
1038-    def __repr__(self):
1039-        reversed_text = self.is_reversed and ' reversed' or ''
1040-        return "<For Node: for %s in %s, tail_len: %d%s>" % \
1041-            (', '.join(self.loopvars), self.sequence, len(self.nodelist_loop),
1042-             reversed_text)
1043-
1044-    def __iter__(self):
1045-        for node in self.nodelist_loop:
1046-            yield node
1047-
1048-    def get_nodes_by_type(self, nodetype):
1049-        nodes = []
1050-        if isinstance(self, nodetype):
1051-            nodes.append(self)
1052-        nodes.extend(self.nodelist_loop.get_nodes_by_type(nodetype))
1053-        return nodes
1054-
1055-    def render(self, context):
1056-        nodelist = NodeList()
1057-        if 'forloop' in context:
1058-            parentloop = context['forloop']
1059-        else:
1060-            parentloop = {}
1061-        context.push()
1062-        try:
1063-            values = self.sequence.resolve(context, True)
1064-        except VariableDoesNotExist:
1065-            values = []
1066-        if values is None:
1067-            values = []
1068-        if not hasattr(values, '__len__'):
1069-            values = list(values)
1070-        len_values = len(values)
1071-        if self.is_reversed:
1072-            values = reversed(values)
1073-        unpack = len(self.loopvars) > 1
1074-        # Create a forloop value in the context.  We'll update counters on each
1075-        # iteration just below.
1076-        loop_dict = context['forloop'] = {'parentloop': parentloop}
1077-        for i, item in enumerate(values):
1078-            # Shortcuts for current loop iteration number.
1079-            loop_dict['counter0'] = i
1080-            loop_dict['counter'] = i+1
1081-            # Reverse counter iteration numbers.
1082-            loop_dict['revcounter'] = len_values - i
1083-            loop_dict['revcounter0'] = len_values - i - 1
1084-            # Boolean values designating first and last times through loop.
1085-            loop_dict['first'] = (i == 0)
1086-            loop_dict['last'] = (i == len_values - 1)
1087-
1088-            if unpack:
1089-                # If there are multiple loop variables, unpack the item into
1090-                # them.
1091-                context.update(dict(zip(self.loopvars, item)))
1092-            else:
1093-                context[self.loopvars[0]] = item
1094-            for node in self.nodelist_loop:
1095-                nodelist.append(node.render(context))
1096-            if unpack:
1097-                # The loop variables were pushed on to the context so pop them
1098-                # off again. This is necessary because the tag lets the length
1099-                # of loopvars differ to the length of each set of items and we
1100-                # don't want to leave any vars from the previous loop on the
1101-                # context.
1102-                context.pop()
1103-        context.pop()
1104-        return nodelist.render(context)
1105-
1106-class IfChangedNode(Node):
1107-    def __init__(self, nodelist, *varlist):
1108-        self.nodelist = nodelist
1109-        self._last_seen = None
1110-        self._varlist = map(Variable, varlist)
1111-        self._id = str(id(self))
1112-
1113-    def render(self, context):
1114-        if 'forloop' in context and self._id not in context['forloop']:
1115-            self._last_seen = None
1116-            context['forloop'][self._id] = 1
1117-        try:
1118-            if self._varlist:
1119-                # Consider multiple parameters.  This automatically behaves
1120-                # like an OR evaluation of the multiple variables.
1121-                compare_to = [var.resolve(context) for var in self._varlist]
1122-            else:
1123-                compare_to = self.nodelist.render(context)
1124-        except VariableDoesNotExist:
1125-            compare_to = None
1126-
1127-        if  compare_to != self._last_seen:
1128-            firstloop = (self._last_seen == None)
1129-            self._last_seen = compare_to
1130-            context.push()
1131-            context['ifchanged'] = {'firstloop': firstloop}
1132-            content = self.nodelist.render(context)
1133-            context.pop()
1134-            return content
1135-        else:
1136-            return ''
1137-
1138-class IfEqualNode(Node):
1139-    def __init__(self, var1, var2, nodelist_true, nodelist_false, negate):
1140-        self.var1, self.var2 = Variable(var1), Variable(var2)
1141-        self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false
1142-        self.negate = negate
1143-
1144-    def __repr__(self):
1145-        return "<IfEqualNode>"
1146-
1147-    def render(self, context):
1148-        try:
1149-            val1 = self.var1.resolve(context)
1150-        except VariableDoesNotExist:
1151-            val1 = None
1152-        try:
1153-            val2 = self.var2.resolve(context)
1154-        except VariableDoesNotExist:
1155-            val2 = None
1156-        if (self.negate and val1 != val2) or (not self.negate and val1 == val2):
1157-            return self.nodelist_true.render(context)
1158-        return self.nodelist_false.render(context)
1159-
1160-class IfNode(Node):
1161-    def __init__(self, bool_exprs, nodelist_true, nodelist_false, link_type):
1162-        self.bool_exprs = bool_exprs
1163-        self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false
1164-        self.link_type = link_type
1165-
1166-    def __repr__(self):
1167-        return "<If node>"
1168-
1169-    def __iter__(self):
1170-        for node in self.nodelist_true:
1171-            yield node
1172-        for node in self.nodelist_false:
1173-            yield node
1174-
1175-    def get_nodes_by_type(self, nodetype):
1176-        nodes = []
1177-        if isinstance(self, nodetype):
1178-            nodes.append(self)
1179-        nodes.extend(self.nodelist_true.get_nodes_by_type(nodetype))
1180-        nodes.extend(self.nodelist_false.get_nodes_by_type(nodetype))
1181-        return nodes
1182-
1183-    def render(self, context):
1184-        if self.link_type == IfNode.LinkTypes.or_:
1185-            for ifnot, bool_expr in self.bool_exprs:
1186-                try:
1187-                    value = bool_expr.resolve(context, True)
1188-                except VariableDoesNotExist:
1189-                    value = None
1190-                if (value and not ifnot) or (ifnot and not value):
1191-                    return self.nodelist_true.render(context)
1192-            return self.nodelist_false.render(context)
1193-        else:
1194-            for ifnot, bool_expr in self.bool_exprs:
1195-                try:
1196-                    value = bool_expr.resolve(context, True)
1197-                except VariableDoesNotExist:
1198-                    value = None
1199-                if not ((value and not ifnot) or (ifnot and not value)):
1200-                    return self.nodelist_false.render(context)
1201-            return self.nodelist_true.render(context)
1202-
1203-    class LinkTypes:
1204-        and_ = 0,
1205-        or_ = 1
1206-
1207-class RegroupNode(Node):
1208-    def __init__(self, target, expression, var_name):
1209-        self.target, self.expression = target, expression
1210-        self.var_name = var_name
1211-
1212-    def render(self, context):
1213-        obj_list = self.target.resolve(context, True)
1214-        if obj_list == None:
1215-            # target variable wasn't found in context; fail silently.
1216-            context[self.var_name] = []
1217-            return ''
1218-        # List of dictionaries in the format:
1219-        # {'grouper': 'key', 'list': [list of contents]}.
1220-        context[self.var_name] = [
1221-            {'grouper': key, 'list': list(val)}
1222-            for key, val in
1223-            groupby(obj_list, lambda v, f=self.expression.resolve: f(v, True))
1224-        ]
1225-        return ''
1226-
1227-def include_is_allowed(filepath):
1228-    for root in settings.ALLOWED_INCLUDE_ROOTS:
1229-        if filepath.startswith(root):
1230-            return True
1231-    return False
1232-
1233-class SsiNode(Node):
1234-    def __init__(self, filepath, parsed):
1235-        self.filepath, self.parsed = filepath, parsed
1236-
1237-    def render(self, context):
1238-        if not include_is_allowed(self.filepath):
1239-            if settings.DEBUG:
1240-                return "[Didn't have permission to include file]"
1241-            else:
1242-                return '' # Fail silently for invalid includes.
1243-        try:
1244-            fp = open(self.filepath, 'r')
1245-            output = fp.read()
1246-            fp.close()
1247-        except IOError:
1248-            output = ''
1249-        if self.parsed:
1250-            try:
1251-                t = Template(output, name=self.filepath)
1252-                return t.render(context)
1253-            except TemplateSyntaxError, e:
1254-                if settings.DEBUG:
1255-                    return "[Included template had syntax error: %s]" % e
1256-                else:
1257-                    return '' # Fail silently for invalid included templates.
1258-        return output
1259-
1260-class LoadNode(Node):
1261-    def render(self, context):
1262-        return ''
1263-
1264-class NowNode(Node):
1265-    def __init__(self, format_string):
1266-        self.format_string = format_string
1267-
1268-    def render(self, context):
1269-        from datetime import datetime
1270-        from django.utils.dateformat import DateFormat
1271-        df = DateFormat(datetime.now())
1272-        return df.format(self.format_string)
1273-
1274-class SpacelessNode(Node):
1275-    def __init__(self, nodelist):
1276-        self.nodelist = nodelist
1277-
1278-    def render(self, context):
1279-        from django.utils.html import strip_spaces_between_tags
1280-        return strip_spaces_between_tags(self.nodelist.render(context).strip())
1281-
1282-class TemplateTagNode(Node):
1283-    mapping = {'openblock': BLOCK_TAG_START,
1284-               'closeblock': BLOCK_TAG_END,
1285-               'openvariable': VARIABLE_TAG_START,
1286-               'closevariable': VARIABLE_TAG_END,
1287-               'openbrace': SINGLE_BRACE_START,
1288-               'closebrace': SINGLE_BRACE_END,
1289-               'opencomment': COMMENT_TAG_START,
1290-               'closecomment': COMMENT_TAG_END,
1291-               }
1292-
1293-    def __init__(self, tagtype):
1294-        self.tagtype = tagtype
1295-
1296-    def render(self, context):
1297-        return self.mapping.get(self.tagtype, '')
1298-
1299-class URLNode(Node):
1300-    def __init__(self, view_name, args, kwargs):
1301-        self.view_name = view_name
1302-        self.args = args
1303-        self.kwargs = kwargs
1304-
1305-    def render(self, context):
1306-        from django.core.urlresolvers import reverse, NoReverseMatch
1307-        args = [arg.resolve(context) for arg in self.args]
1308-        kwargs = dict([(smart_str(k,'ascii'), v.resolve(context))
1309-                       for k, v in self.kwargs.items()])
1310-        try:
1311-            return reverse(self.view_name, args=args, kwargs=kwargs)
1312-        except NoReverseMatch:
1313-            try:
1314-                project_name = settings.SETTINGS_MODULE.split('.')[0]
1315-                return reverse(project_name + '.' + self.view_name,
1316-                               args=args, kwargs=kwargs)
1317-            except NoReverseMatch:
1318-                return ''
1319-
1320-class WidthRatioNode(Node):
1321-    def __init__(self, val_expr, max_expr, max_width):
1322-        self.val_expr = val_expr
1323-        self.max_expr = max_expr
1324-        self.max_width = max_width
1325-
1326-    def render(self, context):
1327-        try:
1328-            value = self.val_expr.resolve(context)
1329-            maxvalue = self.max_expr.resolve(context)
1330-        except VariableDoesNotExist:
1331-            return ''
1332-        try:
1333-            value = float(value)
1334-            maxvalue = float(maxvalue)
1335-            ratio = (value / maxvalue) * int(self.max_width)
1336-        except (ValueError, ZeroDivisionError):
1337-            return ''
1338-        return str(int(round(ratio)))
1339-
1340-class WithNode(Node):
1341-    def __init__(self, var, name, nodelist):
1342-        self.var = var
1343-        self.name = name
1344-        self.nodelist = nodelist
1345-
1346-    def __repr__(self):
1347-        return "<WithNode>"
1348-
1349-    def render(self, context):
1350-        val = self.var.resolve(context)
1351-        context.push()
1352-        context[self.name] = val
1353-        output = self.nodelist.render(context)
1354-        context.pop()
1355-        return output
1356-
1357-#@register.tag
1358-def autoescape(parser, token):
1359-    """
1360-    Force autoescape behaviour for this block.
1361-    """
1362-    args = token.contents.split()
1363-    if len(args) != 2:
1364-        raise TemplateSyntaxError("'Autoescape' tag requires exactly one argument.")
1365-    arg = args[1]
1366-    if arg not in (u'on', u'off'):
1367-        raise TemplateSyntaxError("'Autoescape' argument should be 'on' or 'off'")
1368-    nodelist = parser.parse(('endautoescape',))
1369-    parser.delete_first_token()
1370-    return AutoEscapeControlNode((arg == 'on'), nodelist)
1371-autoescape = register.tag(autoescape)
1372-
1373-#@register.tag
1374-def comment(parser, token):
1375-    """
1376-    Ignores everything between ``{% comment %}`` and ``{% endcomment %}``.
1377-    """
1378-    parser.skip_past('endcomment')
1379-    return CommentNode()
1380-comment = register.tag(comment)
1381-
1382-#@register.tag
1383-def cycle(parser, token):
1384-    """
1385-    Cycles among the given strings each time this tag is encountered.
1386-
1387-    Within a loop, cycles among the given strings each time through
1388-    the loop::
1389-
1390-        {% for o in some_list %}
1391-            <tr class="{% cycle 'row1' 'row2' %}">
1392-                ...
1393-            </tr>
1394-        {% endfor %}
1395-
1396-    Outside of a loop, give the values a unique name the first time you call
1397-    it, then use that name each sucessive time through::
1398-
1399-            <tr class="{% cycle 'row1' 'row2' 'row3' as rowcolors %}">...</tr>
1400-            <tr class="{% cycle rowcolors %}">...</tr>
1401-            <tr class="{% cycle rowcolors %}">...</tr>
1402-
1403-    You can use any number of values, separated by spaces. Commas can also
1404-    be used to separate values; if a comma is used, the cycle values are
1405-    interpreted as literal strings.
1406-    """
1407-
1408-    # Note: This returns the exact same node on each {% cycle name %} call;
1409-    # that is, the node object returned from {% cycle a b c as name %} and the
1410-    # one returned from {% cycle name %} are the exact same object. This
1411-    # shouldn't cause problems (heh), but if it does, now you know.
1412-    #
1413-    # Ugly hack warning: This stuffs the named template dict into parser so
1414-    # that names are only unique within each template (as opposed to using
1415-    # a global variable, which would make cycle names have to be unique across
1416-    # *all* templates.
1417-
1418-    args = token.split_contents()
1419-
1420-    if len(args) < 2:
1421-        raise TemplateSyntaxError("'cycle' tag requires at least two arguments")
1422-
1423-    if ',' in args[1]:
1424-        # Backwards compatibility: {% cycle a,b %} or {% cycle a,b as foo %}
1425-        # case.
1426-        args[1:2] = ['"%s"' % arg for arg in args[1].split(",")]
1427-
1428-    if len(args) == 2:
1429-        # {% cycle foo %} case.
1430-        name = args[1]
1431-        if not hasattr(parser, '_namedCycleNodes'):
1432-            raise TemplateSyntaxError("No named cycles in template. '%s' is not defined" % name)
1433-        if not name in parser._namedCycleNodes:
1434-            raise TemplateSyntaxError("Named cycle '%s' does not exist" % name)
1435-        return parser._namedCycleNodes[name]
1436-
1437-    if len(args) > 4 and args[-2] == 'as':
1438-        name = args[-1]
1439-        node = CycleNode(args[1:-2], name)
1440-        if not hasattr(parser, '_namedCycleNodes'):
1441-            parser._namedCycleNodes = {}
1442-        parser._namedCycleNodes[name] = node
1443-    else:
1444-        node = CycleNode(args[1:])
1445-    return node
1446-cycle = register.tag(cycle)
1447-
1448-def debug(parser, token):
1449-    """
1450-    Outputs a whole load of debugging information, including the current
1451-    context and imported modules.
1452-
1453-    Sample usage::
1454-
1455-        <pre>
1456-            {% debug %}
1457-        </pre>
1458-    """
1459-    return DebugNode()
1460-debug = register.tag(debug)
1461-
1462-#@register.tag(name="filter")
1463-def do_filter(parser, token):
1464-    """
1465-    Filters the contents of the block through variable filters.
1466-
1467-    Filters can also be piped through each other, and they can have
1468-    arguments -- just like in variable syntax.
1469-
1470-    Sample usage::
1471-
1472-        {% filter force_escape|lower %}
1473-            This text will be HTML-escaped, and will appear in lowercase.
1474-        {% endfilter %}
1475-    """
1476-    _, rest = token.contents.split(None, 1)
1477-    filter_expr = parser.compile_filter("var|%s" % (rest))
1478-    for func, unused in filter_expr.filters:
1479-        if getattr(func, '_decorated_function', func).__name__ in ('escape', 'safe'):
1480-            raise TemplateSyntaxError('"filter %s" is not permitted.  Use the "autoescape" tag instead.' % func.__name__)
1481-    nodelist = parser.parse(('endfilter',))
1482-    parser.delete_first_token()
1483-    return FilterNode(filter_expr, nodelist)
1484-do_filter = register.tag("filter", do_filter)
1485-
1486-#@register.tag
1487-def firstof(parser, token):
1488-    """
1489-    Outputs the first variable passed that is not False.
1490-
1491-    Outputs nothing if all the passed variables are False.
1492-
1493-    Sample usage::
1494-
1495-        {% firstof var1 var2 var3 %}
1496-
1497-    This is equivalent to::
1498-
1499-        {% if var1 %}
1500-            {{ var1 }}
1501-        {% else %}{% if var2 %}
1502-            {{ var2 }}
1503-        {% else %}{% if var3 %}
1504-            {{ var3 }}
1505-        {% endif %}{% endif %}{% endif %}
1506-
1507-    but obviously much cleaner!
1508-
1509-    You can also use a literal string as a fallback value in case all
1510-    passed variables are False::
1511-
1512-        {% firstof var1 var2 var3 "fallback value" %}
1513-
1514-    """
1515-    bits = token.split_contents()[1:]
1516-    if len(bits) < 1:
1517-        raise TemplateSyntaxError("'firstof' statement requires at least one"
1518-                                  " argument")
1519-    return FirstOfNode(bits)
1520-firstof = register.tag(firstof)
1521-
1522-#@register.tag(name="for")
1523-def do_for(parser, token):
1524-    """
1525-    Loops over each item in an array.
1526-
1527-    For example, to display a list of athletes given ``athlete_list``::
1528-
1529-        <ul>
1530-        {% for athlete in athlete_list %}
1531-            <li>{{ athlete.name }}</li>
1532-        {% endfor %}
1533-        </ul>
1534-
1535-    You can loop over a list in reverse by using
1536-    ``{% for obj in list reversed %}``.
1537-
1538-    You can also unpack multiple values from a two-dimensional array::
1539-
1540-        {% for key,value in dict.items %}
1541-            {{ key }}: {{ value }}
1542-        {% endfor %}
1543-
1544-    The for loop sets a number of variables available within the loop:
1545-
1546-        ==========================  ================================================
1547-        Variable                    Description
1548-        ==========================  ================================================
1549-        ``forloop.counter``         The current iteration of the loop (1-indexed)
1550-        ``forloop.counter0``        The current iteration of the loop (0-indexed)
1551-        ``forloop.revcounter``      The number of iterations from the end of the
1552-                                    loop (1-indexed)
1553-        ``forloop.revcounter0``     The number of iterations from the end of the
1554-                                    loop (0-indexed)
1555-        ``forloop.first``           True if this is the first time through the loop
1556-        ``forloop.last``            True if this is the last time through the loop
1557-        ``forloop.parentloop``      For nested loops, this is the loop "above" the
1558-                                    current one
1559-        ==========================  ================================================
1560-
1561-    """
1562-    bits = token.contents.split()
1563-    if len(bits) < 4:
1564-        raise TemplateSyntaxError("'for' statements should have at least four"
1565-                                  " words: %s" % token.contents)
1566-
1567-    is_reversed = bits[-1] == 'reversed'
1568-    in_index = is_reversed and -3 or -2
1569-    if bits[in_index] != 'in':
1570-        raise TemplateSyntaxError("'for' statements should use the format"
1571-                                  " 'for x in y': %s" % token.contents)
1572-
1573-    loopvars = re.sub(r' *, *', ',', ' '.join(bits[1:in_index])).split(',')
1574-    for var in loopvars:
1575-        if not var or ' ' in var:
1576-            raise TemplateSyntaxError("'for' tag received an invalid argument:"
1577-                                      " %s" % token.contents)
1578-
1579-    sequence = parser.compile_filter(bits[in_index+1])
1580-    nodelist_loop = parser.parse(('endfor',))
1581-    parser.delete_first_token()
1582-    return ForNode(loopvars, sequence, is_reversed, nodelist_loop)
1583-do_for = register.tag("for", do_for)
1584-
1585-def do_ifequal(parser, token, negate):
1586-    bits = list(token.split_contents())
1587-    if len(bits) != 3:
1588-        raise TemplateSyntaxError, "%r takes two arguments" % bits[0]
1589-    end_tag = 'end' + bits[0]
1590-    nodelist_true = parser.parse(('else', end_tag))
1591-    token = parser.next_token()
1592-    if token.contents == 'else':
1593-        nodelist_false = parser.parse((end_tag,))
1594-        parser.delete_first_token()
1595-    else:
1596-        nodelist_false = NodeList()
1597-    return IfEqualNode(bits[1], bits[2], nodelist_true, nodelist_false, negate)
1598-
1599-#@register.tag
1600-def ifequal(parser, token):
1601-    """
1602-    Outputs the contents of the block if the two arguments equal each other.
1603-
1604-    Examples::
1605-
1606-        {% ifequal user.id comment.user_id %}
1607-            ...
1608-        {% endifequal %}
1609-
1610-        {% ifnotequal user.id comment.user_id %}
1611-            ...
1612-        {% else %}
1613-            ...
1614-        {% endifnotequal %}
1615-    """
1616-    return do_ifequal(parser, token, False)
1617-ifequal = register.tag(ifequal)
1618-
1619-#@register.tag
1620-def ifnotequal(parser, token):
1621-    """
1622-    Outputs the contents of the block if the two arguments are not equal.
1623-    See ifequal.
1624-    """
1625-    return do_ifequal(parser, token, True)
1626-ifnotequal = register.tag(ifnotequal)
1627-
1628-#@register.tag(name="if")
1629-def do_if(parser, token):
1630-    """
1631-    The ``{% if %}`` tag evaluates a variable, and if that variable is "true"
1632-    (i.e., exists, is not empty, and is not a false boolean value), the
1633-    contents of the block are output:
1634-
1635-    ::
1636-
1637-        {% if athlete_list %}
1638-            Number of athletes: {{ athlete_list|count }}
1639-        {% else %}
1640-            No athletes.
1641-        {% endif %}
1642-
1643-    In the above, if ``athlete_list`` is not empty, the number of athletes will
1644-    be displayed by the ``{{ athlete_list|count }}`` variable.
1645-
1646-    As you can see, the ``if`` tag can take an option ``{% else %}`` clause
1647-    that will be displayed if the test fails.
1648-
1649-    ``if`` tags may use ``or``, ``and`` or ``not`` to test a number of
1650-    variables or to negate a given variable::
1651-
1652-        {% if not athlete_list %}
1653-            There are no athletes.
1654-        {% endif %}
1655-
1656-        {% if athlete_list or coach_list %}
1657-            There are some athletes or some coaches.
1658-        {% endif %}
1659-
1660-        {% if athlete_list and coach_list %}
1661-            Both atheletes and coaches are available.
1662-        {% endif %}
1663-
1664-        {% if not athlete_list or coach_list %}
1665-            There are no athletes, or there are some coaches.
1666-        {% endif %}
1667-
1668-        {% if athlete_list and not coach_list %}
1669-            There are some athletes and absolutely no coaches.
1670-        {% endif %}
1671-
1672-    ``if`` tags do not allow ``and`` and ``or`` clauses with the same tag,
1673-    because the order of logic would be ambigous. For example, this is
1674-    invalid::
1675-
1676-        {% if athlete_list and coach_list or cheerleader_list %}
1677-
1678-    If you need to combine ``and`` and ``or`` to do advanced logic, just use
1679-    nested if tags. For example::
1680-
1681-        {% if athlete_list %}
1682-            {% if coach_list or cheerleader_list %}
1683-                We have athletes, and either coaches or cheerleaders!
1684-            {% endif %}
1685-        {% endif %}
1686-    """
1687-    bits = token.contents.split()
1688-    del bits[0]
1689-    if not bits:
1690-        raise TemplateSyntaxError("'if' statement requires at least one argument")
1691-    # Bits now looks something like this: ['a', 'or', 'not', 'b', 'or', 'c.d']
1692-    bitstr = ' '.join(bits)
1693-    boolpairs = bitstr.split(' and ')
1694-    boolvars = []
1695-    if len(boolpairs) == 1:
1696-        link_type = IfNode.LinkTypes.or_
1697-        boolpairs = bitstr.split(' or ')
1698-    else:
1699-        link_type = IfNode.LinkTypes.and_
1700-        if ' or ' in bitstr:
1701-            raise TemplateSyntaxError, "'if' tags can't mix 'and' and 'or'"
1702-    for boolpair in boolpairs:
1703-        if ' ' in boolpair:
1704-            try:
1705-                not_, boolvar = boolpair.split()
1706-            except ValueError:
1707-                raise TemplateSyntaxError, "'if' statement improperly formatted"
1708-            if not_ != 'not':
1709-                raise TemplateSyntaxError, "Expected 'not' in if statement"
1710-            boolvars.append((True, parser.compile_filter(boolvar)))
1711-        else:
1712-            boolvars.append((False, parser.compile_filter(boolpair)))
1713-    nodelist_true = parser.parse(('else', 'endif'))
1714-    token = parser.next_token()
1715-    if token.contents == 'else':
1716-        nodelist_false = parser.parse(('endif',))
1717-        parser.delete_first_token()
1718-    else:
1719-        nodelist_false = NodeList()
1720-    return IfNode(boolvars, nodelist_true, nodelist_false, link_type)
1721-do_if = register.tag("if", do_if)
1722-
1723-#@register.tag
1724-def ifchanged(parser, token):
1725-    """
1726-    Checks if a value has changed from the last iteration of a loop.
1727-
1728-    The 'ifchanged' block tag is used within a loop. It has two possible uses.
1729-
1730-    1. Checks its own rendered contents against its previous state and only
1731-       displays the content if it has changed. For example, this displays a
1732-       list of days, only displaying the month if it changes::
1733-
1734-            <h1>Archive for {{ year }}</h1>
1735-
1736-            {% for date in days %}
1737-                {% ifchanged %}<h3>{{ date|date:"F" }}</h3>{% endifchanged %}
1738-                <a href="{{ date|date:"M/d"|lower }}/">{{ date|date:"j" }}</a>
1739-            {% endfor %}
1740-
1741-    2. If given a variable, check whether that variable has changed.
1742-       For example, the following shows the date every time it changes, but
1743-       only shows the hour if both the hour and the date have changed::
1744-
1745-            {% for date in days %}
1746-                {% ifchanged date.date %} {{ date.date }} {% endifchanged %}
1747-                {% ifchanged date.hour date.date %}
1748-                    {{ date.hour }}
1749-                {% endifchanged %}
1750-            {% endfor %}
1751-    """
1752-    bits = token.contents.split()
1753-    nodelist = parser.parse(('endifchanged',))
1754-    parser.delete_first_token()
1755-    return IfChangedNode(nodelist, *bits[1:])
1756-ifchanged = register.tag(ifchanged)
1757-
1758-#@register.tag
1759-def ssi(parser, token):
1760-    """
1761-    Outputs the contents of a given file into the page.
1762-
1763-    Like a simple "include" tag, the ``ssi`` tag includes the contents
1764-    of another file -- which must be specified using an absolute path --
1765-    in the current page::
1766-
1767-        {% ssi /home/html/ljworld.com/includes/right_generic.html %}
1768-
1769-    If the optional "parsed" parameter is given, the contents of the included
1770-    file are evaluated as template code, with the current context::
1771-
1772-        {% ssi /home/html/ljworld.com/includes/right_generic.html parsed %}
1773-    """
1774-    bits = token.contents.split()
1775-    parsed = False
1776-    if len(bits) not in (2, 3):
1777-        raise TemplateSyntaxError("'ssi' tag takes one argument: the path to"
1778-                                  " the file to be included")
1779-    if len(bits) == 3:
1780-        if bits[2] == 'parsed':
1781-            parsed = True
1782-        else:
1783-            raise TemplateSyntaxError("Second (optional) argument to %s tag"
1784-                                      " must be 'parsed'" % bits[0])
1785-    return SsiNode(bits[1], parsed)
1786-ssi = register.tag(ssi)
1787-
1788-#@register.tag
1789-def load(parser, token):
1790-    """
1791-    Loads a custom template tag set.
1792-
1793-    For example, to load the template tags in
1794-    ``django/templatetags/news/photos.py``::
1795-
1796-        {% load news.photos %}
1797-    """
1798-    bits = token.contents.split()
1799-    for taglib in bits[1:]:
1800-        # add the library to the parser
1801-        try:
1802-            lib = get_library("django.templatetags.%s" % taglib)
1803-            parser.add_library(lib)
1804-        except InvalidTemplateLibrary, e:
1805-            raise TemplateSyntaxError("'%s' is not a valid tag library: %s" %
1806-                                      (taglib, e))
1807-    return LoadNode()
1808-load = register.tag(load)
1809-
1810-#@register.tag
1811-def now(parser, token):
1812-    """
1813-    Displays the date, formatted according to the given string.
1814-
1815-    Uses the same format as PHP's ``date()`` function; see http://php.net/date
1816-    for all the possible values.
1817-
1818-    Sample usage::
1819-
1820-        It is {% now "jS F Y H:i" %}
1821-    """
1822-    bits = token.contents.split('"')
1823-    if len(bits) != 3:
1824-        raise TemplateSyntaxError, "'now' statement takes one argument"
1825-    format_string = bits[1]
1826-    return NowNode(format_string)
1827-now = register.tag(now)
1828-
1829-#@register.tag
1830-def regroup(parser, token):
1831-    """
1832-    Regroups a list of alike objects by a common attribute.
1833-
1834-    This complex tag is best illustrated by use of an example:  say that
1835-    ``people`` is a list of ``Person`` objects that have ``first_name``,
1836-    ``last_name``, and ``gender`` attributes, and you'd like to display a list
1837-    that looks like:
1838-
1839-        * Male:
1840-            * George Bush
1841-            * Bill Clinton
1842-        * Female:
1843-            * Margaret Thatcher
1844-            * Colendeeza Rice
1845-        * Unknown:
1846-            * Pat Smith
1847-
1848-    The following snippet of template code would accomplish this dubious task::
1849-
1850-        {% regroup people by gender as grouped %}
1851-        <ul>
1852-        {% for group in grouped %}
1853-            <li>{{ group.grouper }}
1854-            <ul>
1855-                {% for item in group.list %}
1856-                <li>{{ item }}</li>
1857-                {% endfor %}
1858-            </ul>
1859-        {% endfor %}
1860-        </ul>
1861-
1862-    As you can see, ``{% regroup %}`` populates a variable with a list of
1863-    objects with ``grouper`` and ``list`` attributes.  ``grouper`` contains the
1864-    item that was grouped by; ``list`` contains the list of objects that share
1865-    that ``grouper``.  In this case, ``grouper`` would be ``Male``, ``Female``
1866-    and ``Unknown``, and ``list`` is the list of people with those genders.
1867-
1868-    Note that `{% regroup %}`` does not work when the list to be grouped is not
1869-    sorted by the key you are grouping by!  This means that if your list of
1870-    people was not sorted by gender, you'd need to make sure it is sorted
1871-    before using it, i.e.::
1872-
1873-        {% regroup people|dictsort:"gender" by gender as grouped %}
1874-
1875-    """
1876-    firstbits = token.contents.split(None, 3)
1877-    if len(firstbits) != 4:
1878-        raise TemplateSyntaxError, "'regroup' tag takes five arguments"
1879-    target = parser.compile_filter(firstbits[1])
1880-    if firstbits[2] != 'by':
1881-        raise TemplateSyntaxError("second argument to 'regroup' tag must be 'by'")
1882-    lastbits_reversed = firstbits[3][::-1].split(None, 2)
1883-    if lastbits_reversed[1][::-1] != 'as':
1884-        raise TemplateSyntaxError("next-to-last argument to 'regroup' tag must"
1885-                                  " be 'as'")
1886-
1887-    expression = parser.compile_filter(lastbits_reversed[2][::-1])
1888-
1889-    var_name = lastbits_reversed[0][::-1]
1890-    return RegroupNode(target, expression, var_name)
1891-regroup = register.tag(regroup)
1892-
1893-def spaceless(parser, token):
1894-    """
1895-    Removes whitespace between HTML tags, including tab and newline characters.
1896-
1897-    Example usage::
1898-
1899-        {% spaceless %}
1900-            <p>
1901-                <a href="foo/">Foo</a>
1902-            </p>
1903-        {% endspaceless %}
1904-
1905-    This example would return this HTML::
1906-
1907-        <p><a href="foo/">Foo</a></p>
1908-
1909-    Only space between *tags* is normalized -- not space between tags and text.
1910-    In this example, the space around ``Hello`` won't be stripped::
1911-
1912-        {% spaceless %}
1913-            <strong>
1914-                Hello
1915-            </strong>
1916-        {% endspaceless %}
1917-    """
1918-    nodelist = parser.parse(('endspaceless',))
1919-    parser.delete_first_token()
1920-    return SpacelessNode(nodelist)
1921-spaceless = register.tag(spaceless)
1922-
1923-#@register.tag
1924-def templatetag(parser, token):
1925-    """
1926-    Outputs one of the bits used to compose template tags.
1927-
1928-    Since the template system has no concept of "escaping", to display one of
1929-    the bits used in template tags, you must use the ``{% templatetag %}`` tag.
1930-
1931-    The argument tells which template bit to output:
1932-
1933-        ==================  =======
1934-        Argument            Outputs
1935-        ==================  =======
1936-        ``openblock``       ``{%``
1937-        ``closeblock``      ``%}``
1938-        ``openvariable``    ``{{``
1939-        ``closevariable``   ``}}``
1940-        ``openbrace``       ``{``
1941-        ``closebrace``      ``}``
1942-        ``opencomment``     ``{#``
1943-        ``closecomment``    ``#}``
1944-        ==================  =======
1945-    """
1946-    bits = token.contents.split()
1947-    if len(bits) != 2:
1948-        raise TemplateSyntaxError, "'templatetag' statement takes one argument"
1949-    tag = bits[1]
1950-    if tag not in TemplateTagNode.mapping:
1951-        raise TemplateSyntaxError("Invalid templatetag argument: '%s'."
1952-                                  " Must be one of: %s" %
1953-                                  (tag, TemplateTagNode.mapping.keys()))
1954-    return TemplateTagNode(tag)
1955-templatetag = register.tag(templatetag)
1956-
1957-def url(parser, token):
1958-    """
1959-    Returns an absolute URL matching given view with its parameters.
1960-
1961-    This is a way to define links that aren't tied to a particular URL
1962-    configuration::
1963-
1964-        {% url path.to.some_view arg1,arg2,name1=value1 %}
1965-
1966-    The first argument is a path to a view. It can be an absolute python path
1967-    or just ``app_name.view_name`` without the project name if the view is
1968-    located inside the project.  Other arguments are comma-separated values
1969-    that will be filled in place of positional and keyword arguments in the
1970-    URL. All arguments for the URL should be present.
1971-
1972-    For example if you have a view ``app_name.client`` taking client's id and
1973-    the corresponding line in a URLconf looks like this::
1974-
1975-        ('^client/(\d+)/$', 'app_name.client')
1976-
1977-    and this app's URLconf is included into the project's URLconf under some
1978-    path::
1979-
1980-        ('^clients/', include('project_name.app_name.urls'))
1981-
1982-    then in a template you can create a link for a certain client like this::
1983-
1984-        {% url app_name.client client.id %}
1985-
1986-    The URL will look like ``/clients/client/123/``.
1987-    """
1988-    bits = token.contents.split(' ', 2)
1989-    if len(bits) < 2:
1990-        raise TemplateSyntaxError("'%s' takes at least one argument"
1991-                                  " (path to a view)" % bits[0])
1992-    args = []
1993-    kwargs = {}
1994-    if len(bits) > 2:
1995-        for arg in bits[2].split(','):
1996-            if '=' in arg:
1997-                k, v = arg.split('=', 1)
1998-                k = k.strip()
1999-                kwargs[k] = parser.compile_filter(v)
2000-            else:
2001-                args.append(parser.compile_filter(arg))
2002-    return URLNode(bits[1], args, kwargs)
2003-url = register.tag(url)
2004-
2005-#@register.tag
2006-def widthratio(parser, token):
2007-    """
2008-    For creating bar charts and such, this tag calculates the ratio of a given
2009-    value to a maximum value, and then applies that ratio to a constant.
2010-
2011-    For example::
2012-
2013-        <img src='bar.gif' height='10' width='{% widthratio this_value max_value 100 %}' />
2014-
2015-    Above, if ``this_value`` is 175 and ``max_value`` is 200, the the image in
2016-    the above example will be 88 pixels wide (because 175/200 = .875;
2017-    .875 * 100 = 87.5 which is rounded up to 88).
2018-    """
2019-    bits = token.contents.split()
2020-    if len(bits) != 4:
2021-        raise TemplateSyntaxError("widthratio takes three arguments")
2022-    tag, this_value_expr, max_value_expr, max_width = bits
2023-    try:
2024-        max_width = int(max_width)
2025-    except ValueError:
2026-        raise TemplateSyntaxError("widthratio final argument must be an integer")
2027-    return WidthRatioNode(parser.compile_filter(this_value_expr),
2028-                          parser.compile_filter(max_value_expr), max_width)
2029-widthratio = register.tag(widthratio)
2030-
2031-#@register.tag
2032-def do_with(parser, token):
2033-    """
2034-    Adds a value to the context (inside of this block) for caching and easy
2035-    access.
2036-
2037-    For example::
2038-
2039-        {% with person.some_sql_method as total %}
2040-            {{ total }} object{{ total|pluralize }}
2041-        {% endwith %}
2042-    """
2043-    bits = list(token.split_contents())
2044-    if len(bits) != 4 or bits[2] != "as":
2045-        raise TemplateSyntaxError("%r expected format is 'value as name'" %
2046-                                  bits[0])
2047-    var = parser.compile_filter(bits[1])
2048-    name = bits[3]
2049-    nodelist = parser.parse(('endwith',))
2050-    parser.delete_first_token()
2051-    return WithNode(var, name, nodelist)
2052-do_with = register.tag('with', do_with)
2053diff --git a/django/template/loader.py b/django/template/loader.py
2054index 1d7d945..8f0d55a 100644
2055--- a/django/template/loader.py
2056+++ b/django/template/loader.py
2057@@ -116,4 +116,4 @@ def select_template(template_name_list):
2058     # If we get here, none of the templates could be loaded
2059     raise TemplateDoesNotExist, ', '.join(template_name_list)
2060 
2061-add_to_builtins('django.template.loader_tags')
2062+add_to_builtins('django.template', 'loader_tags')
2063diff --git a/django/templatetags/__init__.py b/django/templatetags/__init__.py
2064index 9204535..37b3dc4 100644
2065--- a/django/templatetags/__init__.py
2066+++ b/django/templatetags/__init__.py
2067@@ -1,7 +1,19 @@
2068+import imp
2069 from django.conf import settings
2070 
2071-for a in settings.INSTALLED_APPS:
2072-    try:
2073-        __path__.extend(__import__(a + '.templatetags', {}, {}, ['']).__path__)
2074-    except ImportError:
2075-        pass
2076+templatetags_modules= []
2077+
2078+def get_templatetags_modules():
2079+    if not templatetags_modules:
2080+        """ Populate list once per thread. """
2081+        for app_module in ['django'] + list(settings.INSTALLED_APPS):
2082+            try:
2083+                components = app_module.split('.')
2084+                mod = __import__(app_module, {}, {}, components[-1])
2085+                imp.find_module('templatetags', mod.__path__)
2086+                templatetag_module = '%s.templatetags' % app_module
2087+                templatetags_modules.append(templatetag_module)
2088+            except ImportError:
2089+                continue
2090+            __import__(templatetag_module)
2091+    return templatetags_modules
2092diff --git a/django/templatetags/defaultfilters.py b/django/templatetags/defaultfilters.py
2093new file mode 100644
2094index 0000000..cef3143
2095--- /dev/null
2096+++ b/django/templatetags/defaultfilters.py
2097@@ -0,0 +1,851 @@
2098+"""Default variable filters."""
2099+
2100+import re
2101+import random as random_module
2102+try:
2103+    from functools import wraps
2104+except ImportError:
2105+    from django.utils.functional import wraps  # Python 2.3, 2.4 fallback.
2106+
2107+from django.template import Variable, Library
2108+from django.conf import settings
2109+from django.utils.translation import ugettext, ungettext
2110+from django.utils.encoding import force_unicode, iri_to_uri
2111+from django.utils.safestring import mark_safe, SafeData
2112+
2113+register = Library()
2114+
2115+#######################
2116+# STRING DECORATOR    #
2117+#######################
2118+
2119+def stringfilter(func):
2120+    """
2121+    Decorator for filters which should only receive unicode objects. The object
2122+    passed as the first positional argument will be converted to a unicode
2123+    object.
2124+    """
2125+    def _dec(*args, **kwargs):
2126+        if args:
2127+            args = list(args)
2128+            args[0] = force_unicode(args[0])
2129+            if isinstance(args[0], SafeData) and getattr(func, 'is_safe', False):
2130+                return mark_safe(func(*args, **kwargs))
2131+        return func(*args, **kwargs)
2132+
2133+    # Include a reference to the real function (used to check original
2134+    # arguments by the template parser).
2135+    _dec._decorated_function = getattr(func, '_decorated_function', func)
2136+    for attr in ('is_safe', 'needs_autoescape'):
2137+        if hasattr(func, attr):
2138+            setattr(_dec, attr, getattr(func, attr))
2139+    return wraps(func)(_dec)
2140+
2141+###################
2142+# STRINGS         #
2143+###################
2144+
2145+
2146+def addslashes(value):
2147+    """
2148+    Adds slashes before quotes. Useful for escaping strings in CSV, for
2149+    example. Less useful for escaping JavaScript; use the ``escapejs``
2150+    filter instead.
2151+    """
2152+    return value.replace('\\', '\\\\').replace('"', '\\"').replace("'", "\\'")
2153+addslashes.is_safe = True
2154+addslashes = stringfilter(addslashes)
2155+
2156+def capfirst(value):
2157+    """Capitalizes the first character of the value."""
2158+    return value and value[0].upper() + value[1:]
2159+capfirst.is_safe=True
2160+capfirst = stringfilter(capfirst)
2161+
2162+_js_escapes = (
2163+    ('\\', '\\\\'),
2164+    ('"', '\\"'),
2165+    ("'", "\\'"),
2166+    ('\n', '\\n'),
2167+    ('\r', '\\r'),
2168+    ('\b', '\\b'),
2169+    ('\f', '\\f'),
2170+    ('\t', '\\t'),
2171+    ('\v', '\\v'),
2172+    ('</', '<\\/'),
2173+)
2174+def escapejs(value):
2175+    """Backslash-escapes characters for use in JavaScript strings."""
2176+    for bad, good in _js_escapes:
2177+        value = value.replace(bad, good)
2178+    return value
2179+escapejs = stringfilter(escapejs)
2180+
2181+def fix_ampersands(value):
2182+    """Replaces ampersands with ``&amp;`` entities."""
2183+    from django.utils.html import fix_ampersands
2184+    return fix_ampersands(value)
2185+fix_ampersands.is_safe=True
2186+fix_ampersands = stringfilter(fix_ampersands)
2187+
2188+def floatformat(text, arg=-1):
2189+    """
2190+    Displays a float to a specified number of decimal places.
2191+
2192+    If called without an argument, it displays the floating point number with
2193+    one decimal place -- but only if there's a decimal place to be displayed:
2194+
2195+    * num1 = 34.23234
2196+    * num2 = 34.00000
2197+    * num3 = 34.26000
2198+    * {{ num1|floatformat }} displays "34.2"
2199+    * {{ num2|floatformat }} displays "34"
2200+    * {{ num3|floatformat }} displays "34.3"
2201+
2202+    If arg is positive, it will always display exactly arg number of decimal
2203+    places:
2204+
2205+    * {{ num1|floatformat:3 }} displays "34.232"
2206+    * {{ num2|floatformat:3 }} displays "34.000"
2207+    * {{ num3|floatformat:3 }} displays "34.260"
2208+
2209+    If arg is negative, it will display arg number of decimal places -- but
2210+    only if there are places to be displayed:
2211+
2212+    * {{ num1|floatformat:"-3" }} displays "34.232"
2213+    * {{ num2|floatformat:"-3" }} displays "34"
2214+    * {{ num3|floatformat:"-3" }} displays "34.260"
2215+    """
2216+    try:
2217+        f = float(text)
2218+    except (ValueError, TypeError):
2219+        return u''
2220+    try:
2221+        d = int(arg)
2222+    except ValueError:
2223+        return force_unicode(f)
2224+    try:
2225+        m = f - int(f)
2226+    except OverflowError:
2227+        return force_unicode(f)
2228+    if not m and d < 0:
2229+        return mark_safe(u'%d' % int(f))
2230+    else:
2231+        formatstr = u'%%.%df' % abs(d)
2232+        return mark_safe(formatstr % f)
2233+floatformat.is_safe = True
2234+
2235+def iriencode(value):
2236+    """Escapes an IRI value for use in a URL."""
2237+    return force_unicode(iri_to_uri(value))
2238+iriencode.is_safe = True
2239+iriencode = stringfilter(iriencode)
2240+
2241+def linenumbers(value, autoescape=None):
2242+    """Displays text with line numbers."""
2243+    from django.utils.html import escape
2244+    lines = value.split(u'\n')
2245+    # Find the maximum width of the line count, for use with zero padding
2246+    # string format command
2247+    width = unicode(len(unicode(len(lines))))
2248+    if not autoescape or isinstance(value, SafeData):
2249+        for i, line in enumerate(lines):
2250+            lines[i] = (u"%0" + width  + u"d. %s") % (i + 1, line)
2251+    else:
2252+        for i, line in enumerate(lines):
2253+            lines[i] = (u"%0" + width  + u"d. %s") % (i + 1, escape(line))
2254+    return mark_safe(u'\n'.join(lines))
2255+linenumbers.is_safe = True
2256+linenumbers.needs_autoescape = True
2257+linenumbers = stringfilter(linenumbers)
2258+
2259+def lower(value):
2260+    """Converts a string into all lowercase."""
2261+    return value.lower()
2262+lower.is_safe = True
2263+lower = stringfilter(lower)
2264+
2265+def make_list(value):
2266+    """
2267+    Returns the value turned into a list.
2268+
2269+    For an integer, it's a list of digits.
2270+    For a string, it's a list of characters.
2271+    """
2272+    return list(value)
2273+make_list.is_safe = False
2274+make_list = stringfilter(make_list)
2275+
2276+def slugify(value):
2277+    """
2278+    Normalizes string, converts to lowercase, removes non-alpha characters,
2279+    and converts spaces to hyphens.
2280+    """
2281+    import unicodedata
2282+    value = unicodedata.normalize('NFKD', value).encode('ascii', 'ignore')
2283+    value = unicode(re.sub('[^\w\s-]', '', value).strip().lower())
2284+    return mark_safe(re.sub('[-\s]+', '-', value))
2285+slugify.is_safe = True
2286+slugify = stringfilter(slugify)
2287+
2288+def stringformat(value, arg):
2289+    """
2290+    Formats the variable according to the arg, a string formatting specifier.
2291+
2292+    This specifier uses Python string formating syntax, with the exception that
2293+    the leading "%" is dropped.
2294+
2295+    See http://docs.python.org/lib/typesseq-strings.html for documentation
2296+    of Python string formatting
2297+    """
2298+    try:
2299+        return (u"%" + unicode(arg)) % value
2300+    except (ValueError, TypeError):
2301+        return u""
2302+stringformat.is_safe = True
2303+
2304+def title(value):
2305+    """Converts a string into titlecase."""
2306+    return re.sub("([a-z])'([A-Z])", lambda m: m.group(0).lower(), value.title())
2307+title.is_safe = True
2308+title = stringfilter(title)
2309+
2310+def truncatewords(value, arg):
2311+    """
2312+    Truncates a string after a certain number of words.
2313+
2314+    Argument: Number of words to truncate after.
2315+    """
2316+    from django.utils.text import truncate_words
2317+    try:
2318+        length = int(arg)
2319+    except ValueError: # Invalid literal for int().
2320+        return value # Fail silently.
2321+    return truncate_words(value, length)
2322+truncatewords.is_safe = True
2323+truncatewords = stringfilter(truncatewords)
2324+
2325+def truncatewords_html(value, arg):
2326+    """
2327+    Truncates HTML after a certain number of words.
2328+
2329+    Argument: Number of words to truncate after.
2330+    """
2331+    from django.utils.text import truncate_html_words
2332+    try:
2333+        length = int(arg)
2334+    except ValueError: # invalid literal for int()
2335+        return value # Fail silently.
2336+    return truncate_html_words(value, length)
2337+truncatewords_html.is_safe = True
2338+truncatewords_html = stringfilter(truncatewords_html)
2339+
2340+def upper(value):
2341+    """Converts a string into all uppercase."""
2342+    return value.upper()
2343+upper.is_safe = False
2344+upper = stringfilter(upper)
2345+
2346+def urlencode(value):
2347+    """Escapes a value for use in a URL."""
2348+    from django.utils.http import urlquote
2349+    return urlquote(value)
2350+urlencode.is_safe = False
2351+urlencode = stringfilter(urlencode)
2352+
2353+def urlize(value, autoescape=None):
2354+    """Converts URLs in plain text into clickable links."""
2355+    from django.utils.html import urlize
2356+    return mark_safe(urlize(value, nofollow=True, autoescape=autoescape))
2357+urlize.is_safe=True
2358+urlize.needs_autoescape = True
2359+urlize = stringfilter(urlize)
2360+
2361+def urlizetrunc(value, limit, autoescape=None):
2362+    """
2363+    Converts URLs into clickable links, truncating URLs to the given character
2364+    limit, and adding 'rel=nofollow' attribute to discourage spamming.
2365+
2366+    Argument: Length to truncate URLs to.
2367+    """
2368+    from django.utils.html import urlize
2369+    return mark_safe(urlize(value, trim_url_limit=int(limit), nofollow=True,
2370+                            autoescape=autoescape))
2371+urlizetrunc.is_safe = True
2372+urlizetrunc.needs_autoescape = True
2373+urlizetrunc = stringfilter(urlizetrunc)
2374+
2375+def wordcount(value):
2376+    """Returns the number of words."""
2377+    return len(value.split())
2378+wordcount.is_safe = False
2379+wordcount = stringfilter(wordcount)
2380+
2381+def wordwrap(value, arg):
2382+    """
2383+    Wraps words at specified line length.
2384+
2385+    Argument: number of characters to wrap the text at.
2386+    """
2387+    from django.utils.text import wrap
2388+    return wrap(value, int(arg))
2389+wordwrap.is_safe = True
2390+wordwrap = stringfilter(wordwrap)
2391+
2392+def ljust(value, arg):
2393+    """
2394+    Left-aligns the value in a field of a given width.
2395+
2396+    Argument: field size.
2397+    """
2398+    return value.ljust(int(arg))
2399+ljust.is_safe = True
2400+ljust = stringfilter(ljust)
2401+
2402+def rjust(value, arg):
2403+    """
2404+    Right-aligns the value in a field of a given width.
2405+
2406+    Argument: field size.
2407+    """
2408+    return value.rjust(int(arg))
2409+rjust.is_safe = True
2410+rjust = stringfilter(rjust)
2411+
2412+def center(value, arg):
2413+    """Centers the value in a field of a given width."""
2414+    return value.center(int(arg))
2415+center.is_safe = True
2416+center = stringfilter(center)
2417+
2418+def cut(value, arg):
2419+    """
2420+    Removes all values of arg from the given string.
2421+    """
2422+    safe = isinstance(value, SafeData)
2423+    value = value.replace(arg, u'')
2424+    if safe and arg != ';':
2425+        return mark_safe(value)
2426+    return value
2427+cut = stringfilter(cut)
2428+
2429+###################
2430+# HTML STRINGS    #
2431+###################
2432+
2433+def escape(value):
2434+    """
2435+    Marks the value as a string that should not be auto-escaped.
2436+    """
2437+    from django.utils.safestring import mark_for_escaping
2438+    return mark_for_escaping(value)
2439+escape.is_safe = True
2440+escape = stringfilter(escape)
2441+
2442+def force_escape(value):
2443+    """
2444+    Escapes a string's HTML. This returns a new string containing the escaped
2445+    characters (as opposed to "escape", which marks the content for later
2446+    possible escaping).
2447+    """
2448+    from django.utils.html import escape
2449+    return mark_safe(escape(value))
2450+force_escape = stringfilter(force_escape)
2451+force_escape.is_safe = True
2452+
2453+def linebreaks(value, autoescape=None):
2454+    """
2455+    Replaces line breaks in plain text with appropriate HTML; a single
2456+    newline becomes an HTML line break (``<br />``) and a new line
2457+    followed by a blank line becomes a paragraph break (``</p>``).
2458+    """
2459+    from django.utils.html import linebreaks
2460+    autoescape = autoescape and not isinstance(value, SafeData)
2461+    return mark_safe(linebreaks(value, autoescape))
2462+linebreaks.is_safe = True
2463+linebreaks.needs_autoescape = True
2464+linebreaks = stringfilter(linebreaks)
2465+
2466+def linebreaksbr(value, autoescape=None):
2467+    """
2468+    Converts all newlines in a piece of plain text to HTML line breaks
2469+    (``<br />``).
2470+    """
2471+    if autoescape and not isinstance(value, SafeData):
2472+        from django.utils.html import escape
2473+        value = escape(value)
2474+    return mark_safe(value.replace('\n', '<br />'))
2475+linebreaksbr.is_safe = True
2476+linebreaksbr.needs_autoescape = True
2477+linebreaksbr = stringfilter(linebreaksbr)
2478+
2479+def safe(value):
2480+    """
2481+    Marks the value as a string that should not be auto-escaped.
2482+    """
2483+    from django.utils.safestring import mark_safe
2484+    return mark_safe(value)
2485+safe.is_safe = True
2486+safe = stringfilter(safe)
2487+
2488+def removetags(value, tags):
2489+    """Removes a space separated list of [X]HTML tags from the output."""
2490+    tags = [re.escape(tag) for tag in tags.split()]
2491+    tags_re = u'(%s)' % u'|'.join(tags)
2492+    starttag_re = re.compile(ur'<%s(/?>|(\s+[^>]*>))' % tags_re, re.U)
2493+    endtag_re = re.compile(u'</%s>' % tags_re)
2494+    value = starttag_re.sub(u'', value)
2495+    value = endtag_re.sub(u'', value)
2496+    return value
2497+removetags.is_safe = True
2498+removetags = stringfilter(removetags)
2499+
2500+def striptags(value):
2501+    """Strips all [X]HTML tags."""
2502+    from django.utils.html import strip_tags
2503+    return strip_tags(value)
2504+striptags.is_safe = True
2505+striptags = stringfilter(striptags)
2506+
2507+###################
2508+# LISTS           #
2509+###################
2510+
2511+def dictsort(value, arg):
2512+    """
2513+    Takes a list of dicts, returns that list sorted by the property given in
2514+    the argument.
2515+    """
2516+    var_resolve = Variable(arg).resolve
2517+    decorated = [(var_resolve(item), item) for item in value]
2518+    decorated.sort()
2519+    return [item[1] for item in decorated]
2520+dictsort.is_safe = False
2521+
2522+def dictsortreversed(value, arg):
2523+    """
2524+    Takes a list of dicts, returns that list sorted in reverse order by the
2525+    property given in the argument.
2526+    """
2527+    var_resolve = Variable(arg).resolve
2528+    decorated = [(var_resolve(item), item) for item in value]
2529+    decorated.sort()
2530+    decorated.reverse()
2531+    return [item[1] for item in decorated]
2532+dictsortreversed.is_safe = False
2533+
2534+def first(value):
2535+    """Returns the first item in a list."""
2536+    try:
2537+        return value[0]
2538+    except IndexError:
2539+        return u''
2540+first.is_safe = False
2541+
2542+def join(value, arg):
2543+    """Joins a list with a string, like Python's ``str.join(list)``."""
2544+    try:
2545+        data = arg.join(map(force_unicode, value))
2546+    except AttributeError: # fail silently but nicely
2547+        return value
2548+    safe_args = reduce(lambda lhs, rhs: lhs and isinstance(rhs, SafeData),
2549+            value, True)
2550+    if safe_args:
2551+        return mark_safe(data)
2552+    else:
2553+        return data
2554+join.is_safe = True
2555+
2556+def last(value):
2557+    "Returns the last item in a list"
2558+    try:
2559+        return value[-1]
2560+    except IndexError:
2561+        return u''
2562+last.is_safe = True
2563+
2564+def length(value):
2565+    """Returns the length of the value - useful for lists."""
2566+    return len(value)
2567+length.is_safe = True
2568+
2569+def length_is(value, arg):
2570+    """Returns a boolean of whether the value's length is the argument."""
2571+    return len(value) == int(arg)
2572+length_is.is_safe = True
2573+
2574+def random(value):
2575+    """Returns a random item from the list."""
2576+    return random_module.choice(value)
2577+random.is_safe = True
2578+
2579+def slice_(value, arg):
2580+    """
2581+    Returns a slice of the list.
2582+
2583+    Uses the same syntax as Python's list slicing; see
2584+    http://diveintopython.org/native_data_types/lists.html#odbchelper.list.slice
2585+    for an introduction.
2586+    """
2587+    try:
2588+        bits = []
2589+        for x in arg.split(u':'):
2590+            if len(x) == 0:
2591+                bits.append(None)
2592+            else:
2593+                bits.append(int(x))
2594+        return value[slice(*bits)]
2595+
2596+    except (ValueError, TypeError):
2597+        return value # Fail silently.
2598+slice_.is_safe = True
2599+
2600+def unordered_list(value, autoescape=None):
2601+    """
2602+    Recursively takes a self-nested list and returns an HTML unordered list --
2603+    WITHOUT opening and closing <ul> tags.
2604+
2605+    The list is assumed to be in the proper format. For example, if ``var``
2606+    contains: ``['States', ['Kansas', ['Lawrence', 'Topeka'], 'Illinois']]``,
2607+    then ``{{ var|unordered_list }}`` would return::
2608+
2609+        <li>States
2610+        <ul>
2611+                <li>Kansas
2612+                <ul>
2613+                        <li>Lawrence</li>
2614+                        <li>Topeka</li>
2615+                </ul>
2616+                </li>
2617+                <li>Illinois</li>
2618+        </ul>
2619+        </li>
2620+    """
2621+    if autoescape:
2622+        from django.utils.html import conditional_escape
2623+        escaper = conditional_escape
2624+    else:
2625+        escaper = lambda x: x
2626+    def convert_old_style_list(list_):
2627+        """
2628+        Converts old style lists to the new easier to understand format.
2629+
2630+        The old list format looked like:
2631+            ['Item 1', [['Item 1.1', []], ['Item 1.2', []]]
2632+
2633+        And it is converted to:
2634+            ['Item 1', ['Item 1.1', 'Item 1.2]]
2635+        """
2636+        if not isinstance(list_, (tuple, list)) or len(list_) != 2:
2637+            return list_, False
2638+        first_item, second_item = list_
2639+        if second_item == []:
2640+            return [first_item], True
2641+        old_style_list = True
2642+        new_second_item = []
2643+        for sublist in second_item:
2644+            item, old_style_list = convert_old_style_list(sublist)
2645+            if not old_style_list:
2646+                break
2647+            new_second_item.extend(item)
2648+        if old_style_list:
2649+            second_item = new_second_item
2650+        return [first_item, second_item], old_style_list
2651+    def _helper(list_, tabs=1):
2652+        indent = u'\t' * tabs
2653+        output = []
2654+
2655+        list_length = len(list_)
2656+        i = 0
2657+        while i < list_length:
2658+            title = list_[i]
2659+            sublist = ''
2660+            sublist_item = None
2661+            if isinstance(title, (list, tuple)):
2662+                sublist_item = title
2663+                title = ''
2664+            elif i < list_length - 1:
2665+                next_item = list_[i+1]
2666+                if next_item and isinstance(next_item, (list, tuple)):
2667+                    # The next item is a sub-list.
2668+                    sublist_item = next_item
2669+                    # We've processed the next item now too.
2670+                    i += 1
2671+            if sublist_item:
2672+                sublist = _helper(sublist_item, tabs+1)
2673+                sublist = '\n%s<ul>\n%s\n%s</ul>\n%s' % (indent, sublist,
2674+                                                         indent, indent)
2675+            output.append('%s<li>%s%s</li>' % (indent,
2676+                    escaper(force_unicode(title)), sublist))
2677+            i += 1
2678+        return '\n'.join(output)
2679+    value, converted = convert_old_style_list(value)
2680+    return mark_safe(_helper(value))
2681+unordered_list.is_safe = True
2682+unordered_list.needs_autoescape = True
2683+
2684+###################
2685+# INTEGERS        #
2686+###################
2687+
2688+def add(value, arg):
2689+    """Adds the arg to the value."""
2690+    return int(value) + int(arg)
2691+add.is_safe = False
2692+
2693+def get_digit(value, arg):
2694+    """
2695+    Given a whole number, returns the requested digit of it, where 1 is the
2696+    right-most digit, 2 is the second-right-most digit, etc. Returns the
2697+    original value for invalid input (if input or argument is not an integer,
2698+    or if argument is less than 1). Otherwise, output is always an integer.
2699+    """
2700+    try:
2701+        arg = int(arg)
2702+        value = int(value)
2703+    except ValueError:
2704+        return value # Fail silently for an invalid argument
2705+    if arg < 1:
2706+        return value
2707+    try:
2708+        return int(str(value)[-arg])
2709+    except IndexError:
2710+        return 0
2711+get_digit.is_safe = False
2712+
2713+###################
2714+# DATES           #
2715+###################
2716+
2717+def date(value, arg=None):
2718+    """Formats a date according to the given format."""
2719+    from django.utils.dateformat import format
2720+    if not value:
2721+        return u''
2722+    if arg is None:
2723+        arg = settings.DATE_FORMAT
2724+    return format(value, arg)
2725+date.is_safe = False
2726+
2727+def time(value, arg=None):
2728+    """Formats a time according to the given format."""
2729+    from django.utils.dateformat import time_format
2730+    if value in (None, u''):
2731+        return u''
2732+    if arg is None:
2733+        arg = settings.TIME_FORMAT
2734+    return time_format(value, arg)
2735+time.is_safe = False
2736+
2737+def timesince(value, arg=None):
2738+    """Formats a date as the time since that date (i.e. "4 days, 6 hours")."""
2739+    from django.utils.timesince import timesince
2740+    if not value:
2741+        return u''
2742+    if arg:
2743+        return timesince(arg, value)
2744+    return timesince(value)
2745+timesince.is_safe = False
2746+
2747+def timeuntil(value, arg=None):
2748+    """Formats a date as the time until that date (i.e. "4 days, 6 hours")."""
2749+    from django.utils.timesince import timesince
2750+    from datetime import datetime
2751+    if not value:
2752+        return u''
2753+    if arg:
2754+        return timesince(arg, value)
2755+    return timesince(datetime.now(), value)
2756+timeuntil.is_safe = False
2757+
2758+###################
2759+# LOGIC           #
2760+###################
2761+
2762+def default(value, arg):
2763+    """If value is unavailable, use given default."""
2764+    return value or arg
2765+default.is_safe = False
2766+
2767+def default_if_none(value, arg):
2768+    """If value is None, use given default."""
2769+    if value is None:
2770+        return arg
2771+    return value
2772+default_if_none.is_safe = False
2773+
2774+def divisibleby(value, arg):
2775+    """Returns True if the value is devisible by the argument."""
2776+    return int(value) % int(arg) == 0
2777+divisibleby.is_safe = False
2778+
2779+def yesno(value, arg=None):
2780+    """
2781+    Given a string mapping values for true, false and (optionally) None,
2782+    returns one of those strings accoding to the value:
2783+
2784+    ==========  ======================  ==================================
2785+    Value       Argument                Outputs
2786+    ==========  ======================  ==================================
2787+    ``True``    ``"yeah,no,maybe"``     ``yeah``
2788+    ``False``   ``"yeah,no,maybe"``     ``no``
2789+    ``None``    ``"yeah,no,maybe"``     ``maybe``
2790+    ``None``    ``"yeah,no"``           ``"no"`` (converts None to False
2791+                                        if no mapping for None is given.
2792+    ==========  ======================  ==================================
2793+    """
2794+    if arg is None:
2795+        arg = ugettext('yes,no,maybe')
2796+    bits = arg.split(u',')
2797+    if len(bits) < 2:
2798+        return value # Invalid arg.
2799+    try:
2800+        yes, no, maybe = bits
2801+    except ValueError:
2802+        # Unpack list of wrong size (no "maybe" value provided).
2803+        yes, no, maybe = bits[0], bits[1], bits[1]
2804+    if value is None:
2805+        return maybe
2806+    if value:
2807+        return yes
2808+    return no
2809+yesno.is_safe = False
2810+
2811+###################
2812+# MISC            #
2813+###################
2814+
2815+def filesizeformat(bytes):
2816+    """
2817+    Formats the value like a 'human-readable' file size (i.e. 13 KB, 4.1 MB,
2818+    102 bytes, etc).
2819+    """
2820+    try:
2821+        bytes = float(bytes)
2822+    except TypeError:
2823+        return u"0 bytes"
2824+
2825+    if bytes < 1024:
2826+        return ungettext("%(size)d byte", "%(size)d bytes", bytes) % {'size': bytes}
2827+    if bytes < 1024 * 1024:
2828+        return ugettext("%.1f KB") % (bytes / 1024)
2829+    if bytes < 1024 * 1024 * 1024:
2830+        return ugettext("%.1f MB") % (bytes / (1024 * 1024))
2831+    return ugettext("%.1f GB") % (bytes / (1024 * 1024 * 1024))
2832+filesizeformat.is_safe = True
2833+
2834+def pluralize(value, arg=u's'):
2835+    """
2836+    Returns a plural suffix if the value is not 1. By default, 's' is used as
2837+    the suffix:
2838+
2839+    * If value is 0, vote{{ value|pluralize }} displays "0 votes".
2840+    * If value is 1, vote{{ value|pluralize }} displays "1 vote".
2841+    * If value is 2, vote{{ value|pluralize }} displays "2 votes".
2842+
2843+    If an argument is provided, that string is used instead:
2844+
2845+    * If value is 0, class{{ value|pluralize:"es" }} displays "0 classes".
2846+    * If value is 1, class{{ value|pluralize:"es" }} displays "1 class".
2847+    * If value is 2, class{{ value|pluralize:"es" }} displays "2 classes".
2848+
2849+    If the provided argument contains a comma, the text before the comma is
2850+    used for the singular case and the text after the comma is used for the
2851+    plural case:
2852+
2853+    * If value is 0, cand{{ value|pluralize:"y,ies" }} displays "0 candies".
2854+    * If value is 1, cand{{ value|pluralize:"y,ies" }} displays "1 candy".
2855+    * If value is 2, cand{{ value|pluralize:"y,ies" }} displays "2 candies".
2856+    """
2857+    if not u',' in arg:
2858+        arg = u',' + arg
2859+    bits = arg.split(u',')
2860+    if len(bits) > 2:
2861+        return u''
2862+    singular_suffix, plural_suffix = bits[:2]
2863+
2864+    try:
2865+        if int(value) != 1:
2866+            return plural_suffix
2867+    except ValueError: # Invalid string that's not a number.
2868+        pass
2869+    except TypeError: # Value isn't a string or a number; maybe it's a list?
2870+        try:
2871+            if len(value) != 1:
2872+                return plural_suffix
2873+        except TypeError: # len() of unsized object.
2874+            pass
2875+    return singular_suffix
2876+pluralize.is_safe = False
2877+
2878+def phone2numeric(value):
2879+    """Takes a phone number and converts it in to its numerical equivalent."""
2880+    from django.utils.text import phone2numeric
2881+    return phone2numeric(value)
2882+phone2numeric.is_safe = True
2883+
2884+def pprint(value):
2885+    """A wrapper around pprint.pprint -- for debugging, really."""
2886+    from pprint import pformat
2887+    try:
2888+        return pformat(value)
2889+    except Exception, e:
2890+        return u"Error in formatting: %s" % force_unicode(e, errors="replace")
2891+pprint.is_safe = True
2892+
2893+# Syntax: register.filter(name of filter, callback)
2894+register.filter(add)
2895+register.filter(addslashes)
2896+register.filter(capfirst)
2897+register.filter(center)
2898+register.filter(cut)
2899+register.filter(date)
2900+register.filter(default)
2901+register.filter(default_if_none)
2902+register.filter(dictsort)
2903+register.filter(dictsortreversed)
2904+register.filter(divisibleby)
2905+register.filter(escape)
2906+register.filter(escapejs)
2907+register.filter(filesizeformat)
2908+register.filter(first)
2909+register.filter(fix_ampersands)
2910+register.filter(floatformat)
2911+register.filter(force_escape)
2912+register.filter(get_digit)
2913+register.filter(iriencode)
2914+register.filter(join)
2915+register.filter(last)
2916+register.filter(length)
2917+register.filter(length_is)
2918+register.filter(linebreaks)
2919+register.filter(linebreaksbr)
2920+register.filter(linenumbers)
2921+register.filter(ljust)
2922+register.filter(lower)
2923+register.filter(make_list)
2924+register.filter(phone2numeric)
2925+register.filter(pluralize)
2926+register.filter(pprint)
2927+register.filter(removetags)
2928+register.filter(random)
2929+register.filter(rjust)
2930+register.filter(safe)
2931+register.filter('slice', slice_)
2932+register.filter(slugify)
2933+register.filter(stringformat)
2934+register.filter(striptags)
2935+register.filter(time)
2936+register.filter(timesince)
2937+register.filter(timeuntil)
2938+register.filter(title)
2939+register.filter(truncatewords)
2940+register.filter(truncatewords_html)
2941+register.filter(unordered_list)
2942+register.filter(upper)
2943+register.filter(urlencode)
2944+register.filter(urlize)
2945+register.filter(urlizetrunc)
2946+register.filter(wordcount)
2947+register.filter(wordwrap)
2948+register.filter(yesno)
2949diff --git a/django/templatetags/defaulttags.py b/django/templatetags/defaulttags.py
2950new file mode 100644
2951index 0000000..535457a
2952--- /dev/null
2953+++ b/django/templatetags/defaulttags.py
2954@@ -0,0 +1,1105 @@
2955+"""Default tags used by the template system, available to all templates."""
2956+
2957+import sys
2958+import re
2959+from itertools import cycle as itertools_cycle
2960+try:
2961+    reversed
2962+except NameError:
2963+    from django.utils.itercompat import reversed     # Python 2.3 fallback
2964+
2965+from django.template import Node, NodeList, Template, Context, Variable
2966+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
2967+from django.template import get_library, Library, InvalidTemplateLibrary
2968+from django.conf import settings
2969+from django.utils.encoding import smart_str, smart_unicode
2970+from django.utils.itercompat import groupby
2971+from django.utils.safestring import mark_safe
2972+
2973+register = Library()
2974+
2975+class AutoEscapeControlNode(Node):
2976+    """Implements the actions of the autoescape tag."""
2977+    def __init__(self, setting, nodelist):
2978+        self.setting, self.nodelist = setting, nodelist
2979+
2980+    def render(self, context):
2981+        old_setting = context.autoescape
2982+        context.autoescape = self.setting
2983+        output = self.nodelist.render(context)
2984+        context.autoescape = old_setting
2985+        if self.setting:
2986+            return mark_safe(output)
2987+        else:
2988+            return output
2989+
2990+class CommentNode(Node):
2991+    def render(self, context):
2992+        return ''
2993+
2994+class CycleNode(Node):
2995+    def __init__(self, cyclevars, variable_name=None):
2996+        self.cycle_iter = itertools_cycle([Variable(v) for v in cyclevars])
2997+        self.variable_name = variable_name
2998+
2999+    def render(self, context):
3000+        value = self.cycle_iter.next().resolve(context)
3001+        if self.variable_name:
3002+            context[self.variable_name] = value
3003+        return value
3004+
3005+class DebugNode(Node):
3006+    def render(self, context):
3007+        from pprint import pformat
3008+        output = [pformat(val) for val in context]
3009+        output.append('\n\n')
3010+        output.append(pformat(sys.modules))
3011+        return ''.join(output)
3012+
3013+class FilterNode(Node):
3014+    def __init__(self, filter_expr, nodelist):
3015+        self.filter_expr, self.nodelist = filter_expr, nodelist
3016+
3017+    def render(self, context):
3018+        output = self.nodelist.render(context)
3019+        # Apply filters.
3020+        context.update({'var': output})
3021+        filtered = self.filter_expr.resolve(context)
3022+        context.pop()
3023+        return filtered
3024+
3025+class FirstOfNode(Node):
3026+    def __init__(self, vars):
3027+        self.vars = map(Variable, vars)
3028+
3029+    def render(self, context):
3030+        for var in self.vars:
3031+            try:
3032+                value = var.resolve(context)
3033+            except VariableDoesNotExist:
3034+                continue
3035+            if value:
3036+                return smart_unicode(value)
3037+        return u''
3038+
3039+class ForNode(Node):
3040+    def __init__(self, loopvars, sequence, is_reversed, nodelist_loop):
3041+        self.loopvars, self.sequence = loopvars, sequence
3042+        self.is_reversed = is_reversed
3043+        self.nodelist_loop = nodelist_loop
3044+
3045+    def __repr__(self):
3046+        reversed_text = self.is_reversed and ' reversed' or ''
3047+        return "<For Node: for %s in %s, tail_len: %d%s>" % \
3048+            (', '.join(self.loopvars), self.sequence, len(self.nodelist_loop),
3049+             reversed_text)
3050+
3051+    def __iter__(self):
3052+        for node in self.nodelist_loop:
3053+            yield node
3054+
3055+    def get_nodes_by_type(self, nodetype):
3056+        nodes = []
3057+        if isinstance(self, nodetype):
3058+            nodes.append(self)
3059+        nodes.extend(self.nodelist_loop.get_nodes_by_type(nodetype))
3060+        return nodes
3061+
3062+    def render(self, context):
3063+        nodelist = NodeList()
3064+        if 'forloop' in context:
3065+            parentloop = context['forloop']
3066+        else:
3067+            parentloop = {}
3068+        context.push()
3069+        try:
3070+            values = self.sequence.resolve(context, True)
3071+        except VariableDoesNotExist:
3072+            values = []
3073+        if values is None:
3074+            values = []
3075+        if not hasattr(values, '__len__'):
3076+            values = list(values)
3077+        len_values = len(values)
3078+        if self.is_reversed:
3079+            values = reversed(values)
3080+        unpack = len(self.loopvars) > 1
3081+        # Create a forloop value in the context.  We'll update counters on each
3082+        # iteration just below.
3083+        loop_dict = context['forloop'] = {'parentloop': parentloop}
3084+        for i, item in enumerate(values):
3085+            # Shortcuts for current loop iteration number.
3086+            loop_dict['counter0'] = i
3087+            loop_dict['counter'] = i+1
3088+            # Reverse counter iteration numbers.
3089+            loop_dict['revcounter'] = len_values - i
3090+            loop_dict['revcounter0'] = len_values - i - 1
3091+            # Boolean values designating first and last times through loop.
3092+            loop_dict['first'] = (i == 0)
3093+            loop_dict['last'] = (i == len_values - 1)
3094+
3095+            if unpack:
3096+                # If there are multiple loop variables, unpack the item into
3097+                # them.
3098+                context.update(dict(zip(self.loopvars, item)))
3099+            else:
3100+                context[self.loopvars[0]] = item
3101+            for node in self.nodelist_loop:
3102+                nodelist.append(node.render(context))
3103+            if unpack:
3104+                # The loop variables were pushed on to the context so pop them
3105+                # off again. This is necessary because the tag lets the length
3106+                # of loopvars differ to the length of each set of items and we
3107+                # don't want to leave any vars from the previous loop on the
3108+                # context.
3109+                context.pop()
3110+        context.pop()
3111+        return nodelist.render(context)
3112+
3113+class IfChangedNode(Node):
3114+    def __init__(self, nodelist, *varlist):
3115+        self.nodelist = nodelist
3116+        self._last_seen = None
3117+        self._varlist = map(Variable, varlist)
3118+        self._id = str(id(self))
3119+
3120+    def render(self, context):
3121+        if 'forloop' in context and self._id not in context['forloop']:
3122+            self._last_seen = None
3123+            context['forloop'][self._id] = 1
3124+        try:
3125+            if self._varlist:
3126+                # Consider multiple parameters.  This automatically behaves
3127+                # like an OR evaluation of the multiple variables.
3128+                compare_to = [var.resolve(context) for var in self._varlist]
3129+            else:
3130+                compare_to = self.nodelist.render(context)
3131+        except VariableDoesNotExist:
3132+            compare_to = None
3133+
3134+        if  compare_to != self._last_seen:
3135+            firstloop = (self._last_seen == None)
3136+            self._last_seen = compare_to
3137+            context.push()
3138+            context['ifchanged'] = {'firstloop': firstloop}
3139+            content = self.nodelist.render(context)
3140+            context.pop()
3141+            return content
3142+        else:
3143+            return ''
3144+
3145+class IfEqualNode(Node):
3146+    def __init__(self, var1, var2, nodelist_true, nodelist_false, negate):
3147+        self.var1, self.var2 = Variable(var1), Variable(var2)
3148+        self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false
3149+        self.negate = negate
3150+
3151+    def __repr__(self):
3152+        return "<IfEqualNode>"
3153+
3154+    def render(self, context):
3155+        try:
3156+            val1 = self.var1.resolve(context)
3157+        except VariableDoesNotExist:
3158+            val1 = None
3159+        try:
3160+            val2 = self.var2.resolve(context)
3161+        except VariableDoesNotExist:
3162+            val2 = None
3163+        if (self.negate and val1 != val2) or (not self.negate and val1 == val2):
3164+            return self.nodelist_true.render(context)
3165+        return self.nodelist_false.render(context)
3166+
3167+class IfNode(Node):
3168+    def __init__(self, bool_exprs, nodelist_true, nodelist_false, link_type):
3169+        self.bool_exprs = bool_exprs
3170+        self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false
3171+        self.link_type = link_type
3172+
3173+    def __repr__(self):
3174+        return "<If node>"
3175+
3176+    def __iter__(self):
3177+        for node in self.nodelist_true:
3178+            yield node
3179+        for node in self.nodelist_false:
3180+            yield node
3181+
3182+    def get_nodes_by_type(self, nodetype):
3183+        nodes = []
3184+        if isinstance(self, nodetype):
3185+            nodes.append(self)
3186+        nodes.extend(self.nodelist_true.get_nodes_by_type(nodetype))
3187+        nodes.extend(self.nodelist_false.get_nodes_by_type(nodetype))
3188+        return nodes
3189+
3190+    def render(self, context):
3191+        if self.link_type == IfNode.LinkTypes.or_:
3192+            for ifnot, bool_expr in self.bool_exprs:
3193+                try:
3194+                    value = bool_expr.resolve(context, True)
3195+                except VariableDoesNotExist:
3196+                    value = None
3197+                if (value and not ifnot) or (ifnot and not value):
3198+                    return self.nodelist_true.render(context)
3199+            return self.nodelist_false.render(context)
3200+        else:
3201+            for ifnot, bool_expr in self.bool_exprs:
3202+                try:
3203+                    value = bool_expr.resolve(context, True)
3204+                except VariableDoesNotExist:
3205+                    value = None
3206+                if not ((value and not ifnot) or (ifnot and not value)):
3207+                    return self.nodelist_false.render(context)
3208+            return self.nodelist_true.render(context)
3209+
3210+    class LinkTypes:
3211+        and_ = 0,
3212+        or_ = 1
3213+
3214+class RegroupNode(Node):
3215+    def __init__(self, target, expression, var_name):
3216+        self.target, self.expression = target, expression
3217+        self.var_name = var_name
3218+
3219+    def render(self, context):
3220+        obj_list = self.target.resolve(context, True)
3221+        if obj_list == None:
3222+            # target variable wasn't found in context; fail silently.
3223+            context[self.var_name] = []
3224+            return ''
3225+        # List of dictionaries in the format:
3226+        # {'grouper': 'key', 'list': [list of contents]}.
3227+        context[self.var_name] = [
3228+            {'grouper': key, 'list': list(val)}
3229+            for key, val in
3230+            groupby(obj_list, lambda v, f=self.expression.resolve: f(v, True))
3231+        ]
3232+        return ''
3233+
3234+def include_is_allowed(filepath):
3235+    for root in settings.ALLOWED_INCLUDE_ROOTS:
3236+        if filepath.startswith(root):
3237+            return True
3238+    return False
3239+
3240+class SsiNode(Node):
3241+    def __init__(self, filepath, parsed):
3242+        self.filepath, self.parsed = filepath, parsed
3243+
3244+    def render(self, context):
3245+        if not include_is_allowed(self.filepath):
3246+            if settings.DEBUG:
3247+                return "[Didn't have permission to include file]"
3248+            else:
3249+                return '' # Fail silently for invalid includes.
3250+        try:
3251+            fp = open(self.filepath, 'r')
3252+            output = fp.read()
3253+            fp.close()
3254+        except IOError:
3255+            output = ''
3256+        if self.parsed:
3257+            try:
3258+                t = Template(output, name=self.filepath)
3259+                return t.render(context)
3260+            except TemplateSyntaxError, e:
3261+                if settings.DEBUG:
3262+                    return "[Included template had syntax error: %s]" % e
3263+                else:
3264+                    return '' # Fail silently for invalid included templates.
3265+        return output
3266+
3267+class LoadNode(Node):
3268+    def render(self, context):
3269+        return ''
3270+
3271+class NowNode(Node):
3272+    def __init__(self, format_string):
3273+        self.format_string = format_string
3274+
3275+    def render(self, context):
3276+        from datetime import datetime
3277+        from django.utils.dateformat import DateFormat
3278+        df = DateFormat(datetime.now())
3279+        return df.format(self.format_string)
3280+
3281+class SpacelessNode(Node):
3282+    def __init__(self, nodelist):
3283+        self.nodelist = nodelist
3284+
3285+    def render(self, context):
3286+        from django.utils.html import strip_spaces_between_tags
3287+        return strip_spaces_between_tags(self.nodelist.render(context).strip())
3288+
3289+class TemplateTagNode(Node):
3290+    mapping = {'openblock': BLOCK_TAG_START,
3291+               'closeblock': BLOCK_TAG_END,
3292+               'openvariable': VARIABLE_TAG_START,
3293+               'closevariable': VARIABLE_TAG_END,
3294+               'openbrace': SINGLE_BRACE_START,
3295+               'closebrace': SINGLE_BRACE_END,
3296+               'opencomment': COMMENT_TAG_START,
3297+               'closecomment': COMMENT_TAG_END,
3298+               }
3299+
3300+    def __init__(self, tagtype):
3301+        self.tagtype = tagtype
3302+
3303+    def render(self, context):
3304+        return self.mapping.get(self.tagtype, '')
3305+
3306+class URLNode(Node):
3307+    def __init__(self, view_name, args, kwargs):
3308+        self.view_name = view_name
3309+        self.args = args
3310+        self.kwargs = kwargs
3311+
3312+    def render(self, context):
3313+        from django.core.urlresolvers import reverse, NoReverseMatch
3314+        args = [arg.resolve(context) for arg in self.args]
3315+        kwargs = dict([(smart_str(k,'ascii'), v.resolve(context))
3316+                       for k, v in self.kwargs.items()])
3317+        try:
3318+            return reverse(self.view_name, args=args, kwargs=kwargs)
3319+        except NoReverseMatch:
3320+            try:
3321+                project_name = settings.SETTINGS_MODULE.split('.')[0]
3322+                return reverse(project_name + '.' + self.view_name,
3323+                               args=args, kwargs=kwargs)
3324+            except NoReverseMatch:
3325+                return ''
3326+
3327+class WidthRatioNode(Node):
3328+    def __init__(self, val_expr, max_expr, max_width):
3329+        self.val_expr = val_expr
3330+        self.max_expr = max_expr
3331+        self.max_width = max_width
3332+
3333+    def render(self, context):
3334+        try:
3335+            value = self.val_expr.resolve(context)
3336+            maxvalue = self.max_expr.resolve(context)
3337+        except VariableDoesNotExist:
3338+            return ''
3339+        try:
3340+            value = float(value)
3341+            maxvalue = float(maxvalue)
3342+            ratio = (value / maxvalue) * int(self.max_width)
3343+        except (ValueError, ZeroDivisionError):
3344+            return ''
3345+        return str(int(round(ratio)))
3346+
3347+class WithNode(Node):
3348+    def __init__(self, var, name, nodelist):
3349+        self.var = var
3350+        self.name = name
3351+        self.nodelist = nodelist
3352+
3353+    def __repr__(self):
3354+        return "<WithNode>"
3355+
3356+    def render(self, context):
3357+        val = self.var.resolve(context)
3358+        context.push()
3359+        context[self.name] = val
3360+        output = self.nodelist.render(context)
3361+        context.pop()
3362+        return output
3363+
3364+#@register.tag
3365+def autoescape(parser, token):
3366+    """
3367+    Force autoescape behaviour for this block.
3368+    """
3369+    args = token.contents.split()
3370+    if len(args) != 2:
3371+        raise TemplateSyntaxError("'Autoescape' tag requires exactly one argument.")
3372+    arg = args[1]
3373+    if arg not in (u'on', u'off'):
3374+        raise TemplateSyntaxError("'Autoescape' argument should be 'on' or 'off'")
3375+    nodelist = parser.parse(('endautoescape',))
3376+    parser.delete_first_token()
3377+    return AutoEscapeControlNode((arg == 'on'), nodelist)
3378+autoescape = register.tag(autoescape)
3379+
3380+#@register.tag
3381+def comment(parser, token):
3382+    """
3383+    Ignores everything between ``{% comment %}`` and ``{% endcomment %}``.
3384+    """
3385+    parser.skip_past('endcomment')
3386+    return CommentNode()
3387+comment = register.tag(comment)
3388+
3389+#@register.tag
3390+def cycle(parser, token):
3391+    """
3392+    Cycles among the given strings each time this tag is encountered.
3393+
3394+    Within a loop, cycles among the given strings each time through
3395+    the loop::
3396+
3397+        {% for o in some_list %}
3398+            <tr class="{% cycle 'row1' 'row2' %}">
3399+                ...
3400+            </tr>
3401+        {% endfor %}
3402+
3403+    Outside of a loop, give the values a unique name the first time you call
3404+    it, then use that name each sucessive time through::
3405+
3406+            <tr class="{% cycle 'row1' 'row2' 'row3' as rowcolors %}">...</tr>
3407+            <tr class="{% cycle rowcolors %}">...</tr>
3408+            <tr class="{% cycle rowcolors %}">...</tr>
3409+
3410+    You can use any number of values, separated by spaces. Commas can also
3411+    be used to separate values; if a comma is used, the cycle values are
3412+    interpreted as literal strings.
3413+    """
3414+
3415+    # Note: This returns the exact same node on each {% cycle name %} call;
3416+    # that is, the node object returned from {% cycle a b c as name %} and the
3417+    # one returned from {% cycle name %} are the exact same object. This
3418+    # shouldn't cause problems (heh), but if it does, now you know.
3419+    #
3420+    # Ugly hack warning: This stuffs the named template dict into parser so
3421+    # that names are only unique within each template (as opposed to using
3422+    # a global variable, which would make cycle names have to be unique across
3423+    # *all* templates.
3424+
3425+    args = token.split_contents()
3426+
3427+    if len(args) < 2:
3428+        raise TemplateSyntaxError("'cycle' tag requires at least two arguments")
3429+
3430+    if ',' in args[1]:
3431+        # Backwards compatibility: {% cycle a,b %} or {% cycle a,b as foo %}
3432+        # case.
3433+        args[1:2] = ['"%s"' % arg for arg in args[1].split(",")]
3434+
3435+    if len(args) == 2:
3436+        # {% cycle foo %} case.
3437+        name = args[1]
3438+        if not hasattr(parser, '_namedCycleNodes'):
3439+            raise TemplateSyntaxError("No named cycles in template. '%s' is not defined" % name)
3440+        if not name in parser._namedCycleNodes:
3441+            raise TemplateSyntaxError("Named cycle '%s' does not exist" % name)
3442+        return parser._namedCycleNodes[name]
3443+
3444+    if len(args) > 4 and args[-2] == 'as':
3445+        name = args[-1]
3446+        node = CycleNode(args[1:-2], name)
3447+        if not hasattr(parser, '_namedCycleNodes'):
3448+            parser._namedCycleNodes = {}
3449+        parser._namedCycleNodes[name] = node
3450+    else:
3451+        node = CycleNode(args[1:])
3452+    return node
3453+cycle = register.tag(cycle)
3454+
3455+def debug(parser, token):
3456+    """
3457+    Outputs a whole load of debugging information, including the current
3458+    context and imported modules.
3459+
3460+    Sample usage::
3461+
3462+        <pre>
3463+            {% debug %}
3464+        </pre>
3465+    """
3466+    return DebugNode()
3467+debug = register.tag(debug)
3468+
3469+#@register.tag(name="filter")
3470+def do_filter(parser, token):
3471+    """
3472+    Filters the contents of the block through variable filters.
3473+
3474+    Filters can also be piped through each other, and they can have
3475+    arguments -- just like in variable syntax.
3476+
3477+    Sample usage::
3478+
3479+        {% filter force_escape|lower %}
3480+            This text will be HTML-escaped, and will appear in lowercase.
3481+        {% endfilter %}
3482+    """
3483+    _, rest = token.contents.split(None, 1)
3484+    filter_expr = parser.compile_filter("var|%s" % (rest))
3485+    for func, unused in filter_expr.filters:
3486+        if getattr(func, '_decorated_function', func).__name__ in ('escape', 'safe'):
3487+            raise TemplateSyntaxError('"filter %s" is not permitted.  Use the "autoescape" tag instead.' % func.__name__)
3488+    nodelist = parser.parse(('endfilter',))
3489+    parser.delete_first_token()
3490+    return FilterNode(filter_expr, nodelist)
3491+do_filter = register.tag("filter", do_filter)
3492+
3493+#@register.tag
3494+def firstof(parser, token):
3495+    """
3496+    Outputs the first variable passed that is not False.
3497+
3498+    Outputs nothing if all the passed variables are False.
3499+
3500+    Sample usage::
3501+
3502+        {% firstof var1 var2 var3 %}
3503+
3504+    This is equivalent to::
3505+
3506+        {% if var1 %}
3507+            {{ var1 }}
3508+        {% else %}{% if var2 %}
3509+            {{ var2 }}
3510+        {% else %}{% if var3 %}
3511+            {{ var3 }}
3512+        {% endif %}{% endif %}{% endif %}
3513+
3514+    but obviously much cleaner!
3515+
3516+    You can also use a literal string as a fallback value in case all
3517+    passed variables are False::
3518+
3519+        {% firstof var1 var2 var3 "fallback value" %}
3520+
3521+    """
3522+    bits = token.split_contents()[1:]
3523+    if len(bits) < 1:
3524+        raise TemplateSyntaxError("'firstof' statement requires at least one"
3525+                                  " argument")
3526+    return FirstOfNode(bits)
3527+firstof = register.tag(firstof)
3528+
3529+#@register.tag(name="for")
3530+def do_for(parser, token):
3531+    """
3532+    Loops over each item in an array.
3533+
3534+    For example, to display a list of athletes given ``athlete_list``::
3535+
3536+        <ul>
3537+        {% for athlete in athlete_list %}
3538+            <li>{{ athlete.name }}</li>
3539+        {% endfor %}
3540+        </ul>
3541+
3542+    You can loop over a list in reverse by using
3543+    ``{% for obj in list reversed %}``.
3544+
3545+    You can also unpack multiple values from a two-dimensional array::
3546+
3547+        {% for key,value in dict.items %}
3548+            {{ key }}: {{ value }}
3549+        {% endfor %}
3550+
3551+    The for loop sets a number of variables available within the loop:
3552+
3553+        ==========================  ================================================
3554+        Variable                    Description
3555+        ==========================  ================================================
3556+        ``forloop.counter``         The current iteration of the loop (1-indexed)
3557+        ``forloop.counter0``        The current iteration of the loop (0-indexed)
3558+        ``forloop.revcounter``      The number of iterations from the end of the
3559+                                    loop (1-indexed)
3560+        ``forloop.revcounter0``     The number of iterations from the end of the
3561+                                    loop (0-indexed)
3562+        ``forloop.first``           True if this is the first time through the loop
3563+        ``forloop.last``            True if this is the last time through the loop
3564+        ``forloop.parentloop``      For nested loops, this is the loop "above" the
3565+                                    current one
3566+        ==========================  ================================================
3567+
3568+    """
3569+    bits = token.contents.split()
3570+    if len(bits) < 4:
3571+        raise TemplateSyntaxError("'for' statements should have at least four"
3572+                                  " words: %s" % token.contents)
3573+
3574+    is_reversed = bits[-1] == 'reversed'
3575+    in_index = is_reversed and -3 or -2
3576+    if bits[in_index] != 'in':
3577+        raise TemplateSyntaxError("'for' statements should use the format"
3578+                                  " 'for x in y': %s" % token.contents)
3579+
3580+    loopvars = re.sub(r' *, *', ',', ' '.join(bits[1:in_index])).split(',')
3581+    for var in loopvars:
3582+        if not var or ' ' in var:
3583+            raise TemplateSyntaxError("'for' tag received an invalid argument:"
3584+                                      " %s" % token.contents)
3585+
3586+    sequence = parser.compile_filter(bits[in_index+1])
3587+    nodelist_loop = parser.parse(('endfor',))
3588+    parser.delete_first_token()
3589+    return ForNode(loopvars, sequence, is_reversed, nodelist_loop)
3590+do_for = register.tag("for", do_for)
3591+
3592+def do_ifequal(parser, token, negate):
3593+    bits = list(token.split_contents())
3594+    if len(bits) != 3:
3595+        raise TemplateSyntaxError, "%r takes two arguments" % bits[0]
3596+    end_tag = 'end' + bits[0]
3597+    nodelist_true = parser.parse(('else', end_tag))
3598+    token = parser.next_token()
3599+    if token.contents == 'else':
3600+        nodelist_false = parser.parse((end_tag,))
3601+        parser.delete_first_token()
3602+    else:
3603+        nodelist_false = NodeList()
3604+    return IfEqualNode(bits[1], bits[2], nodelist_true, nodelist_false, negate)
3605+
3606+#@register.tag
3607+def ifequal(parser, token):
3608+    """
3609+    Outputs the contents of the block if the two arguments equal each other.
3610+
3611+    Examples::
3612+
3613+        {% ifequal user.id comment.user_id %}
3614+            ...
3615+        {% endifequal %}
3616+
3617+        {% ifnotequal user.id comment.user_id %}
3618+            ...
3619+        {% else %}
3620+            ...
3621+        {% endifnotequal %}
3622+    """
3623+    return do_ifequal(parser, token, False)
3624+ifequal = register.tag(ifequal)
3625+
3626+#@register.tag
3627+def ifnotequal(parser, token):
3628+    """
3629+    Outputs the contents of the block if the two arguments are not equal.
3630+    See ifequal.
3631+    """
3632+    return do_ifequal(parser, token, True)
3633+ifnotequal = register.tag(ifnotequal)
3634+
3635+#@register.tag(name="if")
3636+def do_if(parser, token):
3637+    """
3638+    The ``{% if %}`` tag evaluates a variable, and if that variable is "true"
3639+    (i.e., exists, is not empty, and is not a false boolean value), the
3640+    contents of the block are output:
3641+
3642+    ::
3643+
3644+        {% if athlete_list %}
3645+            Number of athletes: {{ athlete_list|count }}
3646+        {% else %}
3647+            No athletes.
3648+        {% endif %}
3649+
3650+    In the above, if ``athlete_list`` is not empty, the number of athletes will
3651+    be displayed by the ``{{ athlete_list|count }}`` variable.
3652+
3653+    As you can see, the ``if`` tag can take an option ``{% else %}`` clause
3654+    that will be displayed if the test fails.
3655+
3656+    ``if`` tags may use ``or``, ``and`` or ``not`` to test a number of
3657+    variables or to negate a given variable::
3658+
3659+        {% if not athlete_list %}
3660+            There are no athletes.
3661+        {% endif %}
3662+
3663+        {% if athlete_list or coach_list %}
3664+            There are some athletes or some coaches.
3665+        {% endif %}
3666+
3667+        {% if athlete_list and coach_list %}
3668+            Both atheletes and coaches are available.
3669+        {% endif %}
3670+
3671+        {% if not athlete_list or coach_list %}
3672+            There are no athletes, or there are some coaches.
3673+        {% endif %}
3674+
3675+        {% if athlete_list and not coach_list %}
3676+            There are some athletes and absolutely no coaches.
3677+        {% endif %}
3678+
3679+    ``if`` tags do not allow ``and`` and ``or`` clauses with the same tag,
3680+    because the order of logic would be ambigous. For example, this is
3681+    invalid::
3682+
3683+        {% if athlete_list and coach_list or cheerleader_list %}
3684+
3685+    If you need to combine ``and`` and ``or`` to do advanced logic, just use
3686+    nested if tags. For example::
3687+
3688+        {% if athlete_list %}
3689+            {% if coach_list or cheerleader_list %}
3690+                We have athletes, and either coaches or cheerleaders!
3691+            {% endif %}
3692+        {% endif %}
3693+    """
3694+    bits = token.contents.split()
3695+    del bits[0]
3696+    if not bits:
3697+        raise TemplateSyntaxError("'if' statement requires at least one argument")
3698+    # Bits now looks something like this: ['a', 'or', 'not', 'b', 'or', 'c.d']
3699+    bitstr = ' '.join(bits)
3700+    boolpairs = bitstr.split(' and ')
3701+    boolvars = []
3702+    if len(boolpairs) == 1:
3703+        link_type = IfNode.LinkTypes.or_
3704+        boolpairs = bitstr.split(' or ')
3705+    else:
3706+        link_type = IfNode.LinkTypes.and_
3707+        if ' or ' in bitstr:
3708+            raise TemplateSyntaxError, "'if' tags can't mix 'and' and 'or'"
3709+    for boolpair in boolpairs:
3710+        if ' ' in boolpair:
3711+            try:
3712+                not_, boolvar = boolpair.split()
3713+            except ValueError:
3714+                raise TemplateSyntaxError, "'if' statement improperly formatted"
3715+            if not_ != 'not':
3716+                raise TemplateSyntaxError, "Expected 'not' in if statement"
3717+            boolvars.append((True, parser.compile_filter(boolvar)))
3718+        else:
3719+            boolvars.append((False, parser.compile_filter(boolpair)))
3720+    nodelist_true = parser.parse(('else', 'endif'))
3721+    token = parser.next_token()
3722+    if token.contents == 'else':
3723+        nodelist_false = parser.parse(('endif',))
3724+        parser.delete_first_token()
3725+    else:
3726+        nodelist_false = NodeList()
3727+    return IfNode(boolvars, nodelist_true, nodelist_false, link_type)
3728+do_if = register.tag("if", do_if)
3729+
3730+#@register.tag
3731+def ifchanged(parser, token):
3732+    """
3733+    Checks if a value has changed from the last iteration of a loop.
3734+
3735+    The 'ifchanged' block tag is used within a loop. It has two possible uses.
3736+
3737+    1. Checks its own rendered contents against its previous state and only
3738+       displays the content if it has changed. For example, this displays a
3739+       list of days, only displaying the month if it changes::
3740+
3741+            <h1>Archive for {{ year }}</h1>
3742+
3743+            {% for date in days %}
3744+                {% ifchanged %}<h3>{{ date|date:"F" }}</h3>{% endifchanged %}
3745+                <a href="{{ date|date:"M/d"|lower }}/">{{ date|date:"j" }}</a>
3746+            {% endfor %}
3747+
3748+    2. If given a variable, check whether that variable has changed.
3749+       For example, the following shows the date every time it changes, but
3750+       only shows the hour if both the hour and the date have changed::
3751+
3752+            {% for date in days %}
3753+                {% ifchanged date.date %} {{ date.date }} {% endifchanged %}
3754+                {% ifchanged date.hour date.date %}
3755+                    {{ date.hour }}
3756+                {% endifchanged %}
3757+            {% endfor %}
3758+    """
3759+    bits = token.contents.split()
3760+    nodelist = parser.parse(('endifchanged',))
3761+    parser.delete_first_token()
3762+    return IfChangedNode(nodelist, *bits[1:])
3763+ifchanged = register.tag(ifchanged)
3764+
3765+#@register.tag
3766+def ssi(parser, token):
3767+    """
3768+    Outputs the contents of a given file into the page.
3769+
3770+    Like a simple "include" tag, the ``ssi`` tag includes the contents
3771+    of another file -- which must be specified using an absolute path --
3772+    in the current page::
3773+
3774+        {% ssi /home/html/ljworld.com/includes/right_generic.html %}
3775+
3776+    If the optional "parsed" parameter is given, the contents of the included
3777+    file are evaluated as template code, with the current context::
3778+
3779+        {% ssi /home/html/ljworld.com/includes/right_generic.html parsed %}
3780+    """
3781+    bits = token.contents.split()
3782+    parsed = False
3783+    if len(bits) not in (2, 3):
3784+        raise TemplateSyntaxError("'ssi' tag takes one argument: the path to"
3785+                                  " the file to be included")
3786+    if len(bits) == 3:
3787+        if bits[2] == 'parsed':
3788+            parsed = True
3789+        else:
3790+            raise TemplateSyntaxError("Second (optional) argument to %s tag"
3791+                                      " must be 'parsed'" % bits[0])
3792+    return SsiNode(bits[1], parsed)
3793+ssi = register.tag(ssi)
3794+
3795+#@register.tag
3796+def load(parser, token):
3797+    """
3798+    Loads a custom template tag set.
3799+
3800+    For example, to load the template tags in
3801+    ``django/templatetags/news/photos.py``::
3802+
3803+        {% load news.photos %}
3804+    """
3805+    bits = token.contents.split()
3806+    for taglib in bits[1:]:
3807+        # add the library to the parser
3808+        try:
3809+            lib = get_library(taglib)
3810+            parser.add_library(lib)
3811+        except InvalidTemplateLibrary, e:
3812+            raise TemplateSyntaxError("'%s' is not a valid tag library: %s" %
3813+                                      (taglib, e))
3814+    return LoadNode()
3815+load = register.tag(load)
3816+
3817+#@register.tag
3818+def now(parser, token):
3819+    """
3820+    Displays the date, formatted according to the given string.
3821+
3822+    Uses the same format as PHP's ``date()`` function; see http://php.net/date
3823+    for all the possible values.
3824+
3825+    Sample usage::
3826+
3827+        It is {% now "jS F Y H:i" %}
3828+    """
3829+    bits = token.contents.split('"')
3830+    if len(bits) != 3:
3831+        raise TemplateSyntaxError, "'now' statement takes one argument"
3832+    format_string = bits[1]
3833+    return NowNode(format_string)
3834+now = register.tag(now)
3835+
3836+#@register.tag
3837+def regroup(parser, token):
3838+    """
3839+    Regroups a list of alike objects by a common attribute.
3840+
3841+    This complex tag is best illustrated by use of an example:  say that
3842+    ``people`` is a list of ``Person`` objects that have ``first_name``,
3843+    ``last_name``, and ``gender`` attributes, and you'd like to display a list
3844+    that looks like:
3845+
3846+        * Male:
3847+            * George Bush
3848+            * Bill Clinton
3849+        * Female:
3850+            * Margaret Thatcher
3851+            * Colendeeza Rice
3852+        * Unknown:
3853+            * Pat Smith
3854+
3855+    The following snippet of template code would accomplish this dubious task::
3856+
3857+        {% regroup people by gender as grouped %}
3858+        <ul>
3859+        {% for group in grouped %}
3860+            <li>{{ group.grouper }}
3861+            <ul>
3862+                {% for item in group.list %}
3863+                <li>{{ item }}</li>
3864+                {% endfor %}
3865+            </ul>
3866+        {% endfor %}
3867+        </ul>
3868+
3869+    As you can see, ``{% regroup %}`` populates a variable with a list of
3870+    objects with ``grouper`` and ``list`` attributes.  ``grouper`` contains the
3871+    item that was grouped by; ``list`` contains the list of objects that share
3872+    that ``grouper``.  In this case, ``grouper`` would be ``Male``, ``Female``
3873+    and ``Unknown``, and ``list`` is the list of people with those genders.
3874+
3875+    Note that `{% regroup %}`` does not work when the list to be grouped is not
3876+    sorted by the key you are grouping by!  This means that if your list of
3877+    people was not sorted by gender, you'd need to make sure it is sorted
3878+    before using it, i.e.::
3879+
3880+        {% regroup people|dictsort:"gender" by gender as grouped %}
3881+
3882+    """
3883+    firstbits = token.contents.split(None, 3)
3884+    if len(firstbits) != 4:
3885+        raise TemplateSyntaxError, "'regroup' tag takes five arguments"
3886+    target = parser.compile_filter(firstbits[1])
3887+    if firstbits[2] != 'by':
3888+        raise TemplateSyntaxError("second argument to 'regroup' tag must be 'by'")
3889+    lastbits_reversed = firstbits[3][::-1].split(None, 2)
3890+    if lastbits_reversed[1][::-1] != 'as':
3891+        raise TemplateSyntaxError("next-to-last argument to 'regroup' tag must"
3892+                                  " be 'as'")
3893+
3894+    expression = parser.compile_filter(lastbits_reversed[2][::-1])
3895+
3896+    var_name = lastbits_reversed[0][::-1]
3897+    return RegroupNode(target, expression, var_name)
3898+regroup = register.tag(regroup)
3899+
3900+def spaceless(parser, token):
3901+    """
3902+    Removes whitespace between HTML tags, including tab and newline characters.
3903+
3904+    Example usage::
3905+
3906+        {% spaceless %}
3907+            <p>
3908+                <a href="foo/">Foo</a>
3909+            </p>
3910+        {% endspaceless %}
3911+
3912+    This example would return this HTML::
3913+
3914+        <p><a href="foo/">Foo</a></p>
3915+
3916+    Only space between *tags* is normalized -- not space between tags and text.
3917+    In this example, the space around ``Hello`` won't be stripped::
3918+
3919+        {% spaceless %}
3920+            <strong>
3921+                Hello
3922+            </strong>
3923+        {% endspaceless %}
3924+    """
3925+    nodelist = parser.parse(('endspaceless',))
3926+    parser.delete_first_token()
3927+    return SpacelessNode(nodelist)
3928+spaceless = register.tag(spaceless)
3929+
3930+#@register.tag
3931+def templatetag(parser, token):
3932+    """
3933+    Outputs one of the bits used to compose template tags.
3934+
3935+    Since the template system has no concept of "escaping", to display one of
3936+    the bits used in template tags, you must use the ``{% templatetag %}`` tag.
3937+
3938+    The argument tells which template bit to output:
3939+
3940+        ==================  =======
3941+        Argument            Outputs
3942+        ==================  =======
3943+        ``openblock``       ``{%``
3944+        ``closeblock``      ``%}``
3945+        ``openvariable``    ``{{``
3946+        ``closevariable``   ``}}``
3947+        ``openbrace``       ``{``
3948+        ``closebrace``      ``}``
3949+        ``opencomment``     ``{#``
3950+        ``closecomment``    ``#}``
3951+        ==================  =======
3952+    """
3953+    bits = token.contents.split()
3954+    if len(bits) != 2:
3955+        raise TemplateSyntaxError, "'templatetag' statement takes one argument"
3956+    tag = bits[1]
3957+    if tag not in TemplateTagNode.mapping:
3958+        raise TemplateSyntaxError("Invalid templatetag argument: '%s'."
3959+                                  " Must be one of: %s" %
3960+                                  (tag, TemplateTagNode.mapping.keys()))
3961+    return TemplateTagNode(tag)
3962+templatetag = register.tag(templatetag)
3963+
3964+def url(parser, token):
3965+    """
3966+    Returns an absolute URL matching given view with its parameters.
3967+
3968+    This is a way to define links that aren't tied to a particular URL
3969+    configuration::
3970+
3971+        {% url path.to.some_view arg1,arg2,name1=value1 %}
3972+
3973+    The first argument is a path to a view. It can be an absolute python path
3974+    or just ``app_name.view_name`` without the project name if the view is
3975+    located inside the project.  Other arguments are comma-separated values
3976+    that will be filled in place of positional and keyword arguments in the
3977+    URL. All arguments for the URL should be present.
3978+
3979+    For example if you have a view ``app_name.client`` taking client's id and
3980+    the corresponding line in a URLconf looks like this::
3981+
3982+        ('^client/(\d+)/$', 'app_name.client')
3983+
3984+    and this app's URLconf is included into the project's URLconf under some
3985+    path::
3986+
3987+        ('^clients/', include('project_name.app_name.urls'))
3988+
3989+    then in a template you can create a link for a certain client like this::
3990+
3991+        {% url app_name.client client.id %}
3992+
3993+    The URL will look like ``/clients/client/123/``.
3994+    """
3995+    bits = token.contents.split(' ', 2)
3996+    if len(bits) < 2:
3997+        raise TemplateSyntaxError("'%s' takes at least one argument"
3998+                                  " (path to a view)" % bits[0])
3999+    args = []
4000+    kwargs = {}
4001+    if len(bits) > 2:
4002+        for arg in bits[2].split(','):
4003+            if '=' in arg:
4004+                k, v = arg.split('=', 1)
4005+                k = k.strip()
4006+                kwargs[k] = parser.compile_filter(v)
4007+            else:
4008+                args.append(parser.compile_filter(arg))
4009+    return URLNode(bits[1], args, kwargs)
4010+url = register.tag(url)
4011+
4012+#@register.tag
4013+def widthratio(parser, token):
4014+    """
4015+    For creating bar charts and such, this tag calculates the ratio of a given
4016+    value to a maximum value, and then applies that ratio to a constant.
4017+
4018+    For example::
4019+
4020+        <img src='bar.gif' height='10' width='{% widthratio this_value max_value 100 %}' />
4021+
4022+    Above, if ``this_value`` is 175 and ``max_value`` is 200, the the image in
4023+    the above example will be 88 pixels wide (because 175/200 = .875;
4024+    .875 * 100 = 87.5 which is rounded up to 88).
4025+    """
4026+    bits = token.contents.split()
4027+    if len(bits) != 4:
4028+        raise TemplateSyntaxError("widthratio takes three arguments")
4029+    tag, this_value_expr, max_value_expr, max_width = bits
4030+    try:
4031+        max_width = int(max_width)
4032+    except ValueError:
4033+        raise TemplateSyntaxError("widthratio final argument must be an integer")
4034+    return WidthRatioNode(parser.compile_filter(this_value_expr),
4035+                          parser.compile_filter(max_value_expr), max_width)
4036+widthratio = register.tag(widthratio)
4037+
4038+#@register.tag
4039+def do_with(parser, token):
4040+    """
4041+    Adds a value to the context (inside of this block) for caching and easy
4042+    access.
4043+
4044+    For example::
4045+
4046+        {% with person.some_sql_method as total %}
4047+            {{ total }} object{{ total|pluralize }}
4048+        {% endwith %}
4049+    """
4050+    bits = list(token.split_contents())
4051+    if len(bits) != 4 or bits[2] != "as":
4052+        raise TemplateSyntaxError("%r expected format is 'value as name'" %
4053+                                  bits[0])
4054+    var = parser.compile_filter(bits[1])
4055+    name = bits[3]
4056+    nodelist = parser.parse(('endwith',))
4057+    parser.delete_first_token()
4058+    return WithNode(var, name, nodelist)
4059+do_with = register.tag('with', do_with)
4060diff --git a/tests/regressiontests/templates/tests.py b/tests/regressiontests/templates/tests.py
4061index 186b8aa..ac0912d 100644
4062--- a/tests/regressiontests/templates/tests.py
4063+++ b/tests/regressiontests/templates/tests.py
4064@@ -51,7 +51,7 @@ def do_echo(parser, token):
4065 
4066 register.tag("echo", do_echo)
4067 
4068-template.libraries['django.templatetags.testtags'] = register
4069+template.libraries['testtags'] = register
4070 
4071 #####################################
4072 # Helper objects for template tests #