Code

Ticket #6587: new_template_lib_loader_10.diff

File new_template_lib_loader_10.diff, 70.0 KB (added by oyvind, 6 years ago)

fixed import to get the correct module, moved defaulttags and defaultfilters to avoid circular imports, fixes #6579

Line 
1diff --git a/django/template/__init__.py b/django/template/__init__.py
2index 5c4ab30..31a3790 100644
3--- a/django/template/__init__.py
4+++ b/django/template/__init__.py
5@@ -59,6 +59,7 @@ from django.utils.encoding import smart_unicode, force_unicode
6 from django.utils.translation import ugettext as _
7 from django.utils.safestring import SafeData, EscapeData, mark_safe, mark_for_escaping
8 from django.utils.html import escape
9+from django.templatetags import get_templatetags_modules
10 
11 __all__ = ('Template', 'Context', 'RequestContext', 'compile_string')
12 
13@@ -913,22 +914,46 @@ class Library(object):
14             return func
15         return dec
16 
17-def get_library(module_name):
18-    lib = libraries.get(module_name, None)
19+def import_library(module_name):
20+    try:
21+        components = module_name.split('.')
22+        mod = __import__(module_name)
23+        for comp in components[1:]:
24+            mod = getattr(mod, comp)
25+    except ImportError, AttributeError:
26+        return None
27+    try:
28+        return mod.register
29+    except AttributeError:
30+        raise InvalidTemplateLibrary("Template library %s does not have a variable named 'register'" % module_name)
31+
32+def get_library(library_name):
33+    lib = libraries.get(library_name, None)
34     if not lib:
35-        try:
36-            mod = __import__(module_name, {}, {}, [''])
37-        except ImportError, e:
38-            raise InvalidTemplateLibrary("Could not load template library from %s, %s" % (module_name, e))
39-        try:
40-            lib = mod.register
41-            libraries[module_name] = lib
42-        except AttributeError:
43-            raise InvalidTemplateLibrary("Template library %s does not have a variable named 'register'" % module_name)
44+
45+        """
46+        If library is not already loaded loop over all templatetags modules to locate it.
47+
48+        {% load somelib %} and {% load someotherlib %} loops twice.
49+
50+        Subsequent loads eg. {% load somelib %} in the same thread will grab the cached
51+        module from libraries.
52+        """
53+        templatetags_modules = get_templatetags_modules()
54+        tried_modules = []
55+        for module in templatetags_modules:
56+            taglib_module = '%s.%s' % (module, library_name)
57+            tried_modules.append(taglib_module)
58+            lib = import_library(taglib_module)
59+            if lib:
60+                libraries[library_name] = lib
61+                break
62+        if not lib:
63+            raise InvalidTemplateLibrary("Template library %s not found, tried %s" % (library_name, ','.join(tried_modules)))
64     return lib
65 
66 def add_to_builtins(module_name):
67-    builtins.append(get_library(module_name))
68+    builtins.append(import_library(module_name))
69 
70-add_to_builtins('django.template.defaulttags')
71-add_to_builtins('django.template.defaultfilters')
72+add_to_builtins('django.templatetags.defaulttags')
73+add_to_builtins('django.templatetags.defaultfilters')
74diff --git a/django/template/defaulttags.py b/django/template/defaulttags.py
75index 01c43ee..535457a 100644
76--- a/django/template/defaulttags.py
77+++ b/django/template/defaulttags.py
78@@ -852,7 +852,7 @@ def load(parser, token):
79     for taglib in bits[1:]:
80         # add the library to the parser
81         try:
82-            lib = get_library("django.templatetags.%s" % taglib)
83+            lib = get_library(taglib)
84             parser.add_library(lib)
85         except InvalidTemplateLibrary, e:
86             raise TemplateSyntaxError("'%s' is not a valid tag library: %s" %
87diff --git a/django/templatetags/__init__.py b/django/templatetags/__init__.py
88index 9204535..6f1aaa8 100644
89--- a/django/templatetags/__init__.py
90+++ b/django/templatetags/__init__.py
91@@ -1,7 +1,18 @@
92 from django.conf import settings
93 
94-for a in settings.INSTALLED_APPS:
95-    try:
96-        __path__.extend(__import__(a + '.templatetags', {}, {}, ['']).__path__)
97-    except ImportError:
98-        pass
99+templatetags_modules= []
100+
101+def get_templatetags_modules():
102+    if not templatetags_modules:
103+        """ Populate list once per thread. """
104+        for app_module in ['django'] + list(settings.INSTALLED_APPS):
105+            try:
106+                name = '%s.templatetags' % app_module
107+                components = name.split('.')
108+                mod = __import__(name)
109+                for comp in components[1:]:
110+                    mod = getattr(mod, comp)
111+                templatetags_modules.append(name)
112+            except ImportError, AttributeError:
113+                pass
114+    return templatetags_modules
115diff --git a/django/templatetags/defaultfilters.py b/django/templatetags/defaultfilters.py
116new file mode 100644
117index 0000000..cef3143
118--- /dev/null
119+++ b/django/templatetags/defaultfilters.py
120@@ -0,0 +1,851 @@
121+"""Default variable filters."""
122+
123+import re
124+import random as random_module
125+try:
126+    from functools import wraps
127+except ImportError:
128+    from django.utils.functional import wraps  # Python 2.3, 2.4 fallback.
129+
130+from django.template import Variable, Library
131+from django.conf import settings
132+from django.utils.translation import ugettext, ungettext
133+from django.utils.encoding import force_unicode, iri_to_uri
134+from django.utils.safestring import mark_safe, SafeData
135+
136+register = Library()
137+
138+#######################
139+# STRING DECORATOR    #
140+#######################
141+
142+def stringfilter(func):
143+    """
144+    Decorator for filters which should only receive unicode objects. The object
145+    passed as the first positional argument will be converted to a unicode
146+    object.
147+    """
148+    def _dec(*args, **kwargs):
149+        if args:
150+            args = list(args)
151+            args[0] = force_unicode(args[0])
152+            if isinstance(args[0], SafeData) and getattr(func, 'is_safe', False):
153+                return mark_safe(func(*args, **kwargs))
154+        return func(*args, **kwargs)
155+
156+    # Include a reference to the real function (used to check original
157+    # arguments by the template parser).
158+    _dec._decorated_function = getattr(func, '_decorated_function', func)
159+    for attr in ('is_safe', 'needs_autoescape'):
160+        if hasattr(func, attr):
161+            setattr(_dec, attr, getattr(func, attr))
162+    return wraps(func)(_dec)
163+
164+###################
165+# STRINGS         #
166+###################
167+
168+
169+def addslashes(value):
170+    """
171+    Adds slashes before quotes. Useful for escaping strings in CSV, for
172+    example. Less useful for escaping JavaScript; use the ``escapejs``
173+    filter instead.
174+    """
175+    return value.replace('\\', '\\\\').replace('"', '\\"').replace("'", "\\'")
176+addslashes.is_safe = True
177+addslashes = stringfilter(addslashes)
178+
179+def capfirst(value):
180+    """Capitalizes the first character of the value."""
181+    return value and value[0].upper() + value[1:]
182+capfirst.is_safe=True
183+capfirst = stringfilter(capfirst)
184+
185+_js_escapes = (
186+    ('\\', '\\\\'),
187+    ('"', '\\"'),
188+    ("'", "\\'"),
189+    ('\n', '\\n'),
190+    ('\r', '\\r'),
191+    ('\b', '\\b'),
192+    ('\f', '\\f'),
193+    ('\t', '\\t'),
194+    ('\v', '\\v'),
195+    ('</', '<\\/'),
196+)
197+def escapejs(value):
198+    """Backslash-escapes characters for use in JavaScript strings."""
199+    for bad, good in _js_escapes:
200+        value = value.replace(bad, good)
201+    return value
202+escapejs = stringfilter(escapejs)
203+
204+def fix_ampersands(value):
205+    """Replaces ampersands with ``&amp;`` entities."""
206+    from django.utils.html import fix_ampersands
207+    return fix_ampersands(value)
208+fix_ampersands.is_safe=True
209+fix_ampersands = stringfilter(fix_ampersands)
210+
211+def floatformat(text, arg=-1):
212+    """
213+    Displays a float to a specified number of decimal places.
214+
215+    If called without an argument, it displays the floating point number with
216+    one decimal place -- but only if there's a decimal place to be displayed:
217+
218+    * num1 = 34.23234
219+    * num2 = 34.00000
220+    * num3 = 34.26000
221+    * {{ num1|floatformat }} displays "34.2"
222+    * {{ num2|floatformat }} displays "34"
223+    * {{ num3|floatformat }} displays "34.3"
224+
225+    If arg is positive, it will always display exactly arg number of decimal
226+    places:
227+
228+    * {{ num1|floatformat:3 }} displays "34.232"
229+    * {{ num2|floatformat:3 }} displays "34.000"
230+    * {{ num3|floatformat:3 }} displays "34.260"
231+
232+    If arg is negative, it will display arg number of decimal places -- but
233+    only if there are places to be displayed:
234+
235+    * {{ num1|floatformat:"-3" }} displays "34.232"
236+    * {{ num2|floatformat:"-3" }} displays "34"
237+    * {{ num3|floatformat:"-3" }} displays "34.260"
238+    """
239+    try:
240+        f = float(text)
241+    except (ValueError, TypeError):
242+        return u''
243+    try:
244+        d = int(arg)
245+    except ValueError:
246+        return force_unicode(f)
247+    try:
248+        m = f - int(f)
249+    except OverflowError:
250+        return force_unicode(f)
251+    if not m and d < 0:
252+        return mark_safe(u'%d' % int(f))
253+    else:
254+        formatstr = u'%%.%df' % abs(d)
255+        return mark_safe(formatstr % f)
256+floatformat.is_safe = True
257+
258+def iriencode(value):
259+    """Escapes an IRI value for use in a URL."""
260+    return force_unicode(iri_to_uri(value))
261+iriencode.is_safe = True
262+iriencode = stringfilter(iriencode)
263+
264+def linenumbers(value, autoescape=None):
265+    """Displays text with line numbers."""
266+    from django.utils.html import escape
267+    lines = value.split(u'\n')
268+    # Find the maximum width of the line count, for use with zero padding
269+    # string format command
270+    width = unicode(len(unicode(len(lines))))
271+    if not autoescape or isinstance(value, SafeData):
272+        for i, line in enumerate(lines):
273+            lines[i] = (u"%0" + width  + u"d. %s") % (i + 1, line)
274+    else:
275+        for i, line in enumerate(lines):
276+            lines[i] = (u"%0" + width  + u"d. %s") % (i + 1, escape(line))
277+    return mark_safe(u'\n'.join(lines))
278+linenumbers.is_safe = True
279+linenumbers.needs_autoescape = True
280+linenumbers = stringfilter(linenumbers)
281+
282+def lower(value):
283+    """Converts a string into all lowercase."""
284+    return value.lower()
285+lower.is_safe = True
286+lower = stringfilter(lower)
287+
288+def make_list(value):
289+    """
290+    Returns the value turned into a list.
291+
292+    For an integer, it's a list of digits.
293+    For a string, it's a list of characters.
294+    """
295+    return list(value)
296+make_list.is_safe = False
297+make_list = stringfilter(make_list)
298+
299+def slugify(value):
300+    """
301+    Normalizes string, converts to lowercase, removes non-alpha characters,
302+    and converts spaces to hyphens.
303+    """
304+    import unicodedata
305+    value = unicodedata.normalize('NFKD', value).encode('ascii', 'ignore')
306+    value = unicode(re.sub('[^\w\s-]', '', value).strip().lower())
307+    return mark_safe(re.sub('[-\s]+', '-', value))
308+slugify.is_safe = True
309+slugify = stringfilter(slugify)
310+
311+def stringformat(value, arg):
312+    """
313+    Formats the variable according to the arg, a string formatting specifier.
314+
315+    This specifier uses Python string formating syntax, with the exception that
316+    the leading "%" is dropped.
317+
318+    See http://docs.python.org/lib/typesseq-strings.html for documentation
319+    of Python string formatting
320+    """
321+    try:
322+        return (u"%" + unicode(arg)) % value
323+    except (ValueError, TypeError):
324+        return u""
325+stringformat.is_safe = True
326+
327+def title(value):
328+    """Converts a string into titlecase."""
329+    return re.sub("([a-z])'([A-Z])", lambda m: m.group(0).lower(), value.title())
330+title.is_safe = True
331+title = stringfilter(title)
332+
333+def truncatewords(value, arg):
334+    """
335+    Truncates a string after a certain number of words.
336+
337+    Argument: Number of words to truncate after.
338+    """
339+    from django.utils.text import truncate_words
340+    try:
341+        length = int(arg)
342+    except ValueError: # Invalid literal for int().
343+        return value # Fail silently.
344+    return truncate_words(value, length)
345+truncatewords.is_safe = True
346+truncatewords = stringfilter(truncatewords)
347+
348+def truncatewords_html(value, arg):
349+    """
350+    Truncates HTML after a certain number of words.
351+
352+    Argument: Number of words to truncate after.
353+    """
354+    from django.utils.text import truncate_html_words
355+    try:
356+        length = int(arg)
357+    except ValueError: # invalid literal for int()
358+        return value # Fail silently.
359+    return truncate_html_words(value, length)
360+truncatewords_html.is_safe = True
361+truncatewords_html = stringfilter(truncatewords_html)
362+
363+def upper(value):
364+    """Converts a string into all uppercase."""
365+    return value.upper()
366+upper.is_safe = False
367+upper = stringfilter(upper)
368+
369+def urlencode(value):
370+    """Escapes a value for use in a URL."""
371+    from django.utils.http import urlquote
372+    return urlquote(value)
373+urlencode.is_safe = False
374+urlencode = stringfilter(urlencode)
375+
376+def urlize(value, autoescape=None):
377+    """Converts URLs in plain text into clickable links."""
378+    from django.utils.html import urlize
379+    return mark_safe(urlize(value, nofollow=True, autoescape=autoescape))
380+urlize.is_safe=True
381+urlize.needs_autoescape = True
382+urlize = stringfilter(urlize)
383+
384+def urlizetrunc(value, limit, autoescape=None):
385+    """
386+    Converts URLs into clickable links, truncating URLs to the given character
387+    limit, and adding 'rel=nofollow' attribute to discourage spamming.
388+
389+    Argument: Length to truncate URLs to.
390+    """
391+    from django.utils.html import urlize
392+    return mark_safe(urlize(value, trim_url_limit=int(limit), nofollow=True,
393+                            autoescape=autoescape))
394+urlizetrunc.is_safe = True
395+urlizetrunc.needs_autoescape = True
396+urlizetrunc = stringfilter(urlizetrunc)
397+
398+def wordcount(value):
399+    """Returns the number of words."""
400+    return len(value.split())
401+wordcount.is_safe = False
402+wordcount = stringfilter(wordcount)
403+
404+def wordwrap(value, arg):
405+    """
406+    Wraps words at specified line length.
407+
408+    Argument: number of characters to wrap the text at.
409+    """
410+    from django.utils.text import wrap
411+    return wrap(value, int(arg))
412+wordwrap.is_safe = True
413+wordwrap = stringfilter(wordwrap)
414+
415+def ljust(value, arg):
416+    """
417+    Left-aligns the value in a field of a given width.
418+
419+    Argument: field size.
420+    """
421+    return value.ljust(int(arg))
422+ljust.is_safe = True
423+ljust = stringfilter(ljust)
424+
425+def rjust(value, arg):
426+    """
427+    Right-aligns the value in a field of a given width.
428+
429+    Argument: field size.
430+    """
431+    return value.rjust(int(arg))
432+rjust.is_safe = True
433+rjust = stringfilter(rjust)
434+
435+def center(value, arg):
436+    """Centers the value in a field of a given width."""
437+    return value.center(int(arg))
438+center.is_safe = True
439+center = stringfilter(center)
440+
441+def cut(value, arg):
442+    """
443+    Removes all values of arg from the given string.
444+    """
445+    safe = isinstance(value, SafeData)
446+    value = value.replace(arg, u'')
447+    if safe and arg != ';':
448+        return mark_safe(value)
449+    return value
450+cut = stringfilter(cut)
451+
452+###################
453+# HTML STRINGS    #
454+###################
455+
456+def escape(value):
457+    """
458+    Marks the value as a string that should not be auto-escaped.
459+    """
460+    from django.utils.safestring import mark_for_escaping
461+    return mark_for_escaping(value)
462+escape.is_safe = True
463+escape = stringfilter(escape)
464+
465+def force_escape(value):
466+    """
467+    Escapes a string's HTML. This returns a new string containing the escaped
468+    characters (as opposed to "escape", which marks the content for later
469+    possible escaping).
470+    """
471+    from django.utils.html import escape
472+    return mark_safe(escape(value))
473+force_escape = stringfilter(force_escape)
474+force_escape.is_safe = True
475+
476+def linebreaks(value, autoescape=None):
477+    """
478+    Replaces line breaks in plain text with appropriate HTML; a single
479+    newline becomes an HTML line break (``<br />``) and a new line
480+    followed by a blank line becomes a paragraph break (``</p>``).
481+    """
482+    from django.utils.html import linebreaks
483+    autoescape = autoescape and not isinstance(value, SafeData)
484+    return mark_safe(linebreaks(value, autoescape))
485+linebreaks.is_safe = True
486+linebreaks.needs_autoescape = True
487+linebreaks = stringfilter(linebreaks)
488+
489+def linebreaksbr(value, autoescape=None):
490+    """
491+    Converts all newlines in a piece of plain text to HTML line breaks
492+    (``<br />``).
493+    """
494+    if autoescape and not isinstance(value, SafeData):
495+        from django.utils.html import escape
496+        value = escape(value)
497+    return mark_safe(value.replace('\n', '<br />'))
498+linebreaksbr.is_safe = True
499+linebreaksbr.needs_autoescape = True
500+linebreaksbr = stringfilter(linebreaksbr)
501+
502+def safe(value):
503+    """
504+    Marks the value as a string that should not be auto-escaped.
505+    """
506+    from django.utils.safestring import mark_safe
507+    return mark_safe(value)
508+safe.is_safe = True
509+safe = stringfilter(safe)
510+
511+def removetags(value, tags):
512+    """Removes a space separated list of [X]HTML tags from the output."""
513+    tags = [re.escape(tag) for tag in tags.split()]
514+    tags_re = u'(%s)' % u'|'.join(tags)
515+    starttag_re = re.compile(ur'<%s(/?>|(\s+[^>]*>))' % tags_re, re.U)
516+    endtag_re = re.compile(u'</%s>' % tags_re)
517+    value = starttag_re.sub(u'', value)
518+    value = endtag_re.sub(u'', value)
519+    return value
520+removetags.is_safe = True
521+removetags = stringfilter(removetags)
522+
523+def striptags(value):
524+    """Strips all [X]HTML tags."""
525+    from django.utils.html import strip_tags
526+    return strip_tags(value)
527+striptags.is_safe = True
528+striptags = stringfilter(striptags)
529+
530+###################
531+# LISTS           #
532+###################
533+
534+def dictsort(value, arg):
535+    """
536+    Takes a list of dicts, returns that list sorted by the property given in
537+    the argument.
538+    """
539+    var_resolve = Variable(arg).resolve
540+    decorated = [(var_resolve(item), item) for item in value]
541+    decorated.sort()
542+    return [item[1] for item in decorated]
543+dictsort.is_safe = False
544+
545+def dictsortreversed(value, arg):
546+    """
547+    Takes a list of dicts, returns that list sorted in reverse order by the
548+    property given in the argument.
549+    """
550+    var_resolve = Variable(arg).resolve
551+    decorated = [(var_resolve(item), item) for item in value]
552+    decorated.sort()
553+    decorated.reverse()
554+    return [item[1] for item in decorated]
555+dictsortreversed.is_safe = False
556+
557+def first(value):
558+    """Returns the first item in a list."""
559+    try:
560+        return value[0]
561+    except IndexError:
562+        return u''
563+first.is_safe = False
564+
565+def join(value, arg):
566+    """Joins a list with a string, like Python's ``str.join(list)``."""
567+    try:
568+        data = arg.join(map(force_unicode, value))
569+    except AttributeError: # fail silently but nicely
570+        return value
571+    safe_args = reduce(lambda lhs, rhs: lhs and isinstance(rhs, SafeData),
572+            value, True)
573+    if safe_args:
574+        return mark_safe(data)
575+    else:
576+        return data
577+join.is_safe = True
578+
579+def last(value):
580+    "Returns the last item in a list"
581+    try:
582+        return value[-1]
583+    except IndexError:
584+        return u''
585+last.is_safe = True
586+
587+def length(value):
588+    """Returns the length of the value - useful for lists."""
589+    return len(value)
590+length.is_safe = True
591+
592+def length_is(value, arg):
593+    """Returns a boolean of whether the value's length is the argument."""
594+    return len(value) == int(arg)
595+length_is.is_safe = True
596+
597+def random(value):
598+    """Returns a random item from the list."""
599+    return random_module.choice(value)
600+random.is_safe = True
601+
602+def slice_(value, arg):
603+    """
604+    Returns a slice of the list.
605+
606+    Uses the same syntax as Python's list slicing; see
607+    http://diveintopython.org/native_data_types/lists.html#odbchelper.list.slice
608+    for an introduction.
609+    """
610+    try:
611+        bits = []
612+        for x in arg.split(u':'):
613+            if len(x) == 0:
614+                bits.append(None)
615+            else:
616+                bits.append(int(x))
617+        return value[slice(*bits)]
618+
619+    except (ValueError, TypeError):
620+        return value # Fail silently.
621+slice_.is_safe = True
622+
623+def unordered_list(value, autoescape=None):
624+    """
625+    Recursively takes a self-nested list and returns an HTML unordered list --
626+    WITHOUT opening and closing <ul> tags.
627+
628+    The list is assumed to be in the proper format. For example, if ``var``
629+    contains: ``['States', ['Kansas', ['Lawrence', 'Topeka'], 'Illinois']]``,
630+    then ``{{ var|unordered_list }}`` would return::
631+
632+        <li>States
633+        <ul>
634+                <li>Kansas
635+                <ul>
636+                        <li>Lawrence</li>
637+                        <li>Topeka</li>
638+                </ul>
639+                </li>
640+                <li>Illinois</li>
641+        </ul>
642+        </li>
643+    """
644+    if autoescape:
645+        from django.utils.html import conditional_escape
646+        escaper = conditional_escape
647+    else:
648+        escaper = lambda x: x
649+    def convert_old_style_list(list_):
650+        """
651+        Converts old style lists to the new easier to understand format.
652+
653+        The old list format looked like:
654+            ['Item 1', [['Item 1.1', []], ['Item 1.2', []]]
655+
656+        And it is converted to:
657+            ['Item 1', ['Item 1.1', 'Item 1.2]]
658+        """
659+        if not isinstance(list_, (tuple, list)) or len(list_) != 2:
660+            return list_, False
661+        first_item, second_item = list_
662+        if second_item == []:
663+            return [first_item], True
664+        old_style_list = True
665+        new_second_item = []
666+        for sublist in second_item:
667+            item, old_style_list = convert_old_style_list(sublist)
668+            if not old_style_list:
669+                break
670+            new_second_item.extend(item)
671+        if old_style_list:
672+            second_item = new_second_item
673+        return [first_item, second_item], old_style_list
674+    def _helper(list_, tabs=1):
675+        indent = u'\t' * tabs
676+        output = []
677+
678+        list_length = len(list_)
679+        i = 0
680+        while i < list_length:
681+            title = list_[i]
682+            sublist = ''
683+            sublist_item = None
684+            if isinstance(title, (list, tuple)):
685+                sublist_item = title
686+                title = ''
687+            elif i < list_length - 1:
688+                next_item = list_[i+1]
689+                if next_item and isinstance(next_item, (list, tuple)):
690+                    # The next item is a sub-list.
691+                    sublist_item = next_item
692+                    # We've processed the next item now too.
693+                    i += 1
694+            if sublist_item:
695+                sublist = _helper(sublist_item, tabs+1)
696+                sublist = '\n%s<ul>\n%s\n%s</ul>\n%s' % (indent, sublist,
697+                                                         indent, indent)
698+            output.append('%s<li>%s%s</li>' % (indent,
699+                    escaper(force_unicode(title)), sublist))
700+            i += 1
701+        return '\n'.join(output)
702+    value, converted = convert_old_style_list(value)
703+    return mark_safe(_helper(value))
704+unordered_list.is_safe = True
705+unordered_list.needs_autoescape = True
706+
707+###################
708+# INTEGERS        #
709+###################
710+
711+def add(value, arg):
712+    """Adds the arg to the value."""
713+    return int(value) + int(arg)
714+add.is_safe = False
715+
716+def get_digit(value, arg):
717+    """
718+    Given a whole number, returns the requested digit of it, where 1 is the
719+    right-most digit, 2 is the second-right-most digit, etc. Returns the
720+    original value for invalid input (if input or argument is not an integer,
721+    or if argument is less than 1). Otherwise, output is always an integer.
722+    """
723+    try:
724+        arg = int(arg)
725+        value = int(value)
726+    except ValueError:
727+        return value # Fail silently for an invalid argument
728+    if arg < 1:
729+        return value
730+    try:
731+        return int(str(value)[-arg])
732+    except IndexError:
733+        return 0
734+get_digit.is_safe = False
735+
736+###################
737+# DATES           #
738+###################
739+
740+def date(value, arg=None):
741+    """Formats a date according to the given format."""
742+    from django.utils.dateformat import format
743+    if not value:
744+        return u''
745+    if arg is None:
746+        arg = settings.DATE_FORMAT
747+    return format(value, arg)
748+date.is_safe = False
749+
750+def time(value, arg=None):
751+    """Formats a time according to the given format."""
752+    from django.utils.dateformat import time_format
753+    if value in (None, u''):
754+        return u''
755+    if arg is None:
756+        arg = settings.TIME_FORMAT
757+    return time_format(value, arg)
758+time.is_safe = False
759+
760+def timesince(value, arg=None):
761+    """Formats a date as the time since that date (i.e. "4 days, 6 hours")."""
762+    from django.utils.timesince import timesince
763+    if not value:
764+        return u''
765+    if arg:
766+        return timesince(arg, value)
767+    return timesince(value)
768+timesince.is_safe = False
769+
770+def timeuntil(value, arg=None):
771+    """Formats a date as the time until that date (i.e. "4 days, 6 hours")."""
772+    from django.utils.timesince import timesince
773+    from datetime import datetime
774+    if not value:
775+        return u''
776+    if arg:
777+        return timesince(arg, value)
778+    return timesince(datetime.now(), value)
779+timeuntil.is_safe = False
780+
781+###################
782+# LOGIC           #
783+###################
784+
785+def default(value, arg):
786+    """If value is unavailable, use given default."""
787+    return value or arg
788+default.is_safe = False
789+
790+def default_if_none(value, arg):
791+    """If value is None, use given default."""
792+    if value is None:
793+        return arg
794+    return value
795+default_if_none.is_safe = False
796+
797+def divisibleby(value, arg):
798+    """Returns True if the value is devisible by the argument."""
799+    return int(value) % int(arg) == 0
800+divisibleby.is_safe = False
801+
802+def yesno(value, arg=None):
803+    """
804+    Given a string mapping values for true, false and (optionally) None,
805+    returns one of those strings accoding to the value:
806+
807+    ==========  ======================  ==================================
808+    Value       Argument                Outputs
809+    ==========  ======================  ==================================
810+    ``True``    ``"yeah,no,maybe"``     ``yeah``
811+    ``False``   ``"yeah,no,maybe"``     ``no``
812+    ``None``    ``"yeah,no,maybe"``     ``maybe``
813+    ``None``    ``"yeah,no"``           ``"no"`` (converts None to False
814+                                        if no mapping for None is given.
815+    ==========  ======================  ==================================
816+    """
817+    if arg is None:
818+        arg = ugettext('yes,no,maybe')
819+    bits = arg.split(u',')
820+    if len(bits) < 2:
821+        return value # Invalid arg.
822+    try:
823+        yes, no, maybe = bits
824+    except ValueError:
825+        # Unpack list of wrong size (no "maybe" value provided).
826+        yes, no, maybe = bits[0], bits[1], bits[1]
827+    if value is None:
828+        return maybe
829+    if value:
830+        return yes
831+    return no
832+yesno.is_safe = False
833+
834+###################
835+# MISC            #
836+###################
837+
838+def filesizeformat(bytes):
839+    """
840+    Formats the value like a 'human-readable' file size (i.e. 13 KB, 4.1 MB,
841+    102 bytes, etc).
842+    """
843+    try:
844+        bytes = float(bytes)
845+    except TypeError:
846+        return u"0 bytes"
847+
848+    if bytes < 1024:
849+        return ungettext("%(size)d byte", "%(size)d bytes", bytes) % {'size': bytes}
850+    if bytes < 1024 * 1024:
851+        return ugettext("%.1f KB") % (bytes / 1024)
852+    if bytes < 1024 * 1024 * 1024:
853+        return ugettext("%.1f MB") % (bytes / (1024 * 1024))
854+    return ugettext("%.1f GB") % (bytes / (1024 * 1024 * 1024))
855+filesizeformat.is_safe = True
856+
857+def pluralize(value, arg=u's'):
858+    """
859+    Returns a plural suffix if the value is not 1. By default, 's' is used as
860+    the suffix:
861+
862+    * If value is 0, vote{{ value|pluralize }} displays "0 votes".
863+    * If value is 1, vote{{ value|pluralize }} displays "1 vote".
864+    * If value is 2, vote{{ value|pluralize }} displays "2 votes".
865+
866+    If an argument is provided, that string is used instead:
867+
868+    * If value is 0, class{{ value|pluralize:"es" }} displays "0 classes".
869+    * If value is 1, class{{ value|pluralize:"es" }} displays "1 class".
870+    * If value is 2, class{{ value|pluralize:"es" }} displays "2 classes".
871+
872+    If the provided argument contains a comma, the text before the comma is
873+    used for the singular case and the text after the comma is used for the
874+    plural case:
875+
876+    * If value is 0, cand{{ value|pluralize:"y,ies" }} displays "0 candies".
877+    * If value is 1, cand{{ value|pluralize:"y,ies" }} displays "1 candy".
878+    * If value is 2, cand{{ value|pluralize:"y,ies" }} displays "2 candies".
879+    """
880+    if not u',' in arg:
881+        arg = u',' + arg
882+    bits = arg.split(u',')
883+    if len(bits) > 2:
884+        return u''
885+    singular_suffix, plural_suffix = bits[:2]
886+
887+    try:
888+        if int(value) != 1:
889+            return plural_suffix
890+    except ValueError: # Invalid string that's not a number.
891+        pass
892+    except TypeError: # Value isn't a string or a number; maybe it's a list?
893+        try:
894+            if len(value) != 1:
895+                return plural_suffix
896+        except TypeError: # len() of unsized object.
897+            pass
898+    return singular_suffix
899+pluralize.is_safe = False
900+
901+def phone2numeric(value):
902+    """Takes a phone number and converts it in to its numerical equivalent."""
903+    from django.utils.text import phone2numeric
904+    return phone2numeric(value)
905+phone2numeric.is_safe = True
906+
907+def pprint(value):
908+    """A wrapper around pprint.pprint -- for debugging, really."""
909+    from pprint import pformat
910+    try:
911+        return pformat(value)
912+    except Exception, e:
913+        return u"Error in formatting: %s" % force_unicode(e, errors="replace")
914+pprint.is_safe = True
915+
916+# Syntax: register.filter(name of filter, callback)
917+register.filter(add)
918+register.filter(addslashes)
919+register.filter(capfirst)
920+register.filter(center)
921+register.filter(cut)
922+register.filter(date)
923+register.filter(default)
924+register.filter(default_if_none)
925+register.filter(dictsort)
926+register.filter(dictsortreversed)
927+register.filter(divisibleby)
928+register.filter(escape)
929+register.filter(escapejs)
930+register.filter(filesizeformat)
931+register.filter(first)
932+register.filter(fix_ampersands)
933+register.filter(floatformat)
934+register.filter(force_escape)
935+register.filter(get_digit)
936+register.filter(iriencode)
937+register.filter(join)
938+register.filter(last)
939+register.filter(length)
940+register.filter(length_is)
941+register.filter(linebreaks)
942+register.filter(linebreaksbr)
943+register.filter(linenumbers)
944+register.filter(ljust)
945+register.filter(lower)
946+register.filter(make_list)
947+register.filter(phone2numeric)
948+register.filter(pluralize)
949+register.filter(pprint)
950+register.filter(removetags)
951+register.filter(random)
952+register.filter(rjust)
953+register.filter(safe)
954+register.filter('slice', slice_)
955+register.filter(slugify)
956+register.filter(stringformat)
957+register.filter(striptags)
958+register.filter(time)
959+register.filter(timesince)
960+register.filter(timeuntil)
961+register.filter(title)
962+register.filter(truncatewords)
963+register.filter(truncatewords_html)
964+register.filter(unordered_list)
965+register.filter(upper)
966+register.filter(urlencode)
967+register.filter(urlize)
968+register.filter(urlizetrunc)
969+register.filter(wordcount)
970+register.filter(wordwrap)
971+register.filter(yesno)
972diff --git a/django/templatetags/defaulttags.py b/django/templatetags/defaulttags.py
973new file mode 100644
974index 0000000..535457a
975--- /dev/null
976+++ b/django/templatetags/defaulttags.py
977@@ -0,0 +1,1105 @@
978+"""Default tags used by the template system, available to all templates."""
979+
980+import sys
981+import re
982+from itertools import cycle as itertools_cycle
983+try:
984+    reversed
985+except NameError:
986+    from django.utils.itercompat import reversed     # Python 2.3 fallback
987+
988+from django.template import Node, NodeList, Template, Context, Variable
989+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
990+from django.template import get_library, Library, InvalidTemplateLibrary
991+from django.conf import settings
992+from django.utils.encoding import smart_str, smart_unicode
993+from django.utils.itercompat import groupby
994+from django.utils.safestring import mark_safe
995+
996+register = Library()
997+
998+class AutoEscapeControlNode(Node):
999+    """Implements the actions of the autoescape tag."""
1000+    def __init__(self, setting, nodelist):
1001+        self.setting, self.nodelist = setting, nodelist
1002+
1003+    def render(self, context):
1004+        old_setting = context.autoescape
1005+        context.autoescape = self.setting
1006+        output = self.nodelist.render(context)
1007+        context.autoescape = old_setting
1008+        if self.setting:
1009+            return mark_safe(output)
1010+        else:
1011+            return output
1012+
1013+class CommentNode(Node):
1014+    def render(self, context):
1015+        return ''
1016+
1017+class CycleNode(Node):
1018+    def __init__(self, cyclevars, variable_name=None):
1019+        self.cycle_iter = itertools_cycle([Variable(v) for v in cyclevars])
1020+        self.variable_name = variable_name
1021+
1022+    def render(self, context):
1023+        value = self.cycle_iter.next().resolve(context)
1024+        if self.variable_name:
1025+            context[self.variable_name] = value
1026+        return value
1027+
1028+class DebugNode(Node):
1029+    def render(self, context):
1030+        from pprint import pformat
1031+        output = [pformat(val) for val in context]
1032+        output.append('\n\n')
1033+        output.append(pformat(sys.modules))
1034+        return ''.join(output)
1035+
1036+class FilterNode(Node):
1037+    def __init__(self, filter_expr, nodelist):
1038+        self.filter_expr, self.nodelist = filter_expr, nodelist
1039+
1040+    def render(self, context):
1041+        output = self.nodelist.render(context)
1042+        # Apply filters.
1043+        context.update({'var': output})
1044+        filtered = self.filter_expr.resolve(context)
1045+        context.pop()
1046+        return filtered
1047+
1048+class FirstOfNode(Node):
1049+    def __init__(self, vars):
1050+        self.vars = map(Variable, vars)
1051+
1052+    def render(self, context):
1053+        for var in self.vars:
1054+            try:
1055+                value = var.resolve(context)
1056+            except VariableDoesNotExist:
1057+                continue
1058+            if value:
1059+                return smart_unicode(value)
1060+        return u''
1061+
1062+class ForNode(Node):
1063+    def __init__(self, loopvars, sequence, is_reversed, nodelist_loop):
1064+        self.loopvars, self.sequence = loopvars, sequence
1065+        self.is_reversed = is_reversed
1066+        self.nodelist_loop = nodelist_loop
1067+
1068+    def __repr__(self):
1069+        reversed_text = self.is_reversed and ' reversed' or ''
1070+        return "<For Node: for %s in %s, tail_len: %d%s>" % \
1071+            (', '.join(self.loopvars), self.sequence, len(self.nodelist_loop),
1072+             reversed_text)
1073+
1074+    def __iter__(self):
1075+        for node in self.nodelist_loop:
1076+            yield node
1077+
1078+    def get_nodes_by_type(self, nodetype):
1079+        nodes = []
1080+        if isinstance(self, nodetype):
1081+            nodes.append(self)
1082+        nodes.extend(self.nodelist_loop.get_nodes_by_type(nodetype))
1083+        return nodes
1084+
1085+    def render(self, context):
1086+        nodelist = NodeList()
1087+        if 'forloop' in context:
1088+            parentloop = context['forloop']
1089+        else:
1090+            parentloop = {}
1091+        context.push()
1092+        try:
1093+            values = self.sequence.resolve(context, True)
1094+        except VariableDoesNotExist:
1095+            values = []
1096+        if values is None:
1097+            values = []
1098+        if not hasattr(values, '__len__'):
1099+            values = list(values)
1100+        len_values = len(values)
1101+        if self.is_reversed:
1102+            values = reversed(values)
1103+        unpack = len(self.loopvars) > 1
1104+        # Create a forloop value in the context.  We'll update counters on each
1105+        # iteration just below.
1106+        loop_dict = context['forloop'] = {'parentloop': parentloop}
1107+        for i, item in enumerate(values):
1108+            # Shortcuts for current loop iteration number.
1109+            loop_dict['counter0'] = i
1110+            loop_dict['counter'] = i+1
1111+            # Reverse counter iteration numbers.
1112+            loop_dict['revcounter'] = len_values - i
1113+            loop_dict['revcounter0'] = len_values - i - 1
1114+            # Boolean values designating first and last times through loop.
1115+            loop_dict['first'] = (i == 0)
1116+            loop_dict['last'] = (i == len_values - 1)
1117+
1118+            if unpack:
1119+                # If there are multiple loop variables, unpack the item into
1120+                # them.
1121+                context.update(dict(zip(self.loopvars, item)))
1122+            else:
1123+                context[self.loopvars[0]] = item
1124+            for node in self.nodelist_loop:
1125+                nodelist.append(node.render(context))
1126+            if unpack:
1127+                # The loop variables were pushed on to the context so pop them
1128+                # off again. This is necessary because the tag lets the length
1129+                # of loopvars differ to the length of each set of items and we
1130+                # don't want to leave any vars from the previous loop on the
1131+                # context.
1132+                context.pop()
1133+        context.pop()
1134+        return nodelist.render(context)
1135+
1136+class IfChangedNode(Node):
1137+    def __init__(self, nodelist, *varlist):
1138+        self.nodelist = nodelist
1139+        self._last_seen = None
1140+        self._varlist = map(Variable, varlist)
1141+        self._id = str(id(self))
1142+
1143+    def render(self, context):
1144+        if 'forloop' in context and self._id not in context['forloop']:
1145+            self._last_seen = None
1146+            context['forloop'][self._id] = 1
1147+        try:
1148+            if self._varlist:
1149+                # Consider multiple parameters.  This automatically behaves
1150+                # like an OR evaluation of the multiple variables.
1151+                compare_to = [var.resolve(context) for var in self._varlist]
1152+            else:
1153+                compare_to = self.nodelist.render(context)
1154+        except VariableDoesNotExist:
1155+            compare_to = None
1156+
1157+        if  compare_to != self._last_seen:
1158+            firstloop = (self._last_seen == None)
1159+            self._last_seen = compare_to
1160+            context.push()
1161+            context['ifchanged'] = {'firstloop': firstloop}
1162+            content = self.nodelist.render(context)
1163+            context.pop()
1164+            return content
1165+        else:
1166+            return ''
1167+
1168+class IfEqualNode(Node):
1169+    def __init__(self, var1, var2, nodelist_true, nodelist_false, negate):
1170+        self.var1, self.var2 = Variable(var1), Variable(var2)
1171+        self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false
1172+        self.negate = negate
1173+
1174+    def __repr__(self):
1175+        return "<IfEqualNode>"
1176+
1177+    def render(self, context):
1178+        try:
1179+            val1 = self.var1.resolve(context)
1180+        except VariableDoesNotExist:
1181+            val1 = None
1182+        try:
1183+            val2 = self.var2.resolve(context)
1184+        except VariableDoesNotExist:
1185+            val2 = None
1186+        if (self.negate and val1 != val2) or (not self.negate and val1 == val2):
1187+            return self.nodelist_true.render(context)
1188+        return self.nodelist_false.render(context)
1189+
1190+class IfNode(Node):
1191+    def __init__(self, bool_exprs, nodelist_true, nodelist_false, link_type):
1192+        self.bool_exprs = bool_exprs
1193+        self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false
1194+        self.link_type = link_type
1195+
1196+    def __repr__(self):
1197+        return "<If node>"
1198+
1199+    def __iter__(self):
1200+        for node in self.nodelist_true:
1201+            yield node
1202+        for node in self.nodelist_false:
1203+            yield node
1204+
1205+    def get_nodes_by_type(self, nodetype):
1206+        nodes = []
1207+        if isinstance(self, nodetype):
1208+            nodes.append(self)
1209+        nodes.extend(self.nodelist_true.get_nodes_by_type(nodetype))
1210+        nodes.extend(self.nodelist_false.get_nodes_by_type(nodetype))
1211+        return nodes
1212+
1213+    def render(self, context):
1214+        if self.link_type == IfNode.LinkTypes.or_:
1215+            for ifnot, bool_expr in self.bool_exprs:
1216+                try:
1217+                    value = bool_expr.resolve(context, True)
1218+                except VariableDoesNotExist:
1219+                    value = None
1220+                if (value and not ifnot) or (ifnot and not value):
1221+                    return self.nodelist_true.render(context)
1222+            return self.nodelist_false.render(context)
1223+        else:
1224+            for ifnot, bool_expr in self.bool_exprs:
1225+                try:
1226+                    value = bool_expr.resolve(context, True)
1227+                except VariableDoesNotExist:
1228+                    value = None
1229+                if not ((value and not ifnot) or (ifnot and not value)):
1230+                    return self.nodelist_false.render(context)
1231+            return self.nodelist_true.render(context)
1232+
1233+    class LinkTypes:
1234+        and_ = 0,
1235+        or_ = 1
1236+
1237+class RegroupNode(Node):
1238+    def __init__(self, target, expression, var_name):
1239+        self.target, self.expression = target, expression
1240+        self.var_name = var_name
1241+
1242+    def render(self, context):
1243+        obj_list = self.target.resolve(context, True)
1244+        if obj_list == None:
1245+            # target variable wasn't found in context; fail silently.
1246+            context[self.var_name] = []
1247+            return ''
1248+        # List of dictionaries in the format:
1249+        # {'grouper': 'key', 'list': [list of contents]}.
1250+        context[self.var_name] = [
1251+            {'grouper': key, 'list': list(val)}
1252+            for key, val in
1253+            groupby(obj_list, lambda v, f=self.expression.resolve: f(v, True))
1254+        ]
1255+        return ''
1256+
1257+def include_is_allowed(filepath):
1258+    for root in settings.ALLOWED_INCLUDE_ROOTS:
1259+        if filepath.startswith(root):
1260+            return True
1261+    return False
1262+
1263+class SsiNode(Node):
1264+    def __init__(self, filepath, parsed):
1265+        self.filepath, self.parsed = filepath, parsed
1266+
1267+    def render(self, context):
1268+        if not include_is_allowed(self.filepath):
1269+            if settings.DEBUG:
1270+                return "[Didn't have permission to include file]"
1271+            else:
1272+                return '' # Fail silently for invalid includes.
1273+        try:
1274+            fp = open(self.filepath, 'r')
1275+            output = fp.read()
1276+            fp.close()
1277+        except IOError:
1278+            output = ''
1279+        if self.parsed:
1280+            try:
1281+                t = Template(output, name=self.filepath)
1282+                return t.render(context)
1283+            except TemplateSyntaxError, e:
1284+                if settings.DEBUG:
1285+                    return "[Included template had syntax error: %s]" % e
1286+                else:
1287+                    return '' # Fail silently for invalid included templates.
1288+        return output
1289+
1290+class LoadNode(Node):
1291+    def render(self, context):
1292+        return ''
1293+
1294+class NowNode(Node):
1295+    def __init__(self, format_string):
1296+        self.format_string = format_string
1297+
1298+    def render(self, context):
1299+        from datetime import datetime
1300+        from django.utils.dateformat import DateFormat
1301+        df = DateFormat(datetime.now())
1302+        return df.format(self.format_string)
1303+
1304+class SpacelessNode(Node):
1305+    def __init__(self, nodelist):
1306+        self.nodelist = nodelist
1307+
1308+    def render(self, context):
1309+        from django.utils.html import strip_spaces_between_tags
1310+        return strip_spaces_between_tags(self.nodelist.render(context).strip())
1311+
1312+class TemplateTagNode(Node):
1313+    mapping = {'openblock': BLOCK_TAG_START,
1314+               'closeblock': BLOCK_TAG_END,
1315+               'openvariable': VARIABLE_TAG_START,
1316+               'closevariable': VARIABLE_TAG_END,
1317+               'openbrace': SINGLE_BRACE_START,
1318+               'closebrace': SINGLE_BRACE_END,
1319+               'opencomment': COMMENT_TAG_START,
1320+               'closecomment': COMMENT_TAG_END,
1321+               }
1322+
1323+    def __init__(self, tagtype):
1324+        self.tagtype = tagtype
1325+
1326+    def render(self, context):
1327+        return self.mapping.get(self.tagtype, '')
1328+
1329+class URLNode(Node):
1330+    def __init__(self, view_name, args, kwargs):
1331+        self.view_name = view_name
1332+        self.args = args
1333+        self.kwargs = kwargs
1334+
1335+    def render(self, context):
1336+        from django.core.urlresolvers import reverse, NoReverseMatch
1337+        args = [arg.resolve(context) for arg in self.args]
1338+        kwargs = dict([(smart_str(k,'ascii'), v.resolve(context))
1339+                       for k, v in self.kwargs.items()])
1340+        try:
1341+            return reverse(self.view_name, args=args, kwargs=kwargs)
1342+        except NoReverseMatch:
1343+            try:
1344+                project_name = settings.SETTINGS_MODULE.split('.')[0]
1345+                return reverse(project_name + '.' + self.view_name,
1346+                               args=args, kwargs=kwargs)
1347+            except NoReverseMatch:
1348+                return ''
1349+
1350+class WidthRatioNode(Node):
1351+    def __init__(self, val_expr, max_expr, max_width):
1352+        self.val_expr = val_expr
1353+        self.max_expr = max_expr
1354+        self.max_width = max_width
1355+
1356+    def render(self, context):
1357+        try:
1358+            value = self.val_expr.resolve(context)
1359+            maxvalue = self.max_expr.resolve(context)
1360+        except VariableDoesNotExist:
1361+            return ''
1362+        try:
1363+            value = float(value)
1364+            maxvalue = float(maxvalue)
1365+            ratio = (value / maxvalue) * int(self.max_width)
1366+        except (ValueError, ZeroDivisionError):
1367+            return ''
1368+        return str(int(round(ratio)))
1369+
1370+class WithNode(Node):
1371+    def __init__(self, var, name, nodelist):
1372+        self.var = var
1373+        self.name = name
1374+        self.nodelist = nodelist
1375+
1376+    def __repr__(self):
1377+        return "<WithNode>"
1378+
1379+    def render(self, context):
1380+        val = self.var.resolve(context)
1381+        context.push()
1382+        context[self.name] = val
1383+        output = self.nodelist.render(context)
1384+        context.pop()
1385+        return output
1386+
1387+#@register.tag
1388+def autoescape(parser, token):
1389+    """
1390+    Force autoescape behaviour for this block.
1391+    """
1392+    args = token.contents.split()
1393+    if len(args) != 2:
1394+        raise TemplateSyntaxError("'Autoescape' tag requires exactly one argument.")
1395+    arg = args[1]
1396+    if arg not in (u'on', u'off'):
1397+        raise TemplateSyntaxError("'Autoescape' argument should be 'on' or 'off'")
1398+    nodelist = parser.parse(('endautoescape',))
1399+    parser.delete_first_token()
1400+    return AutoEscapeControlNode((arg == 'on'), nodelist)
1401+autoescape = register.tag(autoescape)
1402+
1403+#@register.tag
1404+def comment(parser, token):
1405+    """
1406+    Ignores everything between ``{% comment %}`` and ``{% endcomment %}``.
1407+    """
1408+    parser.skip_past('endcomment')
1409+    return CommentNode()
1410+comment = register.tag(comment)
1411+
1412+#@register.tag
1413+def cycle(parser, token):
1414+    """
1415+    Cycles among the given strings each time this tag is encountered.
1416+
1417+    Within a loop, cycles among the given strings each time through
1418+    the loop::
1419+
1420+        {% for o in some_list %}
1421+            <tr class="{% cycle 'row1' 'row2' %}">
1422+                ...
1423+            </tr>
1424+        {% endfor %}
1425+
1426+    Outside of a loop, give the values a unique name the first time you call
1427+    it, then use that name each sucessive time through::
1428+
1429+            <tr class="{% cycle 'row1' 'row2' 'row3' as rowcolors %}">...</tr>
1430+            <tr class="{% cycle rowcolors %}">...</tr>
1431+            <tr class="{% cycle rowcolors %}">...</tr>
1432+
1433+    You can use any number of values, separated by spaces. Commas can also
1434+    be used to separate values; if a comma is used, the cycle values are
1435+    interpreted as literal strings.
1436+    """
1437+
1438+    # Note: This returns the exact same node on each {% cycle name %} call;
1439+    # that is, the node object returned from {% cycle a b c as name %} and the
1440+    # one returned from {% cycle name %} are the exact same object. This
1441+    # shouldn't cause problems (heh), but if it does, now you know.
1442+    #
1443+    # Ugly hack warning: This stuffs the named template dict into parser so
1444+    # that names are only unique within each template (as opposed to using
1445+    # a global variable, which would make cycle names have to be unique across
1446+    # *all* templates.
1447+
1448+    args = token.split_contents()
1449+
1450+    if len(args) < 2:
1451+        raise TemplateSyntaxError("'cycle' tag requires at least two arguments")
1452+
1453+    if ',' in args[1]:
1454+        # Backwards compatibility: {% cycle a,b %} or {% cycle a,b as foo %}
1455+        # case.
1456+        args[1:2] = ['"%s"' % arg for arg in args[1].split(",")]
1457+
1458+    if len(args) == 2:
1459+        # {% cycle foo %} case.
1460+        name = args[1]
1461+        if not hasattr(parser, '_namedCycleNodes'):
1462+            raise TemplateSyntaxError("No named cycles in template. '%s' is not defined" % name)
1463+        if not name in parser._namedCycleNodes:
1464+            raise TemplateSyntaxError("Named cycle '%s' does not exist" % name)
1465+        return parser._namedCycleNodes[name]
1466+
1467+    if len(args) > 4 and args[-2] == 'as':
1468+        name = args[-1]
1469+        node = CycleNode(args[1:-2], name)
1470+        if not hasattr(parser, '_namedCycleNodes'):
1471+            parser._namedCycleNodes = {}
1472+        parser._namedCycleNodes[name] = node
1473+    else:
1474+        node = CycleNode(args[1:])
1475+    return node
1476+cycle = register.tag(cycle)
1477+
1478+def debug(parser, token):
1479+    """
1480+    Outputs a whole load of debugging information, including the current
1481+    context and imported modules.
1482+
1483+    Sample usage::
1484+
1485+        <pre>
1486+            {% debug %}
1487+        </pre>
1488+    """
1489+    return DebugNode()
1490+debug = register.tag(debug)
1491+
1492+#@register.tag(name="filter")
1493+def do_filter(parser, token):
1494+    """
1495+    Filters the contents of the block through variable filters.
1496+
1497+    Filters can also be piped through each other, and they can have
1498+    arguments -- just like in variable syntax.
1499+
1500+    Sample usage::
1501+
1502+        {% filter force_escape|lower %}
1503+            This text will be HTML-escaped, and will appear in lowercase.
1504+        {% endfilter %}
1505+    """
1506+    _, rest = token.contents.split(None, 1)
1507+    filter_expr = parser.compile_filter("var|%s" % (rest))
1508+    for func, unused in filter_expr.filters:
1509+        if getattr(func, '_decorated_function', func).__name__ in ('escape', 'safe'):
1510+            raise TemplateSyntaxError('"filter %s" is not permitted.  Use the "autoescape" tag instead.' % func.__name__)
1511+    nodelist = parser.parse(('endfilter',))
1512+    parser.delete_first_token()
1513+    return FilterNode(filter_expr, nodelist)
1514+do_filter = register.tag("filter", do_filter)
1515+
1516+#@register.tag
1517+def firstof(parser, token):
1518+    """
1519+    Outputs the first variable passed that is not False.
1520+
1521+    Outputs nothing if all the passed variables are False.
1522+
1523+    Sample usage::
1524+
1525+        {% firstof var1 var2 var3 %}
1526+
1527+    This is equivalent to::
1528+
1529+        {% if var1 %}
1530+            {{ var1 }}
1531+        {% else %}{% if var2 %}
1532+            {{ var2 }}
1533+        {% else %}{% if var3 %}
1534+            {{ var3 }}
1535+        {% endif %}{% endif %}{% endif %}
1536+
1537+    but obviously much cleaner!
1538+
1539+    You can also use a literal string as a fallback value in case all
1540+    passed variables are False::
1541+
1542+        {% firstof var1 var2 var3 "fallback value" %}
1543+
1544+    """
1545+    bits = token.split_contents()[1:]
1546+    if len(bits) < 1:
1547+        raise TemplateSyntaxError("'firstof' statement requires at least one"
1548+                                  " argument")
1549+    return FirstOfNode(bits)
1550+firstof = register.tag(firstof)
1551+
1552+#@register.tag(name="for")
1553+def do_for(parser, token):
1554+    """
1555+    Loops over each item in an array.
1556+
1557+    For example, to display a list of athletes given ``athlete_list``::
1558+
1559+        <ul>
1560+        {% for athlete in athlete_list %}
1561+            <li>{{ athlete.name }}</li>
1562+        {% endfor %}
1563+        </ul>
1564+
1565+    You can loop over a list in reverse by using
1566+    ``{% for obj in list reversed %}``.
1567+
1568+    You can also unpack multiple values from a two-dimensional array::
1569+
1570+        {% for key,value in dict.items %}
1571+            {{ key }}: {{ value }}
1572+        {% endfor %}
1573+
1574+    The for loop sets a number of variables available within the loop:
1575+
1576+        ==========================  ================================================
1577+        Variable                    Description
1578+        ==========================  ================================================
1579+        ``forloop.counter``         The current iteration of the loop (1-indexed)
1580+        ``forloop.counter0``        The current iteration of the loop (0-indexed)
1581+        ``forloop.revcounter``      The number of iterations from the end of the
1582+                                    loop (1-indexed)
1583+        ``forloop.revcounter0``     The number of iterations from the end of the
1584+                                    loop (0-indexed)
1585+        ``forloop.first``           True if this is the first time through the loop
1586+        ``forloop.last``            True if this is the last time through the loop
1587+        ``forloop.parentloop``      For nested loops, this is the loop "above" the
1588+                                    current one
1589+        ==========================  ================================================
1590+
1591+    """
1592+    bits = token.contents.split()
1593+    if len(bits) < 4:
1594+        raise TemplateSyntaxError("'for' statements should have at least four"
1595+                                  " words: %s" % token.contents)
1596+
1597+    is_reversed = bits[-1] == 'reversed'
1598+    in_index = is_reversed and -3 or -2
1599+    if bits[in_index] != 'in':
1600+        raise TemplateSyntaxError("'for' statements should use the format"
1601+                                  " 'for x in y': %s" % token.contents)
1602+
1603+    loopvars = re.sub(r' *, *', ',', ' '.join(bits[1:in_index])).split(',')
1604+    for var in loopvars:
1605+        if not var or ' ' in var:
1606+            raise TemplateSyntaxError("'for' tag received an invalid argument:"
1607+                                      " %s" % token.contents)
1608+
1609+    sequence = parser.compile_filter(bits[in_index+1])
1610+    nodelist_loop = parser.parse(('endfor',))
1611+    parser.delete_first_token()
1612+    return ForNode(loopvars, sequence, is_reversed, nodelist_loop)
1613+do_for = register.tag("for", do_for)
1614+
1615+def do_ifequal(parser, token, negate):
1616+    bits = list(token.split_contents())
1617+    if len(bits) != 3:
1618+        raise TemplateSyntaxError, "%r takes two arguments" % bits[0]
1619+    end_tag = 'end' + bits[0]
1620+    nodelist_true = parser.parse(('else', end_tag))
1621+    token = parser.next_token()
1622+    if token.contents == 'else':
1623+        nodelist_false = parser.parse((end_tag,))
1624+        parser.delete_first_token()
1625+    else:
1626+        nodelist_false = NodeList()
1627+    return IfEqualNode(bits[1], bits[2], nodelist_true, nodelist_false, negate)
1628+
1629+#@register.tag
1630+def ifequal(parser, token):
1631+    """
1632+    Outputs the contents of the block if the two arguments equal each other.
1633+
1634+    Examples::
1635+
1636+        {% ifequal user.id comment.user_id %}
1637+            ...
1638+        {% endifequal %}
1639+
1640+        {% ifnotequal user.id comment.user_id %}
1641+            ...
1642+        {% else %}
1643+            ...
1644+        {% endifnotequal %}
1645+    """
1646+    return do_ifequal(parser, token, False)
1647+ifequal = register.tag(ifequal)
1648+
1649+#@register.tag
1650+def ifnotequal(parser, token):
1651+    """
1652+    Outputs the contents of the block if the two arguments are not equal.
1653+    See ifequal.
1654+    """
1655+    return do_ifequal(parser, token, True)
1656+ifnotequal = register.tag(ifnotequal)
1657+
1658+#@register.tag(name="if")
1659+def do_if(parser, token):
1660+    """
1661+    The ``{% if %}`` tag evaluates a variable, and if that variable is "true"
1662+    (i.e., exists, is not empty, and is not a false boolean value), the
1663+    contents of the block are output:
1664+
1665+    ::
1666+
1667+        {% if athlete_list %}
1668+            Number of athletes: {{ athlete_list|count }}
1669+        {% else %}
1670+            No athletes.
1671+        {% endif %}
1672+
1673+    In the above, if ``athlete_list`` is not empty, the number of athletes will
1674+    be displayed by the ``{{ athlete_list|count }}`` variable.
1675+
1676+    As you can see, the ``if`` tag can take an option ``{% else %}`` clause
1677+    that will be displayed if the test fails.
1678+
1679+    ``if`` tags may use ``or``, ``and`` or ``not`` to test a number of
1680+    variables or to negate a given variable::
1681+
1682+        {% if not athlete_list %}
1683+            There are no athletes.
1684+        {% endif %}
1685+
1686+        {% if athlete_list or coach_list %}
1687+            There are some athletes or some coaches.
1688+        {% endif %}
1689+
1690+        {% if athlete_list and coach_list %}
1691+            Both atheletes and coaches are available.
1692+        {% endif %}
1693+
1694+        {% if not athlete_list or coach_list %}
1695+            There are no athletes, or there are some coaches.
1696+        {% endif %}
1697+
1698+        {% if athlete_list and not coach_list %}
1699+            There are some athletes and absolutely no coaches.
1700+        {% endif %}
1701+
1702+    ``if`` tags do not allow ``and`` and ``or`` clauses with the same tag,
1703+    because the order of logic would be ambigous. For example, this is
1704+    invalid::
1705+
1706+        {% if athlete_list and coach_list or cheerleader_list %}
1707+
1708+    If you need to combine ``and`` and ``or`` to do advanced logic, just use
1709+    nested if tags. For example::
1710+
1711+        {% if athlete_list %}
1712+            {% if coach_list or cheerleader_list %}
1713+                We have athletes, and either coaches or cheerleaders!
1714+            {% endif %}
1715+        {% endif %}
1716+    """
1717+    bits = token.contents.split()
1718+    del bits[0]
1719+    if not bits:
1720+        raise TemplateSyntaxError("'if' statement requires at least one argument")
1721+    # Bits now looks something like this: ['a', 'or', 'not', 'b', 'or', 'c.d']
1722+    bitstr = ' '.join(bits)
1723+    boolpairs = bitstr.split(' and ')
1724+    boolvars = []
1725+    if len(boolpairs) == 1:
1726+        link_type = IfNode.LinkTypes.or_
1727+        boolpairs = bitstr.split(' or ')
1728+    else:
1729+        link_type = IfNode.LinkTypes.and_
1730+        if ' or ' in bitstr:
1731+            raise TemplateSyntaxError, "'if' tags can't mix 'and' and 'or'"
1732+    for boolpair in boolpairs:
1733+        if ' ' in boolpair:
1734+            try:
1735+                not_, boolvar = boolpair.split()
1736+            except ValueError:
1737+                raise TemplateSyntaxError, "'if' statement improperly formatted"
1738+            if not_ != 'not':
1739+                raise TemplateSyntaxError, "Expected 'not' in if statement"
1740+            boolvars.append((True, parser.compile_filter(boolvar)))
1741+        else:
1742+            boolvars.append((False, parser.compile_filter(boolpair)))
1743+    nodelist_true = parser.parse(('else', 'endif'))
1744+    token = parser.next_token()
1745+    if token.contents == 'else':
1746+        nodelist_false = parser.parse(('endif',))
1747+        parser.delete_first_token()
1748+    else:
1749+        nodelist_false = NodeList()
1750+    return IfNode(boolvars, nodelist_true, nodelist_false, link_type)
1751+do_if = register.tag("if", do_if)
1752+
1753+#@register.tag
1754+def ifchanged(parser, token):
1755+    """
1756+    Checks if a value has changed from the last iteration of a loop.
1757+
1758+    The 'ifchanged' block tag is used within a loop. It has two possible uses.
1759+
1760+    1. Checks its own rendered contents against its previous state and only
1761+       displays the content if it has changed. For example, this displays a
1762+       list of days, only displaying the month if it changes::
1763+
1764+            <h1>Archive for {{ year }}</h1>
1765+
1766+            {% for date in days %}
1767+                {% ifchanged %}<h3>{{ date|date:"F" }}</h3>{% endifchanged %}
1768+                <a href="{{ date|date:"M/d"|lower }}/">{{ date|date:"j" }}</a>
1769+            {% endfor %}
1770+
1771+    2. If given a variable, check whether that variable has changed.
1772+       For example, the following shows the date every time it changes, but
1773+       only shows the hour if both the hour and the date have changed::
1774+
1775+            {% for date in days %}
1776+                {% ifchanged date.date %} {{ date.date }} {% endifchanged %}
1777+                {% ifchanged date.hour date.date %}
1778+                    {{ date.hour }}
1779+                {% endifchanged %}
1780+            {% endfor %}
1781+    """
1782+    bits = token.contents.split()
1783+    nodelist = parser.parse(('endifchanged',))
1784+    parser.delete_first_token()
1785+    return IfChangedNode(nodelist, *bits[1:])
1786+ifchanged = register.tag(ifchanged)
1787+
1788+#@register.tag
1789+def ssi(parser, token):
1790+    """
1791+    Outputs the contents of a given file into the page.
1792+
1793+    Like a simple "include" tag, the ``ssi`` tag includes the contents
1794+    of another file -- which must be specified using an absolute path --
1795+    in the current page::
1796+
1797+        {% ssi /home/html/ljworld.com/includes/right_generic.html %}
1798+
1799+    If the optional "parsed" parameter is given, the contents of the included
1800+    file are evaluated as template code, with the current context::
1801+
1802+        {% ssi /home/html/ljworld.com/includes/right_generic.html parsed %}
1803+    """
1804+    bits = token.contents.split()
1805+    parsed = False
1806+    if len(bits) not in (2, 3):
1807+        raise TemplateSyntaxError("'ssi' tag takes one argument: the path to"
1808+                                  " the file to be included")
1809+    if len(bits) == 3:
1810+        if bits[2] == 'parsed':
1811+            parsed = True
1812+        else:
1813+            raise TemplateSyntaxError("Second (optional) argument to %s tag"
1814+                                      " must be 'parsed'" % bits[0])
1815+    return SsiNode(bits[1], parsed)
1816+ssi = register.tag(ssi)
1817+
1818+#@register.tag
1819+def load(parser, token):
1820+    """
1821+    Loads a custom template tag set.
1822+
1823+    For example, to load the template tags in
1824+    ``django/templatetags/news/photos.py``::
1825+
1826+        {% load news.photos %}
1827+    """
1828+    bits = token.contents.split()
1829+    for taglib in bits[1:]:
1830+        # add the library to the parser
1831+        try:
1832+            lib = get_library(taglib)
1833+            parser.add_library(lib)
1834+        except InvalidTemplateLibrary, e:
1835+            raise TemplateSyntaxError("'%s' is not a valid tag library: %s" %
1836+                                      (taglib, e))
1837+    return LoadNode()
1838+load = register.tag(load)
1839+
1840+#@register.tag
1841+def now(parser, token):
1842+    """
1843+    Displays the date, formatted according to the given string.
1844+
1845+    Uses the same format as PHP's ``date()`` function; see http://php.net/date
1846+    for all the possible values.
1847+
1848+    Sample usage::
1849+
1850+        It is {% now "jS F Y H:i" %}
1851+    """
1852+    bits = token.contents.split('"')
1853+    if len(bits) != 3:
1854+        raise TemplateSyntaxError, "'now' statement takes one argument"
1855+    format_string = bits[1]
1856+    return NowNode(format_string)
1857+now = register.tag(now)
1858+
1859+#@register.tag
1860+def regroup(parser, token):
1861+    """
1862+    Regroups a list of alike objects by a common attribute.
1863+
1864+    This complex tag is best illustrated by use of an example:  say that
1865+    ``people`` is a list of ``Person`` objects that have ``first_name``,
1866+    ``last_name``, and ``gender`` attributes, and you'd like to display a list
1867+    that looks like:
1868+
1869+        * Male:
1870+            * George Bush
1871+            * Bill Clinton
1872+        * Female:
1873+            * Margaret Thatcher
1874+            * Colendeeza Rice
1875+        * Unknown:
1876+            * Pat Smith
1877+
1878+    The following snippet of template code would accomplish this dubious task::
1879+
1880+        {% regroup people by gender as grouped %}
1881+        <ul>
1882+        {% for group in grouped %}
1883+            <li>{{ group.grouper }}
1884+            <ul>
1885+                {% for item in group.list %}
1886+                <li>{{ item }}</li>
1887+                {% endfor %}
1888+            </ul>
1889+        {% endfor %}
1890+        </ul>
1891+
1892+    As you can see, ``{% regroup %}`` populates a variable with a list of
1893+    objects with ``grouper`` and ``list`` attributes.  ``grouper`` contains the
1894+    item that was grouped by; ``list`` contains the list of objects that share
1895+    that ``grouper``.  In this case, ``grouper`` would be ``Male``, ``Female``
1896+    and ``Unknown``, and ``list`` is the list of people with those genders.
1897+
1898+    Note that `{% regroup %}`` does not work when the list to be grouped is not
1899+    sorted by the key you are grouping by!  This means that if your list of
1900+    people was not sorted by gender, you'd need to make sure it is sorted
1901+    before using it, i.e.::
1902+
1903+        {% regroup people|dictsort:"gender" by gender as grouped %}
1904+
1905+    """
1906+    firstbits = token.contents.split(None, 3)
1907+    if len(firstbits) != 4:
1908+        raise TemplateSyntaxError, "'regroup' tag takes five arguments"
1909+    target = parser.compile_filter(firstbits[1])
1910+    if firstbits[2] != 'by':
1911+        raise TemplateSyntaxError("second argument to 'regroup' tag must be 'by'")
1912+    lastbits_reversed = firstbits[3][::-1].split(None, 2)
1913+    if lastbits_reversed[1][::-1] != 'as':
1914+        raise TemplateSyntaxError("next-to-last argument to 'regroup' tag must"
1915+                                  " be 'as'")
1916+
1917+    expression = parser.compile_filter(lastbits_reversed[2][::-1])
1918+
1919+    var_name = lastbits_reversed[0][::-1]
1920+    return RegroupNode(target, expression, var_name)
1921+regroup = register.tag(regroup)
1922+
1923+def spaceless(parser, token):
1924+    """
1925+    Removes whitespace between HTML tags, including tab and newline characters.
1926+
1927+    Example usage::
1928+
1929+        {% spaceless %}
1930+            <p>
1931+                <a href="foo/">Foo</a>
1932+            </p>
1933+        {% endspaceless %}
1934+
1935+    This example would return this HTML::
1936+
1937+        <p><a href="foo/">Foo</a></p>
1938+
1939+    Only space between *tags* is normalized -- not space between tags and text.
1940+    In this example, the space around ``Hello`` won't be stripped::
1941+
1942+        {% spaceless %}
1943+            <strong>
1944+                Hello
1945+            </strong>
1946+        {% endspaceless %}
1947+    """
1948+    nodelist = parser.parse(('endspaceless',))
1949+    parser.delete_first_token()
1950+    return SpacelessNode(nodelist)
1951+spaceless = register.tag(spaceless)
1952+
1953+#@register.tag
1954+def templatetag(parser, token):
1955+    """
1956+    Outputs one of the bits used to compose template tags.
1957+
1958+    Since the template system has no concept of "escaping", to display one of
1959+    the bits used in template tags, you must use the ``{% templatetag %}`` tag.
1960+
1961+    The argument tells which template bit to output:
1962+
1963+        ==================  =======
1964+        Argument            Outputs
1965+        ==================  =======
1966+        ``openblock``       ``{%``
1967+        ``closeblock``      ``%}``
1968+        ``openvariable``    ``{{``
1969+        ``closevariable``   ``}}``
1970+        ``openbrace``       ``{``
1971+        ``closebrace``      ``}``
1972+        ``opencomment``     ``{#``
1973+        ``closecomment``    ``#}``
1974+        ==================  =======
1975+    """
1976+    bits = token.contents.split()
1977+    if len(bits) != 2:
1978+        raise TemplateSyntaxError, "'templatetag' statement takes one argument"
1979+    tag = bits[1]
1980+    if tag not in TemplateTagNode.mapping:
1981+        raise TemplateSyntaxError("Invalid templatetag argument: '%s'."
1982+                                  " Must be one of: %s" %
1983+                                  (tag, TemplateTagNode.mapping.keys()))
1984+    return TemplateTagNode(tag)
1985+templatetag = register.tag(templatetag)
1986+
1987+def url(parser, token):
1988+    """
1989+    Returns an absolute URL matching given view with its parameters.
1990+
1991+    This is a way to define links that aren't tied to a particular URL
1992+    configuration::
1993+
1994+        {% url path.to.some_view arg1,arg2,name1=value1 %}
1995+
1996+    The first argument is a path to a view. It can be an absolute python path
1997+    or just ``app_name.view_name`` without the project name if the view is
1998+    located inside the project.  Other arguments are comma-separated values
1999+    that will be filled in place of positional and keyword arguments in the
2000+    URL. All arguments for the URL should be present.
2001+
2002+    For example if you have a view ``app_name.client`` taking client's id and
2003+    the corresponding line in a URLconf looks like this::
2004+
2005+        ('^client/(\d+)/$', 'app_name.client')
2006+
2007+    and this app's URLconf is included into the project's URLconf under some
2008+    path::
2009+
2010+        ('^clients/', include('project_name.app_name.urls'))
2011+
2012+    then in a template you can create a link for a certain client like this::
2013+
2014+        {% url app_name.client client.id %}
2015+
2016+    The URL will look like ``/clients/client/123/``.
2017+    """
2018+    bits = token.contents.split(' ', 2)
2019+    if len(bits) < 2:
2020+        raise TemplateSyntaxError("'%s' takes at least one argument"
2021+                                  " (path to a view)" % bits[0])
2022+    args = []
2023+    kwargs = {}
2024+    if len(bits) > 2:
2025+        for arg in bits[2].split(','):
2026+            if '=' in arg:
2027+                k, v = arg.split('=', 1)
2028+                k = k.strip()
2029+                kwargs[k] = parser.compile_filter(v)
2030+            else:
2031+                args.append(parser.compile_filter(arg))
2032+    return URLNode(bits[1], args, kwargs)
2033+url = register.tag(url)
2034+
2035+#@register.tag
2036+def widthratio(parser, token):
2037+    """
2038+    For creating bar charts and such, this tag calculates the ratio of a given
2039+    value to a maximum value, and then applies that ratio to a constant.
2040+
2041+    For example::
2042+
2043+        <img src='bar.gif' height='10' width='{% widthratio this_value max_value 100 %}' />
2044+
2045+    Above, if ``this_value`` is 175 and ``max_value`` is 200, the the image in
2046+    the above example will be 88 pixels wide (because 175/200 = .875;
2047+    .875 * 100 = 87.5 which is rounded up to 88).
2048+    """
2049+    bits = token.contents.split()
2050+    if len(bits) != 4:
2051+        raise TemplateSyntaxError("widthratio takes three arguments")
2052+    tag, this_value_expr, max_value_expr, max_width = bits
2053+    try:
2054+        max_width = int(max_width)
2055+    except ValueError:
2056+        raise TemplateSyntaxError("widthratio final argument must be an integer")
2057+    return WidthRatioNode(parser.compile_filter(this_value_expr),
2058+                          parser.compile_filter(max_value_expr), max_width)
2059+widthratio = register.tag(widthratio)
2060+
2061+#@register.tag
2062+def do_with(parser, token):
2063+    """
2064+    Adds a value to the context (inside of this block) for caching and easy
2065+    access.
2066+
2067+    For example::
2068+
2069+        {% with person.some_sql_method as total %}
2070+            {{ total }} object{{ total|pluralize }}
2071+        {% endwith %}
2072+    """
2073+    bits = list(token.split_contents())
2074+    if len(bits) != 4 or bits[2] != "as":
2075+        raise TemplateSyntaxError("%r expected format is 'value as name'" %
2076+                                  bits[0])
2077+    var = parser.compile_filter(bits[1])
2078+    name = bits[3]
2079+    nodelist = parser.parse(('endwith',))
2080+    parser.delete_first_token()
2081+    return WithNode(var, name, nodelist)
2082+do_with = register.tag('with', do_with)
2083diff --git a/tests/regressiontests/templates/tests.py b/tests/regressiontests/templates/tests.py
2084index 186b8aa..ac0912d 100644
2085--- a/tests/regressiontests/templates/tests.py
2086+++ b/tests/regressiontests/templates/tests.py
2087@@ -51,7 +51,7 @@ def do_echo(parser, token):
2088 
2089 register.tag("echo", do_echo)
2090 
2091-template.libraries['django.templatetags.testtags'] = register
2092+template.libraries['testtags'] = register
2093 
2094 #####################################
2095 # Helper objects for template tests #