Ticket #6587: new_template_lib_loader_14.diff

File new_template_lib_loader_14.diff, 135.0 KB (added by oyvind, 16 years ago)

Final patch, i hope :)

  • django/template/__init__.py

    diff --git a/django/template/__init__.py b/django/template/__init__.py
    index 5c4ab30..58755db 100644
    a b u'<html><h1>Hello</h1></html>'  
    4949u'<html></html>'
    5050"""
    5151import re
     52import imp
    5253from inspect import getargspec
    5354from django.conf import settings
    5455from django.template.context import Context, RequestContext, ContextPopException
    from django.utils.encoding import smart_unicode, force_unicode  
    5960from django.utils.translation import ugettext as _
    6061from django.utils.safestring import SafeData, EscapeData, mark_safe, mark_for_escaping
    6162from django.utils.html import escape
     63from django.templatetags import get_templatetags_modules
    6264
    6365__all__ = ('Template', 'Context', 'RequestContext', 'compile_string')
    6466
    class Library(object):  
    913915            return func
    914916        return dec
    915917
    916 def get_library(module_name):
    917     lib = libraries.get(module_name, None)
     918def import_library(taglib_module):
     919    components = taglib_module.split('.')
     920    parent_components = components[:-1]
     921    parent_module = '.'.join(parent_components)
     922    try:
     923        mod = __import__(parent_module, {}, {}, [parent_components[-1]])
     924        imp.find_module(components[-1], mod.__path__)
     925    except ImportError:
     926        return None
     927    mod = __import__(taglib_module, {}, {}, [components[-1]])
     928    try:
     929        return mod.register
     930    except AttributeError:
     931        raise InvalidTemplateLibrary("Template library %s does not have a variable named 'register'" % taglib_module)
     932
     933def get_library(library_name):
     934    lib = libraries.get(library_name, None)
    918935    if not lib:
    919         try:
    920             mod = __import__(module_name, {}, {}, [''])
    921         except ImportError, e:
    922             raise InvalidTemplateLibrary("Could not load template library from %s, %s" % (module_name, e))
    923         try:
    924             lib = mod.register
    925             libraries[module_name] = lib
    926         except AttributeError:
    927             raise InvalidTemplateLibrary("Template library %s does not have a variable named 'register'" % module_name)
     936
     937        """
     938        If library is not already loaded loop over all templatetags modules to locate it.
     939
     940        {% load somelib %} and {% load someotherlib %} loops twice.
     941
     942        Subsequent loads eg. {% load somelib %} in the same thread will grab the cached
     943        module from libraries.
     944        """
     945        templatetags_modules = get_templatetags_modules()
     946        tried_modules = []
     947        for module in templatetags_modules:
     948            taglib_module = str('%s.%s' % (module, library_name))
     949            tried_modules.append(taglib_module)
     950            lib = import_library(taglib_module)
     951            if lib:
     952                libraries[library_name] = lib
     953                break
     954        if not lib:
     955            raise InvalidTemplateLibrary("Template library %s not found, tried %s" % (library_name, ','.join(tried_modules)))
    928956    return lib
    929957
    930 def add_to_builtins(module_name):
    931     builtins.append(get_library(module_name))
     958def add_to_builtins(module):
     959    builtins.append(import_library(module))
    932960
    933 add_to_builtins('django.template.defaulttags')
    934 add_to_builtins('django.template.defaultfilters')
     961add_to_builtins('django.templatetags.defaulttags')
     962add_to_builtins('django.templatetags.defaultfilters')
  • deleted file django/template/defaultfilters.py

    diff --git a/django/template/defaultfilters.py b/django/template/defaultfilters.py
    deleted file mode 100644
    index cef3143..0000000
    + -  
    1 """Default variable filters."""
    2 
    3 import re
    4 import random as random_module
    5 try:
    6     from functools import wraps
    7 except ImportError:
    8     from django.utils.functional import wraps  # Python 2.3, 2.4 fallback.
    9 
    10 from django.template import Variable, Library
    11 from django.conf import settings
    12 from django.utils.translation import ugettext, ungettext
    13 from django.utils.encoding import force_unicode, iri_to_uri
    14 from django.utils.safestring import mark_safe, SafeData
    15 
    16 register = Library()
    17 
    18 #######################
    19 # STRING DECORATOR    #
    20 #######################
    21 
    22 def stringfilter(func):
    23     """
    24     Decorator for filters which should only receive unicode objects. The object
    25     passed as the first positional argument will be converted to a unicode
    26     object.
    27     """
    28     def _dec(*args, **kwargs):
    29         if args:
    30             args = list(args)
    31             args[0] = force_unicode(args[0])
    32             if isinstance(args[0], SafeData) and getattr(func, 'is_safe', False):
    33                 return mark_safe(func(*args, **kwargs))
    34         return func(*args, **kwargs)
    35 
    36     # Include a reference to the real function (used to check original
    37     # arguments by the template parser).
    38     _dec._decorated_function = getattr(func, '_decorated_function', func)
    39     for attr in ('is_safe', 'needs_autoescape'):
    40         if hasattr(func, attr):
    41             setattr(_dec, attr, getattr(func, attr))
    42     return wraps(func)(_dec)
    43 
    44 ###################
    45 # STRINGS         #
    46 ###################
    47 
    48 
    49 def addslashes(value):
    50     """
    51     Adds slashes before quotes. Useful for escaping strings in CSV, for
    52     example. Less useful for escaping JavaScript; use the ``escapejs``
    53     filter instead.
    54     """
    55     return value.replace('\\', '\\\\').replace('"', '\\"').replace("'", "\\'")
    56 addslashes.is_safe = True
    57 addslashes = stringfilter(addslashes)
    58 
    59 def capfirst(value):
    60     """Capitalizes the first character of the value."""
    61     return value and value[0].upper() + value[1:]
    62 capfirst.is_safe=True
    63 capfirst = stringfilter(capfirst)
    64 
    65 _js_escapes = (
    66     ('\\', '\\\\'),
    67     ('"', '\\"'),
    68     ("'", "\\'"),
    69     ('\n', '\\n'),
    70     ('\r', '\\r'),
    71     ('\b', '\\b'),
    72     ('\f', '\\f'),
    73     ('\t', '\\t'),
    74     ('\v', '\\v'),
    75     ('</', '<\\/'),
    76 )
    77 def escapejs(value):
    78     """Backslash-escapes characters for use in JavaScript strings."""
    79     for bad, good in _js_escapes:
    80         value = value.replace(bad, good)
    81     return value
    82 escapejs = stringfilter(escapejs)
    83 
    84 def fix_ampersands(value):
    85     """Replaces ampersands with ``&amp;`` entities."""
    86     from django.utils.html import fix_ampersands
    87     return fix_ampersands(value)
    88 fix_ampersands.is_safe=True
    89 fix_ampersands = stringfilter(fix_ampersands)
    90 
    91 def floatformat(text, arg=-1):
    92     """
    93     Displays a float to a specified number of decimal places.
    94 
    95     If called without an argument, it displays the floating point number with
    96     one decimal place -- but only if there's a decimal place to be displayed:
    97 
    98     * num1 = 34.23234
    99     * num2 = 34.00000
    100     * num3 = 34.26000
    101     * {{ num1|floatformat }} displays "34.2"
    102     * {{ num2|floatformat }} displays "34"
    103     * {{ num3|floatformat }} displays "34.3"
    104 
    105     If arg is positive, it will always display exactly arg number of decimal
    106     places:
    107 
    108     * {{ num1|floatformat:3 }} displays "34.232"
    109     * {{ num2|floatformat:3 }} displays "34.000"
    110     * {{ num3|floatformat:3 }} displays "34.260"
    111 
    112     If arg is negative, it will display arg number of decimal places -- but
    113     only if there are places to be displayed:
    114 
    115     * {{ num1|floatformat:"-3" }} displays "34.232"
    116     * {{ num2|floatformat:"-3" }} displays "34"
    117     * {{ num3|floatformat:"-3" }} displays "34.260"
    118     """
    119     try:
    120         f = float(text)
    121     except (ValueError, TypeError):
    122         return u''
    123     try:
    124         d = int(arg)
    125     except ValueError:
    126         return force_unicode(f)
    127     try:
    128         m = f - int(f)
    129     except OverflowError:
    130         return force_unicode(f)
    131     if not m and d < 0:
    132         return mark_safe(u'%d' % int(f))
    133     else:
    134         formatstr = u'%%.%df' % abs(d)
    135         return mark_safe(formatstr % f)
    136 floatformat.is_safe = True
    137 
    138 def iriencode(value):
    139     """Escapes an IRI value for use in a URL."""
    140     return force_unicode(iri_to_uri(value))
    141 iriencode.is_safe = True
    142 iriencode = stringfilter(iriencode)
    143 
    144 def linenumbers(value, autoescape=None):
    145     """Displays text with line numbers."""
    146     from django.utils.html import escape
    147     lines = value.split(u'\n')
    148     # Find the maximum width of the line count, for use with zero padding
    149     # string format command
    150     width = unicode(len(unicode(len(lines))))
    151     if not autoescape or isinstance(value, SafeData):
    152         for i, line in enumerate(lines):
    153             lines[i] = (u"%0" + width  + u"d. %s") % (i + 1, line)
    154     else:
    155         for i, line in enumerate(lines):
    156             lines[i] = (u"%0" + width  + u"d. %s") % (i + 1, escape(line))
    157     return mark_safe(u'\n'.join(lines))
    158 linenumbers.is_safe = True
    159 linenumbers.needs_autoescape = True
    160 linenumbers = stringfilter(linenumbers)
    161 
    162 def lower(value):
    163     """Converts a string into all lowercase."""
    164     return value.lower()
    165 lower.is_safe = True
    166 lower = stringfilter(lower)
    167 
    168 def make_list(value):
    169     """
    170     Returns the value turned into a list.
    171 
    172     For an integer, it's a list of digits.
    173     For a string, it's a list of characters.
    174     """
    175     return list(value)
    176 make_list.is_safe = False
    177 make_list = stringfilter(make_list)
    178 
    179 def slugify(value):
    180     """
    181     Normalizes string, converts to lowercase, removes non-alpha characters,
    182     and converts spaces to hyphens.
    183     """
    184     import unicodedata
    185     value = unicodedata.normalize('NFKD', value).encode('ascii', 'ignore')
    186     value = unicode(re.sub('[^\w\s-]', '', value).strip().lower())
    187     return mark_safe(re.sub('[-\s]+', '-', value))
    188 slugify.is_safe = True
    189 slugify = stringfilter(slugify)
    190 
    191 def stringformat(value, arg):
    192     """
    193     Formats the variable according to the arg, a string formatting specifier.
    194 
    195     This specifier uses Python string formating syntax, with the exception that
    196     the leading "%" is dropped.
    197 
    198     See http://docs.python.org/lib/typesseq-strings.html for documentation
    199     of Python string formatting
    200     """
    201     try:
    202         return (u"%" + unicode(arg)) % value
    203     except (ValueError, TypeError):
    204         return u""
    205 stringformat.is_safe = True
    206 
    207 def title(value):
    208     """Converts a string into titlecase."""
    209     return re.sub("([a-z])'([A-Z])", lambda m: m.group(0).lower(), value.title())
    210 title.is_safe = True
    211 title = stringfilter(title)
    212 
    213 def truncatewords(value, arg):
    214     """
    215     Truncates a string after a certain number of words.
    216 
    217     Argument: Number of words to truncate after.
    218     """
    219     from django.utils.text import truncate_words
    220     try:
    221         length = int(arg)
    222     except ValueError: # Invalid literal for int().
    223         return value # Fail silently.
    224     return truncate_words(value, length)
    225 truncatewords.is_safe = True
    226 truncatewords = stringfilter(truncatewords)
    227 
    228 def truncatewords_html(value, arg):
    229     """
    230     Truncates HTML after a certain number of words.
    231 
    232     Argument: Number of words to truncate after.
    233     """
    234     from django.utils.text import truncate_html_words
    235     try:
    236         length = int(arg)
    237     except ValueError: # invalid literal for int()
    238         return value # Fail silently.
    239     return truncate_html_words(value, length)
    240 truncatewords_html.is_safe = True
    241 truncatewords_html = stringfilter(truncatewords_html)
    242 
    243 def upper(value):
    244     """Converts a string into all uppercase."""
    245     return value.upper()
    246 upper.is_safe = False
    247 upper = stringfilter(upper)
    248 
    249 def urlencode(value):
    250     """Escapes a value for use in a URL."""
    251     from django.utils.http import urlquote
    252     return urlquote(value)
    253 urlencode.is_safe = False
    254 urlencode = stringfilter(urlencode)
    255 
    256 def urlize(value, autoescape=None):
    257     """Converts URLs in plain text into clickable links."""
    258     from django.utils.html import urlize
    259     return mark_safe(urlize(value, nofollow=True, autoescape=autoescape))
    260 urlize.is_safe=True
    261 urlize.needs_autoescape = True
    262 urlize = stringfilter(urlize)
    263 
    264 def urlizetrunc(value, limit, autoescape=None):
    265     """
    266     Converts URLs into clickable links, truncating URLs to the given character
    267     limit, and adding 'rel=nofollow' attribute to discourage spamming.
    268 
    269     Argument: Length to truncate URLs to.
    270     """
    271     from django.utils.html import urlize
    272     return mark_safe(urlize(value, trim_url_limit=int(limit), nofollow=True,
    273                             autoescape=autoescape))
    274 urlizetrunc.is_safe = True
    275 urlizetrunc.needs_autoescape = True
    276 urlizetrunc = stringfilter(urlizetrunc)
    277 
    278 def wordcount(value):
    279     """Returns the number of words."""
    280     return len(value.split())
    281 wordcount.is_safe = False
    282 wordcount = stringfilter(wordcount)
    283 
    284 def wordwrap(value, arg):
    285     """
    286     Wraps words at specified line length.
    287 
    288     Argument: number of characters to wrap the text at.
    289     """
    290     from django.utils.text import wrap
    291     return wrap(value, int(arg))
    292 wordwrap.is_safe = True
    293 wordwrap = stringfilter(wordwrap)
    294 
    295 def ljust(value, arg):
    296     """
    297     Left-aligns the value in a field of a given width.
    298 
    299     Argument: field size.
    300     """
    301     return value.ljust(int(arg))
    302 ljust.is_safe = True
    303 ljust = stringfilter(ljust)
    304 
    305 def rjust(value, arg):
    306     """
    307     Right-aligns the value in a field of a given width.
    308 
    309     Argument: field size.
    310     """
    311     return value.rjust(int(arg))
    312 rjust.is_safe = True
    313 rjust = stringfilter(rjust)
    314 
    315 def center(value, arg):
    316     """Centers the value in a field of a given width."""
    317     return value.center(int(arg))
    318 center.is_safe = True
    319 center = stringfilter(center)
    320 
    321 def cut(value, arg):
    322     """
    323     Removes all values of arg from the given string.
    324     """
    325     safe = isinstance(value, SafeData)
    326     value = value.replace(arg, u'')
    327     if safe and arg != ';':
    328         return mark_safe(value)
    329     return value
    330 cut = stringfilter(cut)
    331 
    332 ###################
    333 # HTML STRINGS    #
    334 ###################
    335 
    336 def escape(value):
    337     """
    338     Marks the value as a string that should not be auto-escaped.
    339     """
    340     from django.utils.safestring import mark_for_escaping
    341     return mark_for_escaping(value)
    342 escape.is_safe = True
    343 escape = stringfilter(escape)
    344 
    345 def force_escape(value):
    346     """
    347     Escapes a string's HTML. This returns a new string containing the escaped
    348     characters (as opposed to "escape", which marks the content for later
    349     possible escaping).
    350     """
    351     from django.utils.html import escape
    352     return mark_safe(escape(value))
    353 force_escape = stringfilter(force_escape)
    354 force_escape.is_safe = True
    355 
    356 def linebreaks(value, autoescape=None):
    357     """
    358     Replaces line breaks in plain text with appropriate HTML; a single
    359     newline becomes an HTML line break (``<br />``) and a new line
    360     followed by a blank line becomes a paragraph break (``</p>``).
    361     """
    362     from django.utils.html import linebreaks
    363     autoescape = autoescape and not isinstance(value, SafeData)
    364     return mark_safe(linebreaks(value, autoescape))
    365 linebreaks.is_safe = True
    366 linebreaks.needs_autoescape = True
    367 linebreaks = stringfilter(linebreaks)
    368 
    369 def linebreaksbr(value, autoescape=None):
    370     """
    371     Converts all newlines in a piece of plain text to HTML line breaks
    372     (``<br />``).
    373     """
    374     if autoescape and not isinstance(value, SafeData):
    375         from django.utils.html import escape
    376         value = escape(value)
    377     return mark_safe(value.replace('\n', '<br />'))
    378 linebreaksbr.is_safe = True
    379 linebreaksbr.needs_autoescape = True
    380 linebreaksbr = stringfilter(linebreaksbr)
    381 
    382 def safe(value):
    383     """
    384     Marks the value as a string that should not be auto-escaped.
    385     """
    386     from django.utils.safestring import mark_safe
    387     return mark_safe(value)
    388 safe.is_safe = True
    389 safe = stringfilter(safe)
    390 
    391 def removetags(value, tags):
    392     """Removes a space separated list of [X]HTML tags from the output."""
    393     tags = [re.escape(tag) for tag in tags.split()]
    394     tags_re = u'(%s)' % u'|'.join(tags)
    395     starttag_re = re.compile(ur'<%s(/?>|(\s+[^>]*>))' % tags_re, re.U)
    396     endtag_re = re.compile(u'</%s>' % tags_re)
    397     value = starttag_re.sub(u'', value)
    398     value = endtag_re.sub(u'', value)
    399     return value
    400 removetags.is_safe = True
    401 removetags = stringfilter(removetags)
    402 
    403 def striptags(value):
    404     """Strips all [X]HTML tags."""
    405     from django.utils.html import strip_tags
    406     return strip_tags(value)
    407 striptags.is_safe = True
    408 striptags = stringfilter(striptags)
    409 
    410 ###################
    411 # LISTS           #
    412 ###################
    413 
    414 def dictsort(value, arg):
    415     """
    416     Takes a list of dicts, returns that list sorted by the property given in
    417     the argument.
    418     """
    419     var_resolve = Variable(arg).resolve
    420     decorated = [(var_resolve(item), item) for item in value]
    421     decorated.sort()
    422     return [item[1] for item in decorated]
    423 dictsort.is_safe = False
    424 
    425 def dictsortreversed(value, arg):
    426     """
    427     Takes a list of dicts, returns that list sorted in reverse order by the
    428     property given in the argument.
    429     """
    430     var_resolve = Variable(arg).resolve
    431     decorated = [(var_resolve(item), item) for item in value]
    432     decorated.sort()
    433     decorated.reverse()
    434     return [item[1] for item in decorated]
    435 dictsortreversed.is_safe = False
    436 
    437 def first(value):
    438     """Returns the first item in a list."""
    439     try:
    440         return value[0]
    441     except IndexError:
    442         return u''
    443 first.is_safe = False
    444 
    445 def join(value, arg):
    446     """Joins a list with a string, like Python's ``str.join(list)``."""
    447     try:
    448         data = arg.join(map(force_unicode, value))
    449     except AttributeError: # fail silently but nicely
    450         return value
    451     safe_args = reduce(lambda lhs, rhs: lhs and isinstance(rhs, SafeData),
    452             value, True)
    453     if safe_args:
    454         return mark_safe(data)
    455     else:
    456         return data
    457 join.is_safe = True
    458 
    459 def last(value):
    460     "Returns the last item in a list"
    461     try:
    462         return value[-1]
    463     except IndexError:
    464         return u''
    465 last.is_safe = True
    466 
    467 def length(value):
    468     """Returns the length of the value - useful for lists."""
    469     return len(value)
    470 length.is_safe = True
    471 
    472 def length_is(value, arg):
    473     """Returns a boolean of whether the value's length is the argument."""
    474     return len(value) == int(arg)
    475 length_is.is_safe = True
    476 
    477 def random(value):
    478     """Returns a random item from the list."""
    479     return random_module.choice(value)
    480 random.is_safe = True
    481 
    482 def slice_(value, arg):
    483     """
    484     Returns a slice of the list.
    485 
    486     Uses the same syntax as Python's list slicing; see
    487     http://diveintopython.org/native_data_types/lists.html#odbchelper.list.slice
    488     for an introduction.
    489     """
    490     try:
    491         bits = []
    492         for x in arg.split(u':'):
    493             if len(x) == 0:
    494                 bits.append(None)
    495             else:
    496                 bits.append(int(x))
    497         return value[slice(*bits)]
    498 
    499     except (ValueError, TypeError):
    500         return value # Fail silently.
    501 slice_.is_safe = True
    502 
    503 def unordered_list(value, autoescape=None):
    504     """
    505     Recursively takes a self-nested list and returns an HTML unordered list --
    506     WITHOUT opening and closing <ul> tags.
    507 
    508     The list is assumed to be in the proper format. For example, if ``var``
    509     contains: ``['States', ['Kansas', ['Lawrence', 'Topeka'], 'Illinois']]``,
    510     then ``{{ var|unordered_list }}`` would return::
    511 
    512         <li>States
    513         <ul>
    514                 <li>Kansas
    515                 <ul>
    516                         <li>Lawrence</li>
    517                         <li>Topeka</li>
    518                 </ul>
    519                 </li>
    520                 <li>Illinois</li>
    521         </ul>
    522         </li>
    523     """
    524     if autoescape:
    525         from django.utils.html import conditional_escape
    526         escaper = conditional_escape
    527     else:
    528         escaper = lambda x: x
    529     def convert_old_style_list(list_):
    530         """
    531         Converts old style lists to the new easier to understand format.
    532 
    533         The old list format looked like:
    534             ['Item 1', [['Item 1.1', []], ['Item 1.2', []]]
    535 
    536         And it is converted to:
    537             ['Item 1', ['Item 1.1', 'Item 1.2]]
    538         """
    539         if not isinstance(list_, (tuple, list)) or len(list_) != 2:
    540             return list_, False
    541         first_item, second_item = list_
    542         if second_item == []:
    543             return [first_item], True
    544         old_style_list = True
    545         new_second_item = []
    546         for sublist in second_item:
    547             item, old_style_list = convert_old_style_list(sublist)
    548             if not old_style_list:
    549                 break
    550             new_second_item.extend(item)
    551         if old_style_list:
    552             second_item = new_second_item
    553         return [first_item, second_item], old_style_list
    554     def _helper(list_, tabs=1):
    555         indent = u'\t' * tabs
    556         output = []
    557 
    558         list_length = len(list_)
    559         i = 0
    560         while i < list_length:
    561             title = list_[i]
    562             sublist = ''
    563             sublist_item = None
    564             if isinstance(title, (list, tuple)):
    565                 sublist_item = title
    566                 title = ''
    567             elif i < list_length - 1:
    568                 next_item = list_[i+1]
    569                 if next_item and isinstance(next_item, (list, tuple)):
    570                     # The next item is a sub-list.
    571                     sublist_item = next_item
    572                     # We've processed the next item now too.
    573                     i += 1
    574             if sublist_item:
    575                 sublist = _helper(sublist_item, tabs+1)
    576                 sublist = '\n%s<ul>\n%s\n%s</ul>\n%s' % (indent, sublist,
    577                                                          indent, indent)
    578             output.append('%s<li>%s%s</li>' % (indent,
    579                     escaper(force_unicode(title)), sublist))
    580             i += 1
    581         return '\n'.join(output)
    582     value, converted = convert_old_style_list(value)
    583     return mark_safe(_helper(value))
    584 unordered_list.is_safe = True
    585 unordered_list.needs_autoescape = True
    586 
    587 ###################
    588 # INTEGERS        #
    589 ###################
    590 
    591 def add(value, arg):
    592     """Adds the arg to the value."""
    593     return int(value) + int(arg)
    594 add.is_safe = False
    595 
    596 def get_digit(value, arg):
    597     """
    598     Given a whole number, returns the requested digit of it, where 1 is the
    599     right-most digit, 2 is the second-right-most digit, etc. Returns the
    600     original value for invalid input (if input or argument is not an integer,
    601     or if argument is less than 1). Otherwise, output is always an integer.
    602     """
    603     try:
    604         arg = int(arg)
    605         value = int(value)
    606     except ValueError:
    607         return value # Fail silently for an invalid argument
    608     if arg < 1:
    609         return value
    610     try:
    611         return int(str(value)[-arg])
    612     except IndexError:
    613         return 0
    614 get_digit.is_safe = False
    615 
    616 ###################
    617 # DATES           #
    618 ###################
    619 
    620 def date(value, arg=None):
    621     """Formats a date according to the given format."""
    622     from django.utils.dateformat import format
    623     if not value:
    624         return u''
    625     if arg is None:
    626         arg = settings.DATE_FORMAT
    627     return format(value, arg)
    628 date.is_safe = False
    629 
    630 def time(value, arg=None):
    631     """Formats a time according to the given format."""
    632     from django.utils.dateformat import time_format
    633     if value in (None, u''):
    634         return u''
    635     if arg is None:
    636         arg = settings.TIME_FORMAT
    637     return time_format(value, arg)
    638 time.is_safe = False
    639 
    640 def timesince(value, arg=None):
    641     """Formats a date as the time since that date (i.e. "4 days, 6 hours")."""
    642     from django.utils.timesince import timesince
    643     if not value:
    644         return u''
    645     if arg:
    646         return timesince(arg, value)
    647     return timesince(value)
    648 timesince.is_safe = False
    649 
    650 def timeuntil(value, arg=None):
    651     """Formats a date as the time until that date (i.e. "4 days, 6 hours")."""
    652     from django.utils.timesince import timesince
    653     from datetime import datetime
    654     if not value:
    655         return u''
    656     if arg:
    657         return timesince(arg, value)
    658     return timesince(datetime.now(), value)
    659 timeuntil.is_safe = False
    660 
    661 ###################
    662 # LOGIC           #
    663 ###################
    664 
    665 def default(value, arg):
    666     """If value is unavailable, use given default."""
    667     return value or arg
    668 default.is_safe = False
    669 
    670 def default_if_none(value, arg):
    671     """If value is None, use given default."""
    672     if value is None:
    673         return arg
    674     return value
    675 default_if_none.is_safe = False
    676 
    677 def divisibleby(value, arg):
    678     """Returns True if the value is devisible by the argument."""
    679     return int(value) % int(arg) == 0
    680 divisibleby.is_safe = False
    681 
    682 def yesno(value, arg=None):
    683     """
    684     Given a string mapping values for true, false and (optionally) None,
    685     returns one of those strings accoding to the value:
    686 
    687     ==========  ======================  ==================================
    688     Value       Argument                Outputs
    689     ==========  ======================  ==================================
    690     ``True``    ``"yeah,no,maybe"``     ``yeah``
    691     ``False``   ``"yeah,no,maybe"``     ``no``
    692     ``None``    ``"yeah,no,maybe"``     ``maybe``
    693     ``None``    ``"yeah,no"``           ``"no"`` (converts None to False
    694                                         if no mapping for None is given.
    695     ==========  ======================  ==================================
    696     """
    697     if arg is None:
    698         arg = ugettext('yes,no,maybe')
    699     bits = arg.split(u',')
    700     if len(bits) < 2:
    701         return value # Invalid arg.
    702     try:
    703         yes, no, maybe = bits
    704     except ValueError:
    705         # Unpack list of wrong size (no "maybe" value provided).
    706         yes, no, maybe = bits[0], bits[1], bits[1]
    707     if value is None:
    708         return maybe
    709     if value:
    710         return yes
    711     return no
    712 yesno.is_safe = False
    713 
    714 ###################
    715 # MISC            #
    716 ###################
    717 
    718 def filesizeformat(bytes):
    719     """
    720     Formats the value like a 'human-readable' file size (i.e. 13 KB, 4.1 MB,
    721     102 bytes, etc).
    722     """
    723     try:
    724         bytes = float(bytes)
    725     except TypeError:
    726         return u"0 bytes"
    727 
    728     if bytes < 1024:
    729         return ungettext("%(size)d byte", "%(size)d bytes", bytes) % {'size': bytes}
    730     if bytes < 1024 * 1024:
    731         return ugettext("%.1f KB") % (bytes / 1024)
    732     if bytes < 1024 * 1024 * 1024:
    733         return ugettext("%.1f MB") % (bytes / (1024 * 1024))
    734     return ugettext("%.1f GB") % (bytes / (1024 * 1024 * 1024))
    735 filesizeformat.is_safe = True
    736 
    737 def pluralize(value, arg=u's'):
    738     """
    739     Returns a plural suffix if the value is not 1. By default, 's' is used as
    740     the suffix:
    741 
    742     * If value is 0, vote{{ value|pluralize }} displays "0 votes".
    743     * If value is 1, vote{{ value|pluralize }} displays "1 vote".
    744     * If value is 2, vote{{ value|pluralize }} displays "2 votes".
    745 
    746     If an argument is provided, that string is used instead:
    747 
    748     * If value is 0, class{{ value|pluralize:"es" }} displays "0 classes".
    749     * If value is 1, class{{ value|pluralize:"es" }} displays "1 class".
    750     * If value is 2, class{{ value|pluralize:"es" }} displays "2 classes".
    751 
    752     If the provided argument contains a comma, the text before the comma is
    753     used for the singular case and the text after the comma is used for the
    754     plural case:
    755 
    756     * If value is 0, cand{{ value|pluralize:"y,ies" }} displays "0 candies".
    757     * If value is 1, cand{{ value|pluralize:"y,ies" }} displays "1 candy".
    758     * If value is 2, cand{{ value|pluralize:"y,ies" }} displays "2 candies".
    759     """
    760     if not u',' in arg:
    761         arg = u',' + arg
    762     bits = arg.split(u',')
    763     if len(bits) > 2:
    764         return u''
    765     singular_suffix, plural_suffix = bits[:2]
    766 
    767     try:
    768         if int(value) != 1:
    769             return plural_suffix
    770     except ValueError: # Invalid string that's not a number.
    771         pass
    772     except TypeError: # Value isn't a string or a number; maybe it's a list?
    773         try:
    774             if len(value) != 1:
    775                 return plural_suffix
    776         except TypeError: # len() of unsized object.
    777             pass
    778     return singular_suffix
    779 pluralize.is_safe = False
    780 
    781 def phone2numeric(value):
    782     """Takes a phone number and converts it in to its numerical equivalent."""
    783     from django.utils.text import phone2numeric
    784     return phone2numeric(value)
    785 phone2numeric.is_safe = True
    786 
    787 def pprint(value):
    788     """A wrapper around pprint.pprint -- for debugging, really."""
    789     from pprint import pformat
    790     try:
    791         return pformat(value)
    792     except Exception, e:
    793         return u"Error in formatting: %s" % force_unicode(e, errors="replace")
    794 pprint.is_safe = True
    795 
    796 # Syntax: register.filter(name of filter, callback)
    797 register.filter(add)
    798 register.filter(addslashes)
    799 register.filter(capfirst)
    800 register.filter(center)
    801 register.filter(cut)
    802 register.filter(date)
    803 register.filter(default)
    804 register.filter(default_if_none)
    805 register.filter(dictsort)
    806 register.filter(dictsortreversed)
    807 register.filter(divisibleby)
    808 register.filter(escape)
    809 register.filter(escapejs)
    810 register.filter(filesizeformat)
    811 register.filter(first)
    812 register.filter(fix_ampersands)
    813 register.filter(floatformat)
    814 register.filter(force_escape)
    815 register.filter(get_digit)
    816 register.filter(iriencode)
    817 register.filter(join)
    818 register.filter(last)
    819 register.filter(length)
    820 register.filter(length_is)
    821 register.filter(linebreaks)
    822 register.filter(linebreaksbr)
    823 register.filter(linenumbers)
    824 register.filter(ljust)
    825 register.filter(lower)
    826 register.filter(make_list)
    827 register.filter(phone2numeric)
    828 register.filter(pluralize)
    829 register.filter(pprint)
    830 register.filter(removetags)
    831 register.filter(random)
    832 register.filter(rjust)
    833 register.filter(safe)
    834 register.filter('slice', slice_)
    835 register.filter(slugify)
    836 register.filter(stringformat)
    837 register.filter(striptags)
    838 register.filter(time)
    839 register.filter(timesince)
    840 register.filter(timeuntil)
    841 register.filter(title)
    842 register.filter(truncatewords)
    843 register.filter(truncatewords_html)
    844 register.filter(unordered_list)
    845 register.filter(upper)
    846 register.filter(urlencode)
    847 register.filter(urlize)
    848 register.filter(urlizetrunc)
    849 register.filter(wordcount)
    850 register.filter(wordwrap)
    851 register.filter(yesno)
  • deleted file django/template/defaulttags.py

    diff --git a/django/template/defaulttags.py b/django/template/defaulttags.py
    deleted file mode 100644
    index 01c43ee..0000000
    + -  
    1 """Default tags used by the template system, available to all templates."""
    2 
    3 import sys
    4 import re
    5 from itertools import cycle as itertools_cycle
    6 try:
    7     reversed
    8 except NameError:
    9     from django.utils.itercompat import reversed     # Python 2.3 fallback
    10 
    11 from django.template import Node, NodeList, Template, Context, Variable
    12 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
    13 from django.template import get_library, Library, InvalidTemplateLibrary
    14 from django.conf import settings
    15 from django.utils.encoding import smart_str, smart_unicode
    16 from django.utils.itercompat import groupby
    17 from django.utils.safestring import mark_safe
    18 
    19 register = Library()
    20 
    21 class AutoEscapeControlNode(Node):
    22     """Implements the actions of the autoescape tag."""
    23     def __init__(self, setting, nodelist):
    24         self.setting, self.nodelist = setting, nodelist
    25 
    26     def render(self, context):
    27         old_setting = context.autoescape
    28         context.autoescape = self.setting
    29         output = self.nodelist.render(context)
    30         context.autoescape = old_setting
    31         if self.setting:
    32             return mark_safe(output)
    33         else:
    34             return output
    35 
    36 class CommentNode(Node):
    37     def render(self, context):
    38         return ''
    39 
    40 class CycleNode(Node):
    41     def __init__(self, cyclevars, variable_name=None):
    42         self.cycle_iter = itertools_cycle([Variable(v) for v in cyclevars])
    43         self.variable_name = variable_name
    44 
    45     def render(self, context):
    46         value = self.cycle_iter.next().resolve(context)
    47         if self.variable_name:
    48             context[self.variable_name] = value
    49         return value
    50 
    51 class DebugNode(Node):
    52     def render(self, context):
    53         from pprint import pformat
    54         output = [pformat(val) for val in context]
    55         output.append('\n\n')
    56         output.append(pformat(sys.modules))
    57         return ''.join(output)
    58 
    59 class FilterNode(Node):
    60     def __init__(self, filter_expr, nodelist):
    61         self.filter_expr, self.nodelist = filter_expr, nodelist
    62 
    63     def render(self, context):
    64         output = self.nodelist.render(context)
    65         # Apply filters.
    66         context.update({'var': output})
    67         filtered = self.filter_expr.resolve(context)
    68         context.pop()
    69         return filtered
    70 
    71 class FirstOfNode(Node):
    72     def __init__(self, vars):
    73         self.vars = map(Variable, vars)
    74 
    75     def render(self, context):
    76         for var in self.vars:
    77             try:
    78                 value = var.resolve(context)
    79             except VariableDoesNotExist:
    80                 continue
    81             if value:
    82                 return smart_unicode(value)
    83         return u''
    84 
    85 class ForNode(Node):
    86     def __init__(self, loopvars, sequence, is_reversed, nodelist_loop):
    87         self.loopvars, self.sequence = loopvars, sequence
    88         self.is_reversed = is_reversed
    89         self.nodelist_loop = nodelist_loop
    90 
    91     def __repr__(self):
    92         reversed_text = self.is_reversed and ' reversed' or ''
    93         return "<For Node: for %s in %s, tail_len: %d%s>" % \
    94             (', '.join(self.loopvars), self.sequence, len(self.nodelist_loop),
    95              reversed_text)
    96 
    97     def __iter__(self):
    98         for node in self.nodelist_loop:
    99             yield node
    100 
    101     def get_nodes_by_type(self, nodetype):
    102         nodes = []
    103         if isinstance(self, nodetype):
    104             nodes.append(self)
    105         nodes.extend(self.nodelist_loop.get_nodes_by_type(nodetype))
    106         return nodes
    107 
    108     def render(self, context):
    109         nodelist = NodeList()
    110         if 'forloop' in context:
    111             parentloop = context['forloop']
    112         else:
    113             parentloop = {}
    114         context.push()
    115         try:
    116             values = self.sequence.resolve(context, True)
    117         except VariableDoesNotExist:
    118             values = []
    119         if values is None:
    120             values = []
    121         if not hasattr(values, '__len__'):
    122             values = list(values)
    123         len_values = len(values)
    124         if self.is_reversed:
    125             values = reversed(values)
    126         unpack = len(self.loopvars) > 1
    127         # Create a forloop value in the context.  We'll update counters on each
    128         # iteration just below.
    129         loop_dict = context['forloop'] = {'parentloop': parentloop}
    130         for i, item in enumerate(values):
    131             # Shortcuts for current loop iteration number.
    132             loop_dict['counter0'] = i
    133             loop_dict['counter'] = i+1
    134             # Reverse counter iteration numbers.
    135             loop_dict['revcounter'] = len_values - i
    136             loop_dict['revcounter0'] = len_values - i - 1
    137             # Boolean values designating first and last times through loop.
    138             loop_dict['first'] = (i == 0)
    139             loop_dict['last'] = (i == len_values - 1)
    140 
    141             if unpack:
    142                 # If there are multiple loop variables, unpack the item into
    143                 # them.
    144                 context.update(dict(zip(self.loopvars, item)))
    145             else:
    146                 context[self.loopvars[0]] = item
    147             for node in self.nodelist_loop:
    148                 nodelist.append(node.render(context))
    149             if unpack:
    150                 # The loop variables were pushed on to the context so pop them
    151                 # off again. This is necessary because the tag lets the length
    152                 # of loopvars differ to the length of each set of items and we
    153                 # don't want to leave any vars from the previous loop on the
    154                 # context.
    155                 context.pop()
    156         context.pop()
    157         return nodelist.render(context)
    158 
    159 class IfChangedNode(Node):
    160     def __init__(self, nodelist, *varlist):
    161         self.nodelist = nodelist
    162         self._last_seen = None
    163         self._varlist = map(Variable, varlist)
    164         self._id = str(id(self))
    165 
    166     def render(self, context):
    167         if 'forloop' in context and self._id not in context['forloop']:
    168             self._last_seen = None
    169             context['forloop'][self._id] = 1
    170         try:
    171             if self._varlist:
    172                 # Consider multiple parameters.  This automatically behaves
    173                 # like an OR evaluation of the multiple variables.
    174                 compare_to = [var.resolve(context) for var in self._varlist]
    175             else:
    176                 compare_to = self.nodelist.render(context)
    177         except VariableDoesNotExist:
    178             compare_to = None
    179 
    180         if  compare_to != self._last_seen:
    181             firstloop = (self._last_seen == None)
    182             self._last_seen = compare_to
    183             context.push()
    184             context['ifchanged'] = {'firstloop': firstloop}
    185             content = self.nodelist.render(context)
    186             context.pop()
    187             return content
    188         else:
    189             return ''
    190 
    191 class IfEqualNode(Node):
    192     def __init__(self, var1, var2, nodelist_true, nodelist_false, negate):
    193         self.var1, self.var2 = Variable(var1), Variable(var2)
    194         self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false
    195         self.negate = negate
    196 
    197     def __repr__(self):
    198         return "<IfEqualNode>"
    199 
    200     def render(self, context):
    201         try:
    202             val1 = self.var1.resolve(context)
    203         except VariableDoesNotExist:
    204             val1 = None
    205         try:
    206             val2 = self.var2.resolve(context)
    207         except VariableDoesNotExist:
    208             val2 = None
    209         if (self.negate and val1 != val2) or (not self.negate and val1 == val2):
    210             return self.nodelist_true.render(context)
    211         return self.nodelist_false.render(context)
    212 
    213 class IfNode(Node):
    214     def __init__(self, bool_exprs, nodelist_true, nodelist_false, link_type):
    215         self.bool_exprs = bool_exprs
    216         self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false
    217         self.link_type = link_type
    218 
    219     def __repr__(self):
    220         return "<If node>"
    221 
    222     def __iter__(self):
    223         for node in self.nodelist_true:
    224             yield node
    225         for node in self.nodelist_false:
    226             yield node
    227 
    228     def get_nodes_by_type(self, nodetype):
    229         nodes = []
    230         if isinstance(self, nodetype):
    231             nodes.append(self)
    232         nodes.extend(self.nodelist_true.get_nodes_by_type(nodetype))
    233         nodes.extend(self.nodelist_false.get_nodes_by_type(nodetype))
    234         return nodes
    235 
    236     def render(self, context):
    237         if self.link_type == IfNode.LinkTypes.or_:
    238             for ifnot, bool_expr in self.bool_exprs:
    239                 try:
    240                     value = bool_expr.resolve(context, True)
    241                 except VariableDoesNotExist:
    242                     value = None
    243                 if (value and not ifnot) or (ifnot and not value):
    244                     return self.nodelist_true.render(context)
    245             return self.nodelist_false.render(context)
    246         else:
    247             for ifnot, bool_expr in self.bool_exprs:
    248                 try:
    249                     value = bool_expr.resolve(context, True)
    250                 except VariableDoesNotExist:
    251                     value = None
    252                 if not ((value and not ifnot) or (ifnot and not value)):
    253                     return self.nodelist_false.render(context)
    254             return self.nodelist_true.render(context)
    255 
    256     class LinkTypes:
    257         and_ = 0,
    258         or_ = 1
    259 
    260 class RegroupNode(Node):
    261     def __init__(self, target, expression, var_name):
    262         self.target, self.expression = target, expression
    263         self.var_name = var_name
    264 
    265     def render(self, context):
    266         obj_list = self.target.resolve(context, True)
    267         if obj_list == None:
    268             # target variable wasn't found in context; fail silently.
    269             context[self.var_name] = []
    270             return ''
    271         # List of dictionaries in the format:
    272         # {'grouper': 'key', 'list': [list of contents]}.
    273         context[self.var_name] = [
    274             {'grouper': key, 'list': list(val)}
    275             for key, val in
    276             groupby(obj_list, lambda v, f=self.expression.resolve: f(v, True))
    277         ]
    278         return ''
    279 
    280 def include_is_allowed(filepath):
    281     for root in settings.ALLOWED_INCLUDE_ROOTS:
    282         if filepath.startswith(root):
    283             return True
    284     return False
    285 
    286 class SsiNode(Node):
    287     def __init__(self, filepath, parsed):
    288         self.filepath, self.parsed = filepath, parsed
    289 
    290     def render(self, context):
    291         if not include_is_allowed(self.filepath):
    292             if settings.DEBUG:
    293                 return "[Didn't have permission to include file]"
    294             else:
    295                 return '' # Fail silently for invalid includes.
    296         try:
    297             fp = open(self.filepath, 'r')
    298             output = fp.read()
    299             fp.close()
    300         except IOError:
    301             output = ''
    302         if self.parsed:
    303             try:
    304                 t = Template(output, name=self.filepath)
    305                 return t.render(context)
    306             except TemplateSyntaxError, e:
    307                 if settings.DEBUG:
    308                     return "[Included template had syntax error: %s]" % e
    309                 else:
    310                     return '' # Fail silently for invalid included templates.
    311         return output
    312 
    313 class LoadNode(Node):
    314     def render(self, context):
    315         return ''
    316 
    317 class NowNode(Node):
    318     def __init__(self, format_string):
    319         self.format_string = format_string
    320 
    321     def render(self, context):
    322         from datetime import datetime
    323         from django.utils.dateformat import DateFormat
    324         df = DateFormat(datetime.now())
    325         return df.format(self.format_string)
    326 
    327 class SpacelessNode(Node):
    328     def __init__(self, nodelist):
    329         self.nodelist = nodelist
    330 
    331     def render(self, context):
    332         from django.utils.html import strip_spaces_between_tags
    333         return strip_spaces_between_tags(self.nodelist.render(context).strip())
    334 
    335 class TemplateTagNode(Node):
    336     mapping = {'openblock': BLOCK_TAG_START,
    337                'closeblock': BLOCK_TAG_END,
    338                'openvariable': VARIABLE_TAG_START,
    339                'closevariable': VARIABLE_TAG_END,
    340                'openbrace': SINGLE_BRACE_START,
    341                'closebrace': SINGLE_BRACE_END,
    342                'opencomment': COMMENT_TAG_START,
    343                'closecomment': COMMENT_TAG_END,
    344                }
    345 
    346     def __init__(self, tagtype):
    347         self.tagtype = tagtype
    348 
    349     def render(self, context):
    350         return self.mapping.get(self.tagtype, '')
    351 
    352 class URLNode(Node):
    353     def __init__(self, view_name, args, kwargs):
    354         self.view_name = view_name
    355         self.args = args
    356         self.kwargs = kwargs
    357 
    358     def render(self, context):
    359         from django.core.urlresolvers import reverse, NoReverseMatch
    360         args = [arg.resolve(context) for arg in self.args]
    361         kwargs = dict([(smart_str(k,'ascii'), v.resolve(context))
    362                        for k, v in self.kwargs.items()])
    363         try:
    364             return reverse(self.view_name, args=args, kwargs=kwargs)
    365         except NoReverseMatch:
    366             try:
    367                 project_name = settings.SETTINGS_MODULE.split('.')[0]
    368                 return reverse(project_name + '.' + self.view_name,
    369                                args=args, kwargs=kwargs)
    370             except NoReverseMatch:
    371                 return ''
    372 
    373 class WidthRatioNode(Node):
    374     def __init__(self, val_expr, max_expr, max_width):
    375         self.val_expr = val_expr
    376         self.max_expr = max_expr
    377         self.max_width = max_width
    378 
    379     def render(self, context):
    380         try:
    381             value = self.val_expr.resolve(context)
    382             maxvalue = self.max_expr.resolve(context)
    383         except VariableDoesNotExist:
    384             return ''
    385         try:
    386             value = float(value)
    387             maxvalue = float(maxvalue)
    388             ratio = (value / maxvalue) * int(self.max_width)
    389         except (ValueError, ZeroDivisionError):
    390             return ''
    391         return str(int(round(ratio)))
    392 
    393 class WithNode(Node):
    394     def __init__(self, var, name, nodelist):
    395         self.var = var
    396         self.name = name
    397         self.nodelist = nodelist
    398 
    399     def __repr__(self):
    400         return "<WithNode>"
    401 
    402     def render(self, context):
    403         val = self.var.resolve(context)
    404         context.push()
    405         context[self.name] = val
    406         output = self.nodelist.render(context)
    407         context.pop()
    408         return output
    409 
    410 #@register.tag
    411 def autoescape(parser, token):
    412     """
    413     Force autoescape behaviour for this block.
    414     """
    415     args = token.contents.split()
    416     if len(args) != 2:
    417         raise TemplateSyntaxError("'Autoescape' tag requires exactly one argument.")
    418     arg = args[1]
    419     if arg not in (u'on', u'off'):
    420         raise TemplateSyntaxError("'Autoescape' argument should be 'on' or 'off'")
    421     nodelist = parser.parse(('endautoescape',))
    422     parser.delete_first_token()
    423     return AutoEscapeControlNode((arg == 'on'), nodelist)
    424 autoescape = register.tag(autoescape)
    425 
    426 #@register.tag
    427 def comment(parser, token):
    428     """
    429     Ignores everything between ``{% comment %}`` and ``{% endcomment %}``.
    430     """
    431     parser.skip_past('endcomment')
    432     return CommentNode()
    433 comment = register.tag(comment)
    434 
    435 #@register.tag
    436 def cycle(parser, token):
    437     """
    438     Cycles among the given strings each time this tag is encountered.
    439 
    440     Within a loop, cycles among the given strings each time through
    441     the loop::
    442 
    443         {% for o in some_list %}
    444             <tr class="{% cycle 'row1' 'row2' %}">
    445                 ...
    446             </tr>
    447         {% endfor %}
    448 
    449     Outside of a loop, give the values a unique name the first time you call
    450     it, then use that name each sucessive time through::
    451 
    452             <tr class="{% cycle 'row1' 'row2' 'row3' as rowcolors %}">...</tr>
    453             <tr class="{% cycle rowcolors %}">...</tr>
    454             <tr class="{% cycle rowcolors %}">...</tr>
    455 
    456     You can use any number of values, separated by spaces. Commas can also
    457     be used to separate values; if a comma is used, the cycle values are
    458     interpreted as literal strings.
    459     """
    460 
    461     # Note: This returns the exact same node on each {% cycle name %} call;
    462     # that is, the node object returned from {% cycle a b c as name %} and the
    463     # one returned from {% cycle name %} are the exact same object. This
    464     # shouldn't cause problems (heh), but if it does, now you know.
    465     #
    466     # Ugly hack warning: This stuffs the named template dict into parser so
    467     # that names are only unique within each template (as opposed to using
    468     # a global variable, which would make cycle names have to be unique across
    469     # *all* templates.
    470 
    471     args = token.split_contents()
    472 
    473     if len(args) < 2:
    474         raise TemplateSyntaxError("'cycle' tag requires at least two arguments")
    475 
    476     if ',' in args[1]:
    477         # Backwards compatibility: {% cycle a,b %} or {% cycle a,b as foo %}
    478         # case.
    479         args[1:2] = ['"%s"' % arg for arg in args[1].split(",")]
    480 
    481     if len(args) == 2:
    482         # {% cycle foo %} case.
    483         name = args[1]
    484         if not hasattr(parser, '_namedCycleNodes'):
    485             raise TemplateSyntaxError("No named cycles in template. '%s' is not defined" % name)
    486         if not name in parser._namedCycleNodes:
    487             raise TemplateSyntaxError("Named cycle '%s' does not exist" % name)
    488         return parser._namedCycleNodes[name]
    489 
    490     if len(args) > 4 and args[-2] == 'as':
    491         name = args[-1]
    492         node = CycleNode(args[1:-2], name)
    493         if not hasattr(parser, '_namedCycleNodes'):
    494             parser._namedCycleNodes = {}
    495         parser._namedCycleNodes[name] = node
    496     else:
    497         node = CycleNode(args[1:])
    498     return node
    499 cycle = register.tag(cycle)
    500 
    501 def debug(parser, token):
    502     """
    503     Outputs a whole load of debugging information, including the current
    504     context and imported modules.
    505 
    506     Sample usage::
    507 
    508         <pre>
    509             {% debug %}
    510         </pre>
    511     """
    512     return DebugNode()
    513 debug = register.tag(debug)
    514 
    515 #@register.tag(name="filter")
    516 def do_filter(parser, token):
    517     """
    518     Filters the contents of the block through variable filters.
    519 
    520     Filters can also be piped through each other, and they can have
    521     arguments -- just like in variable syntax.
    522 
    523     Sample usage::
    524 
    525         {% filter force_escape|lower %}
    526             This text will be HTML-escaped, and will appear in lowercase.
    527         {% endfilter %}
    528     """
    529     _, rest = token.contents.split(None, 1)
    530     filter_expr = parser.compile_filter("var|%s" % (rest))
    531     for func, unused in filter_expr.filters:
    532         if getattr(func, '_decorated_function', func).__name__ in ('escape', 'safe'):
    533             raise TemplateSyntaxError('"filter %s" is not permitted.  Use the "autoescape" tag instead.' % func.__name__)
    534     nodelist = parser.parse(('endfilter',))
    535     parser.delete_first_token()
    536     return FilterNode(filter_expr, nodelist)
    537 do_filter = register.tag("filter", do_filter)
    538 
    539 #@register.tag
    540 def firstof(parser, token):
    541     """
    542     Outputs the first variable passed that is not False.
    543 
    544     Outputs nothing if all the passed variables are False.
    545 
    546     Sample usage::
    547 
    548         {% firstof var1 var2 var3 %}
    549 
    550     This is equivalent to::
    551 
    552         {% if var1 %}
    553             {{ var1 }}
    554         {% else %}{% if var2 %}
    555             {{ var2 }}
    556         {% else %}{% if var3 %}
    557             {{ var3 }}
    558         {% endif %}{% endif %}{% endif %}
    559 
    560     but obviously much cleaner!
    561 
    562     You can also use a literal string as a fallback value in case all
    563     passed variables are False::
    564 
    565         {% firstof var1 var2 var3 "fallback value" %}
    566 
    567     """
    568     bits = token.split_contents()[1:]
    569     if len(bits) < 1:
    570         raise TemplateSyntaxError("'firstof' statement requires at least one"
    571                                   " argument")
    572     return FirstOfNode(bits)
    573 firstof = register.tag(firstof)
    574 
    575 #@register.tag(name="for")
    576 def do_for(parser, token):
    577     """
    578     Loops over each item in an array.
    579 
    580     For example, to display a list of athletes given ``athlete_list``::
    581 
    582         <ul>
    583         {% for athlete in athlete_list %}
    584             <li>{{ athlete.name }}</li>
    585         {% endfor %}
    586         </ul>
    587 
    588     You can loop over a list in reverse by using
    589     ``{% for obj in list reversed %}``.
    590 
    591     You can also unpack multiple values from a two-dimensional array::
    592 
    593         {% for key,value in dict.items %}
    594             {{ key }}: {{ value }}
    595         {% endfor %}
    596 
    597     The for loop sets a number of variables available within the loop:
    598 
    599         ==========================  ================================================
    600         Variable                    Description
    601         ==========================  ================================================
    602         ``forloop.counter``         The current iteration of the loop (1-indexed)
    603         ``forloop.counter0``        The current iteration of the loop (0-indexed)
    604         ``forloop.revcounter``      The number of iterations from the end of the
    605                                     loop (1-indexed)
    606         ``forloop.revcounter0``     The number of iterations from the end of the
    607                                     loop (0-indexed)
    608         ``forloop.first``           True if this is the first time through the loop
    609         ``forloop.last``            True if this is the last time through the loop
    610         ``forloop.parentloop``      For nested loops, this is the loop "above" the
    611                                     current one
    612         ==========================  ================================================
    613 
    614     """
    615     bits = token.contents.split()
    616     if len(bits) < 4:
    617         raise TemplateSyntaxError("'for' statements should have at least four"
    618                                   " words: %s" % token.contents)
    619 
    620     is_reversed = bits[-1] == 'reversed'
    621     in_index = is_reversed and -3 or -2
    622     if bits[in_index] != 'in':
    623         raise TemplateSyntaxError("'for' statements should use the format"
    624                                   " 'for x in y': %s" % token.contents)
    625 
    626     loopvars = re.sub(r' *, *', ',', ' '.join(bits[1:in_index])).split(',')
    627     for var in loopvars:
    628         if not var or ' ' in var:
    629             raise TemplateSyntaxError("'for' tag received an invalid argument:"
    630                                       " %s" % token.contents)
    631 
    632     sequence = parser.compile_filter(bits[in_index+1])
    633     nodelist_loop = parser.parse(('endfor',))
    634     parser.delete_first_token()
    635     return ForNode(loopvars, sequence, is_reversed, nodelist_loop)
    636 do_for = register.tag("for", do_for)
    637 
    638 def do_ifequal(parser, token, negate):
    639     bits = list(token.split_contents())
    640     if len(bits) != 3:
    641         raise TemplateSyntaxError, "%r takes two arguments" % bits[0]
    642     end_tag = 'end' + bits[0]
    643     nodelist_true = parser.parse(('else', end_tag))
    644     token = parser.next_token()
    645     if token.contents == 'else':
    646         nodelist_false = parser.parse((end_tag,))
    647         parser.delete_first_token()
    648     else:
    649         nodelist_false = NodeList()
    650     return IfEqualNode(bits[1], bits[2], nodelist_true, nodelist_false, negate)
    651 
    652 #@register.tag
    653 def ifequal(parser, token):
    654     """
    655     Outputs the contents of the block if the two arguments equal each other.
    656 
    657     Examples::
    658 
    659         {% ifequal user.id comment.user_id %}
    660             ...
    661         {% endifequal %}
    662 
    663         {% ifnotequal user.id comment.user_id %}
    664             ...
    665         {% else %}
    666             ...
    667         {% endifnotequal %}
    668     """
    669     return do_ifequal(parser, token, False)
    670 ifequal = register.tag(ifequal)
    671 
    672 #@register.tag
    673 def ifnotequal(parser, token):
    674     """
    675     Outputs the contents of the block if the two arguments are not equal.
    676     See ifequal.
    677     """
    678     return do_ifequal(parser, token, True)
    679 ifnotequal = register.tag(ifnotequal)
    680 
    681 #@register.tag(name="if")
    682 def do_if(parser, token):
    683     """
    684     The ``{% if %}`` tag evaluates a variable, and if that variable is "true"
    685     (i.e., exists, is not empty, and is not a false boolean value), the
    686     contents of the block are output:
    687 
    688     ::
    689 
    690         {% if athlete_list %}
    691             Number of athletes: {{ athlete_list|count }}
    692         {% else %}
    693             No athletes.
    694         {% endif %}
    695 
    696     In the above, if ``athlete_list`` is not empty, the number of athletes will
    697     be displayed by the ``{{ athlete_list|count }}`` variable.
    698 
    699     As you can see, the ``if`` tag can take an option ``{% else %}`` clause
    700     that will be displayed if the test fails.
    701 
    702     ``if`` tags may use ``or``, ``and`` or ``not`` to test a number of
    703     variables or to negate a given variable::
    704 
    705         {% if not athlete_list %}
    706             There are no athletes.
    707         {% endif %}
    708 
    709         {% if athlete_list or coach_list %}
    710             There are some athletes or some coaches.
    711         {% endif %}
    712 
    713         {% if athlete_list and coach_list %}
    714             Both atheletes and coaches are available.
    715         {% endif %}
    716 
    717         {% if not athlete_list or coach_list %}
    718             There are no athletes, or there are some coaches.
    719         {% endif %}
    720 
    721         {% if athlete_list and not coach_list %}
    722             There are some athletes and absolutely no coaches.
    723         {% endif %}
    724 
    725     ``if`` tags do not allow ``and`` and ``or`` clauses with the same tag,
    726     because the order of logic would be ambigous. For example, this is
    727     invalid::
    728 
    729         {% if athlete_list and coach_list or cheerleader_list %}
    730 
    731     If you need to combine ``and`` and ``or`` to do advanced logic, just use
    732     nested if tags. For example::
    733 
    734         {% if athlete_list %}
    735             {% if coach_list or cheerleader_list %}
    736                 We have athletes, and either coaches or cheerleaders!
    737             {% endif %}
    738         {% endif %}
    739     """
    740     bits = token.contents.split()
    741     del bits[0]
    742     if not bits:
    743         raise TemplateSyntaxError("'if' statement requires at least one argument")
    744     # Bits now looks something like this: ['a', 'or', 'not', 'b', 'or', 'c.d']
    745     bitstr = ' '.join(bits)
    746     boolpairs = bitstr.split(' and ')
    747     boolvars = []
    748     if len(boolpairs) == 1:
    749         link_type = IfNode.LinkTypes.or_
    750         boolpairs = bitstr.split(' or ')
    751     else:
    752         link_type = IfNode.LinkTypes.and_
    753         if ' or ' in bitstr:
    754             raise TemplateSyntaxError, "'if' tags can't mix 'and' and 'or'"
    755     for boolpair in boolpairs:
    756         if ' ' in boolpair:
    757             try:
    758                 not_, boolvar = boolpair.split()
    759             except ValueError:
    760                 raise TemplateSyntaxError, "'if' statement improperly formatted"
    761             if not_ != 'not':
    762                 raise TemplateSyntaxError, "Expected 'not' in if statement"
    763             boolvars.append((True, parser.compile_filter(boolvar)))
    764         else:
    765             boolvars.append((False, parser.compile_filter(boolpair)))
    766     nodelist_true = parser.parse(('else', 'endif'))
    767     token = parser.next_token()
    768     if token.contents == 'else':
    769         nodelist_false = parser.parse(('endif',))
    770         parser.delete_first_token()
    771     else:
    772         nodelist_false = NodeList()
    773     return IfNode(boolvars, nodelist_true, nodelist_false, link_type)
    774 do_if = register.tag("if", do_if)
    775 
    776 #@register.tag
    777 def ifchanged(parser, token):
    778     """
    779     Checks if a value has changed from the last iteration of a loop.
    780 
    781     The 'ifchanged' block tag is used within a loop. It has two possible uses.
    782 
    783     1. Checks its own rendered contents against its previous state and only
    784        displays the content if it has changed. For example, this displays a
    785        list of days, only displaying the month if it changes::
    786 
    787             <h1>Archive for {{ year }}</h1>
    788 
    789             {% for date in days %}
    790                 {% ifchanged %}<h3>{{ date|date:"F" }}</h3>{% endifchanged %}
    791                 <a href="{{ date|date:"M/d"|lower }}/">{{ date|date:"j" }}</a>
    792             {% endfor %}
    793 
    794     2. If given a variable, check whether that variable has changed.
    795        For example, the following shows the date every time it changes, but
    796        only shows the hour if both the hour and the date have changed::
    797 
    798             {% for date in days %}
    799                 {% ifchanged date.date %} {{ date.date }} {% endifchanged %}
    800                 {% ifchanged date.hour date.date %}
    801                     {{ date.hour }}
    802                 {% endifchanged %}
    803             {% endfor %}
    804     """
    805     bits = token.contents.split()
    806     nodelist = parser.parse(('endifchanged',))
    807     parser.delete_first_token()
    808     return IfChangedNode(nodelist, *bits[1:])
    809 ifchanged = register.tag(ifchanged)
    810 
    811 #@register.tag
    812 def ssi(parser, token):
    813     """
    814     Outputs the contents of a given file into the page.
    815 
    816     Like a simple "include" tag, the ``ssi`` tag includes the contents
    817     of another file -- which must be specified using an absolute path --
    818     in the current page::
    819 
    820         {% ssi /home/html/ljworld.com/includes/right_generic.html %}
    821 
    822     If the optional "parsed" parameter is given, the contents of the included
    823     file are evaluated as template code, with the current context::
    824 
    825         {% ssi /home/html/ljworld.com/includes/right_generic.html parsed %}
    826     """
    827     bits = token.contents.split()
    828     parsed = False
    829     if len(bits) not in (2, 3):
    830         raise TemplateSyntaxError("'ssi' tag takes one argument: the path to"
    831                                   " the file to be included")
    832     if len(bits) == 3:
    833         if bits[2] == 'parsed':
    834             parsed = True
    835         else:
    836             raise TemplateSyntaxError("Second (optional) argument to %s tag"
    837                                       " must be 'parsed'" % bits[0])
    838     return SsiNode(bits[1], parsed)
    839 ssi = register.tag(ssi)
    840 
    841 #@register.tag
    842 def load(parser, token):
    843     """
    844     Loads a custom template tag set.
    845 
    846     For example, to load the template tags in
    847     ``django/templatetags/news/photos.py``::
    848 
    849         {% load news.photos %}
    850     """
    851     bits = token.contents.split()
    852     for taglib in bits[1:]:
    853         # add the library to the parser
    854         try:
    855             lib = get_library("django.templatetags.%s" % taglib)
    856             parser.add_library(lib)
    857         except InvalidTemplateLibrary, e:
    858             raise TemplateSyntaxError("'%s' is not a valid tag library: %s" %
    859                                       (taglib, e))
    860     return LoadNode()
    861 load = register.tag(load)
    862 
    863 #@register.tag
    864 def now(parser, token):
    865     """
    866     Displays the date, formatted according to the given string.
    867 
    868     Uses the same format as PHP's ``date()`` function; see http://php.net/date
    869     for all the possible values.
    870 
    871     Sample usage::
    872 
    873         It is {% now "jS F Y H:i" %}
    874     """
    875     bits = token.contents.split('"')
    876     if len(bits) != 3:
    877         raise TemplateSyntaxError, "'now' statement takes one argument"
    878     format_string = bits[1]
    879     return NowNode(format_string)
    880 now = register.tag(now)
    881 
    882 #@register.tag
    883 def regroup(parser, token):
    884     """
    885     Regroups a list of alike objects by a common attribute.
    886 
    887     This complex tag is best illustrated by use of an example:  say that
    888     ``people`` is a list of ``Person`` objects that have ``first_name``,
    889     ``last_name``, and ``gender`` attributes, and you'd like to display a list
    890     that looks like:
    891 
    892         * Male:
    893             * George Bush
    894             * Bill Clinton
    895         * Female:
    896             * Margaret Thatcher
    897             * Colendeeza Rice
    898         * Unknown:
    899             * Pat Smith
    900 
    901     The following snippet of template code would accomplish this dubious task::
    902 
    903         {% regroup people by gender as grouped %}
    904         <ul>
    905         {% for group in grouped %}
    906             <li>{{ group.grouper }}
    907             <ul>
    908                 {% for item in group.list %}
    909                 <li>{{ item }}</li>
    910                 {% endfor %}
    911             </ul>
    912         {% endfor %}
    913         </ul>
    914 
    915     As you can see, ``{% regroup %}`` populates a variable with a list of
    916     objects with ``grouper`` and ``list`` attributes.  ``grouper`` contains the
    917     item that was grouped by; ``list`` contains the list of objects that share
    918     that ``grouper``.  In this case, ``grouper`` would be ``Male``, ``Female``
    919     and ``Unknown``, and ``list`` is the list of people with those genders.
    920 
    921     Note that `{% regroup %}`` does not work when the list to be grouped is not
    922     sorted by the key you are grouping by!  This means that if your list of
    923     people was not sorted by gender, you'd need to make sure it is sorted
    924     before using it, i.e.::
    925 
    926         {% regroup people|dictsort:"gender" by gender as grouped %}
    927 
    928     """
    929     firstbits = token.contents.split(None, 3)
    930     if len(firstbits) != 4:
    931         raise TemplateSyntaxError, "'regroup' tag takes five arguments"
    932     target = parser.compile_filter(firstbits[1])
    933     if firstbits[2] != 'by':
    934         raise TemplateSyntaxError("second argument to 'regroup' tag must be 'by'")
    935     lastbits_reversed = firstbits[3][::-1].split(None, 2)
    936     if lastbits_reversed[1][::-1] != 'as':
    937         raise TemplateSyntaxError("next-to-last argument to 'regroup' tag must"
    938                                   " be 'as'")
    939 
    940     expression = parser.compile_filter(lastbits_reversed[2][::-1])
    941 
    942     var_name = lastbits_reversed[0][::-1]
    943     return RegroupNode(target, expression, var_name)
    944 regroup = register.tag(regroup)
    945 
    946 def spaceless(parser, token):
    947     """
    948     Removes whitespace between HTML tags, including tab and newline characters.
    949 
    950     Example usage::
    951 
    952         {% spaceless %}
    953             <p>
    954                 <a href="foo/">Foo</a>
    955             </p>
    956         {% endspaceless %}
    957 
    958     This example would return this HTML::
    959 
    960         <p><a href="foo/">Foo</a></p>
    961 
    962     Only space between *tags* is normalized -- not space between tags and text.
    963     In this example, the space around ``Hello`` won't be stripped::
    964 
    965         {% spaceless %}
    966             <strong>
    967                 Hello
    968             </strong>
    969         {% endspaceless %}
    970     """
    971     nodelist = parser.parse(('endspaceless',))
    972     parser.delete_first_token()
    973     return SpacelessNode(nodelist)
    974 spaceless = register.tag(spaceless)
    975 
    976 #@register.tag
    977 def templatetag(parser, token):
    978     """
    979     Outputs one of the bits used to compose template tags.
    980 
    981     Since the template system has no concept of "escaping", to display one of
    982     the bits used in template tags, you must use the ``{% templatetag %}`` tag.
    983 
    984     The argument tells which template bit to output:
    985 
    986         ==================  =======
    987         Argument            Outputs
    988         ==================  =======
    989         ``openblock``       ``{%``
    990         ``closeblock``      ``%}``
    991         ``openvariable``    ``{{``
    992         ``closevariable``   ``}}``
    993         ``openbrace``       ``{``
    994         ``closebrace``      ``}``
    995         ``opencomment``     ``{#``
    996         ``closecomment``    ``#}``
    997         ==================  =======
    998     """
    999     bits = token.contents.split()
    1000     if len(bits) != 2:
    1001         raise TemplateSyntaxError, "'templatetag' statement takes one argument"
    1002     tag = bits[1]
    1003     if tag not in TemplateTagNode.mapping:
    1004         raise TemplateSyntaxError("Invalid templatetag argument: '%s'."
    1005                                   " Must be one of: %s" %
    1006                                   (tag, TemplateTagNode.mapping.keys()))
    1007     return TemplateTagNode(tag)
    1008 templatetag = register.tag(templatetag)
    1009 
    1010 def url(parser, token):
    1011     """
    1012     Returns an absolute URL matching given view with its parameters.
    1013 
    1014     This is a way to define links that aren't tied to a particular URL
    1015     configuration::
    1016 
    1017         {% url path.to.some_view arg1,arg2,name1=value1 %}
    1018 
    1019     The first argument is a path to a view. It can be an absolute python path
    1020     or just ``app_name.view_name`` without the project name if the view is
    1021     located inside the project.  Other arguments are comma-separated values
    1022     that will be filled in place of positional and keyword arguments in the
    1023     URL. All arguments for the URL should be present.
    1024 
    1025     For example if you have a view ``app_name.client`` taking client's id and
    1026     the corresponding line in a URLconf looks like this::
    1027 
    1028         ('^client/(\d+)/$', 'app_name.client')
    1029 
    1030     and this app's URLconf is included into the project's URLconf under some
    1031     path::
    1032 
    1033         ('^clients/', include('project_name.app_name.urls'))
    1034 
    1035     then in a template you can create a link for a certain client like this::
    1036 
    1037         {% url app_name.client client.id %}
    1038 
    1039     The URL will look like ``/clients/client/123/``.
    1040     """
    1041     bits = token.contents.split(' ', 2)
    1042     if len(bits) < 2:
    1043         raise TemplateSyntaxError("'%s' takes at least one argument"
    1044                                   " (path to a view)" % bits[0])
    1045     args = []
    1046     kwargs = {}
    1047     if len(bits) > 2:
    1048         for arg in bits[2].split(','):
    1049             if '=' in arg:
    1050                 k, v = arg.split('=', 1)
    1051                 k = k.strip()
    1052                 kwargs[k] = parser.compile_filter(v)
    1053             else:
    1054                 args.append(parser.compile_filter(arg))
    1055     return URLNode(bits[1], args, kwargs)
    1056 url = register.tag(url)
    1057 
    1058 #@register.tag
    1059 def widthratio(parser, token):
    1060     """
    1061     For creating bar charts and such, this tag calculates the ratio of a given
    1062     value to a maximum value, and then applies that ratio to a constant.
    1063 
    1064     For example::
    1065 
    1066         <img src='bar.gif' height='10' width='{% widthratio this_value max_value 100 %}' />
    1067 
    1068     Above, if ``this_value`` is 175 and ``max_value`` is 200, the the image in
    1069     the above example will be 88 pixels wide (because 175/200 = .875;
    1070     .875 * 100 = 87.5 which is rounded up to 88).
    1071     """
    1072     bits = token.contents.split()
    1073     if len(bits) != 4:
    1074         raise TemplateSyntaxError("widthratio takes three arguments")
    1075     tag, this_value_expr, max_value_expr, max_width = bits
    1076     try:
    1077         max_width = int(max_width)
    1078     except ValueError:
    1079         raise TemplateSyntaxError("widthratio final argument must be an integer")
    1080     return WidthRatioNode(parser.compile_filter(this_value_expr),
    1081                           parser.compile_filter(max_value_expr), max_width)
    1082 widthratio = register.tag(widthratio)
    1083 
    1084 #@register.tag
    1085 def do_with(parser, token):
    1086     """
    1087     Adds a value to the context (inside of this block) for caching and easy
    1088     access.
    1089 
    1090     For example::
    1091 
    1092         {% with person.some_sql_method as total %}
    1093             {{ total }} object{{ total|pluralize }}
    1094         {% endwith %}
    1095     """
    1096     bits = list(token.split_contents())
    1097     if len(bits) != 4 or bits[2] != "as":
    1098         raise TemplateSyntaxError("%r expected format is 'value as name'" %
    1099                                   bits[0])
    1100     var = parser.compile_filter(bits[1])
    1101     name = bits[3]
    1102     nodelist = parser.parse(('endwith',))
    1103     parser.delete_first_token()
    1104     return WithNode(var, name, nodelist)
    1105 do_with = register.tag('with', do_with)
  • django/templatetags/__init__.py

    diff --git a/django/templatetags/__init__.py b/django/templatetags/__init__.py
    index 9204535..9c47230 100644
    a b  
     1import imp
    12from django.conf import settings
    23
    3 for a in settings.INSTALLED_APPS:
    4     try:
    5         __path__.extend(__import__(a + '.templatetags', {}, {}, ['']).__path__)
    6     except ImportError:
    7         pass
     4templatetags_modules= []
     5
     6def get_templatetags_modules():
     7    if not templatetags_modules:
     8        """ Populate list once per thread. """
     9        for app_module in ['django'] + list(settings.INSTALLED_APPS):
     10            try:
     11                components = app_module.split('.')
     12                mod = __import__(app_module, {}, {}, [components[-1]])
     13                imp.find_module('templatetags', mod.__path__)
     14                templatetag_module = '%s.templatetags' % app_module
     15                templatetags_modules.append(templatetag_module)
     16            except ImportError:
     17                continue
     18    return templatetags_modules
  • new file django/templatetags/defaultfilters.py

    diff --git a/django/templatetags/defaultfilters.py b/django/templatetags/defaultfilters.py
    new file mode 100644
    index 0000000..cef3143
    - +  
     1"""Default variable filters."""
     2
     3import re
     4import random as random_module
     5try:
     6    from functools import wraps
     7except ImportError:
     8    from django.utils.functional import wraps  # Python 2.3, 2.4 fallback.
     9
     10from django.template import Variable, Library
     11from django.conf import settings
     12from django.utils.translation import ugettext, ungettext
     13from django.utils.encoding import force_unicode, iri_to_uri
     14from django.utils.safestring import mark_safe, SafeData
     15
     16register = Library()
     17
     18#######################
     19# STRING DECORATOR    #
     20#######################
     21
     22def stringfilter(func):
     23    """
     24    Decorator for filters which should only receive unicode objects. The object
     25    passed as the first positional argument will be converted to a unicode
     26    object.
     27    """
     28    def _dec(*args, **kwargs):
     29        if args:
     30            args = list(args)
     31            args[0] = force_unicode(args[0])
     32            if isinstance(args[0], SafeData) and getattr(func, 'is_safe', False):
     33                return mark_safe(func(*args, **kwargs))
     34        return func(*args, **kwargs)
     35
     36    # Include a reference to the real function (used to check original
     37    # arguments by the template parser).
     38    _dec._decorated_function = getattr(func, '_decorated_function', func)
     39    for attr in ('is_safe', 'needs_autoescape'):
     40        if hasattr(func, attr):
     41            setattr(_dec, attr, getattr(func, attr))
     42    return wraps(func)(_dec)
     43
     44###################
     45# STRINGS         #
     46###################
     47
     48
     49def addslashes(value):
     50    """
     51    Adds slashes before quotes. Useful for escaping strings in CSV, for
     52    example. Less useful for escaping JavaScript; use the ``escapejs``
     53    filter instead.
     54    """
     55    return value.replace('\\', '\\\\').replace('"', '\\"').replace("'", "\\'")
     56addslashes.is_safe = True
     57addslashes = stringfilter(addslashes)
     58
     59def capfirst(value):
     60    """Capitalizes the first character of the value."""
     61    return value and value[0].upper() + value[1:]
     62capfirst.is_safe=True
     63capfirst = stringfilter(capfirst)
     64
     65_js_escapes = (
     66    ('\\', '\\\\'),
     67    ('"', '\\"'),
     68    ("'", "\\'"),
     69    ('\n', '\\n'),
     70    ('\r', '\\r'),
     71    ('\b', '\\b'),
     72    ('\f', '\\f'),
     73    ('\t', '\\t'),
     74    ('\v', '\\v'),
     75    ('</', '<\\/'),
     76)
     77def escapejs(value):
     78    """Backslash-escapes characters for use in JavaScript strings."""
     79    for bad, good in _js_escapes:
     80        value = value.replace(bad, good)
     81    return value
     82escapejs = stringfilter(escapejs)
     83
     84def fix_ampersands(value):
     85    """Replaces ampersands with ``&amp;`` entities."""
     86    from django.utils.html import fix_ampersands
     87    return fix_ampersands(value)
     88fix_ampersands.is_safe=True
     89fix_ampersands = stringfilter(fix_ampersands)
     90
     91def floatformat(text, arg=-1):
     92    """
     93    Displays a float to a specified number of decimal places.
     94
     95    If called without an argument, it displays the floating point number with
     96    one decimal place -- but only if there's a decimal place to be displayed:
     97
     98    * num1 = 34.23234
     99    * num2 = 34.00000
     100    * num3 = 34.26000
     101    * {{ num1|floatformat }} displays "34.2"
     102    * {{ num2|floatformat }} displays "34"
     103    * {{ num3|floatformat }} displays "34.3"
     104
     105    If arg is positive, it will always display exactly arg number of decimal
     106    places:
     107
     108    * {{ num1|floatformat:3 }} displays "34.232"
     109    * {{ num2|floatformat:3 }} displays "34.000"
     110    * {{ num3|floatformat:3 }} displays "34.260"
     111
     112    If arg is negative, it will display arg number of decimal places -- but
     113    only if there are places to be displayed:
     114
     115    * {{ num1|floatformat:"-3" }} displays "34.232"
     116    * {{ num2|floatformat:"-3" }} displays "34"
     117    * {{ num3|floatformat:"-3" }} displays "34.260"
     118    """
     119    try:
     120        f = float(text)
     121    except (ValueError, TypeError):
     122        return u''
     123    try:
     124        d = int(arg)
     125    except ValueError:
     126        return force_unicode(f)
     127    try:
     128        m = f - int(f)
     129    except OverflowError:
     130        return force_unicode(f)
     131    if not m and d < 0:
     132        return mark_safe(u'%d' % int(f))
     133    else:
     134        formatstr = u'%%.%df' % abs(d)
     135        return mark_safe(formatstr % f)
     136floatformat.is_safe = True
     137
     138def iriencode(value):
     139    """Escapes an IRI value for use in a URL."""
     140    return force_unicode(iri_to_uri(value))
     141iriencode.is_safe = True
     142iriencode = stringfilter(iriencode)
     143
     144def linenumbers(value, autoescape=None):
     145    """Displays text with line numbers."""
     146    from django.utils.html import escape
     147    lines = value.split(u'\n')
     148    # Find the maximum width of the line count, for use with zero padding
     149    # string format command
     150    width = unicode(len(unicode(len(lines))))
     151    if not autoescape or isinstance(value, SafeData):
     152        for i, line in enumerate(lines):
     153            lines[i] = (u"%0" + width  + u"d. %s") % (i + 1, line)
     154    else:
     155        for i, line in enumerate(lines):
     156            lines[i] = (u"%0" + width  + u"d. %s") % (i + 1, escape(line))
     157    return mark_safe(u'\n'.join(lines))
     158linenumbers.is_safe = True
     159linenumbers.needs_autoescape = True
     160linenumbers = stringfilter(linenumbers)
     161
     162def lower(value):
     163    """Converts a string into all lowercase."""
     164    return value.lower()
     165lower.is_safe = True
     166lower = stringfilter(lower)
     167
     168def make_list(value):
     169    """
     170    Returns the value turned into a list.
     171
     172    For an integer, it's a list of digits.
     173    For a string, it's a list of characters.
     174    """
     175    return list(value)
     176make_list.is_safe = False
     177make_list = stringfilter(make_list)
     178
     179def slugify(value):
     180    """
     181    Normalizes string, converts to lowercase, removes non-alpha characters,
     182    and converts spaces to hyphens.
     183    """
     184    import unicodedata
     185    value = unicodedata.normalize('NFKD', value).encode('ascii', 'ignore')
     186    value = unicode(re.sub('[^\w\s-]', '', value).strip().lower())
     187    return mark_safe(re.sub('[-\s]+', '-', value))
     188slugify.is_safe = True
     189slugify = stringfilter(slugify)
     190
     191def stringformat(value, arg):
     192    """
     193    Formats the variable according to the arg, a string formatting specifier.
     194
     195    This specifier uses Python string formating syntax, with the exception that
     196    the leading "%" is dropped.
     197
     198    See http://docs.python.org/lib/typesseq-strings.html for documentation
     199    of Python string formatting
     200    """
     201    try:
     202        return (u"%" + unicode(arg)) % value
     203    except (ValueError, TypeError):
     204        return u""
     205stringformat.is_safe = True
     206
     207def title(value):
     208    """Converts a string into titlecase."""
     209    return re.sub("([a-z])'([A-Z])", lambda m: m.group(0).lower(), value.title())
     210title.is_safe = True
     211title = stringfilter(title)
     212
     213def truncatewords(value, arg):
     214    """
     215    Truncates a string after a certain number of words.
     216
     217    Argument: Number of words to truncate after.
     218    """
     219    from django.utils.text import truncate_words
     220    try:
     221        length = int(arg)
     222    except ValueError: # Invalid literal for int().
     223        return value # Fail silently.
     224    return truncate_words(value, length)
     225truncatewords.is_safe = True
     226truncatewords = stringfilter(truncatewords)
     227
     228def truncatewords_html(value, arg):
     229    """
     230    Truncates HTML after a certain number of words.
     231
     232    Argument: Number of words to truncate after.
     233    """
     234    from django.utils.text import truncate_html_words
     235    try:
     236        length = int(arg)
     237    except ValueError: # invalid literal for int()
     238        return value # Fail silently.
     239    return truncate_html_words(value, length)
     240truncatewords_html.is_safe = True
     241truncatewords_html = stringfilter(truncatewords_html)
     242
     243def upper(value):
     244    """Converts a string into all uppercase."""
     245    return value.upper()
     246upper.is_safe = False
     247upper = stringfilter(upper)
     248
     249def urlencode(value):
     250    """Escapes a value for use in a URL."""
     251    from django.utils.http import urlquote
     252    return urlquote(value)
     253urlencode.is_safe = False
     254urlencode = stringfilter(urlencode)
     255
     256def urlize(value, autoescape=None):
     257    """Converts URLs in plain text into clickable links."""
     258    from django.utils.html import urlize
     259    return mark_safe(urlize(value, nofollow=True, autoescape=autoescape))
     260urlize.is_safe=True
     261urlize.needs_autoescape = True
     262urlize = stringfilter(urlize)
     263
     264def urlizetrunc(value, limit, autoescape=None):
     265    """
     266    Converts URLs into clickable links, truncating URLs to the given character
     267    limit, and adding 'rel=nofollow' attribute to discourage spamming.
     268
     269    Argument: Length to truncate URLs to.
     270    """
     271    from django.utils.html import urlize
     272    return mark_safe(urlize(value, trim_url_limit=int(limit), nofollow=True,
     273                            autoescape=autoescape))
     274urlizetrunc.is_safe = True
     275urlizetrunc.needs_autoescape = True
     276urlizetrunc = stringfilter(urlizetrunc)
     277
     278def wordcount(value):
     279    """Returns the number of words."""
     280    return len(value.split())
     281wordcount.is_safe = False
     282wordcount = stringfilter(wordcount)
     283
     284def wordwrap(value, arg):
     285    """
     286    Wraps words at specified line length.
     287
     288    Argument: number of characters to wrap the text at.
     289    """
     290    from django.utils.text import wrap
     291    return wrap(value, int(arg))
     292wordwrap.is_safe = True
     293wordwrap = stringfilter(wordwrap)
     294
     295def ljust(value, arg):
     296    """
     297    Left-aligns the value in a field of a given width.
     298
     299    Argument: field size.
     300    """
     301    return value.ljust(int(arg))
     302ljust.is_safe = True
     303ljust = stringfilter(ljust)
     304
     305def rjust(value, arg):
     306    """
     307    Right-aligns the value in a field of a given width.
     308
     309    Argument: field size.
     310    """
     311    return value.rjust(int(arg))
     312rjust.is_safe = True
     313rjust = stringfilter(rjust)
     314
     315def center(value, arg):
     316    """Centers the value in a field of a given width."""
     317    return value.center(int(arg))
     318center.is_safe = True
     319center = stringfilter(center)
     320
     321def cut(value, arg):
     322    """
     323    Removes all values of arg from the given string.
     324    """
     325    safe = isinstance(value, SafeData)
     326    value = value.replace(arg, u'')
     327    if safe and arg != ';':
     328        return mark_safe(value)
     329    return value
     330cut = stringfilter(cut)
     331
     332###################
     333# HTML STRINGS    #
     334###################
     335
     336def escape(value):
     337    """
     338    Marks the value as a string that should not be auto-escaped.
     339    """
     340    from django.utils.safestring import mark_for_escaping
     341    return mark_for_escaping(value)
     342escape.is_safe = True
     343escape = stringfilter(escape)
     344
     345def force_escape(value):
     346    """
     347    Escapes a string's HTML. This returns a new string containing the escaped
     348    characters (as opposed to "escape", which marks the content for later
     349    possible escaping).
     350    """
     351    from django.utils.html import escape
     352    return mark_safe(escape(value))
     353force_escape = stringfilter(force_escape)
     354force_escape.is_safe = True
     355
     356def linebreaks(value, autoescape=None):
     357    """
     358    Replaces line breaks in plain text with appropriate HTML; a single
     359    newline becomes an HTML line break (``<br />``) and a new line
     360    followed by a blank line becomes a paragraph break (``</p>``).
     361    """
     362    from django.utils.html import linebreaks
     363    autoescape = autoescape and not isinstance(value, SafeData)
     364    return mark_safe(linebreaks(value, autoescape))
     365linebreaks.is_safe = True
     366linebreaks.needs_autoescape = True
     367linebreaks = stringfilter(linebreaks)
     368
     369def linebreaksbr(value, autoescape=None):
     370    """
     371    Converts all newlines in a piece of plain text to HTML line breaks
     372    (``<br />``).
     373    """
     374    if autoescape and not isinstance(value, SafeData):
     375        from django.utils.html import escape
     376        value = escape(value)
     377    return mark_safe(value.replace('\n', '<br />'))
     378linebreaksbr.is_safe = True
     379linebreaksbr.needs_autoescape = True
     380linebreaksbr = stringfilter(linebreaksbr)
     381
     382def safe(value):
     383    """
     384    Marks the value as a string that should not be auto-escaped.
     385    """
     386    from django.utils.safestring import mark_safe
     387    return mark_safe(value)
     388safe.is_safe = True
     389safe = stringfilter(safe)
     390
     391def removetags(value, tags):
     392    """Removes a space separated list of [X]HTML tags from the output."""
     393    tags = [re.escape(tag) for tag in tags.split()]
     394    tags_re = u'(%s)' % u'|'.join(tags)
     395    starttag_re = re.compile(ur'<%s(/?>|(\s+[^>]*>))' % tags_re, re.U)
     396    endtag_re = re.compile(u'</%s>' % tags_re)
     397    value = starttag_re.sub(u'', value)
     398    value = endtag_re.sub(u'', value)
     399    return value
     400removetags.is_safe = True
     401removetags = stringfilter(removetags)
     402
     403def striptags(value):
     404    """Strips all [X]HTML tags."""
     405    from django.utils.html import strip_tags
     406    return strip_tags(value)
     407striptags.is_safe = True
     408striptags = stringfilter(striptags)
     409
     410###################
     411# LISTS           #
     412###################
     413
     414def dictsort(value, arg):
     415    """
     416    Takes a list of dicts, returns that list sorted by the property given in
     417    the argument.
     418    """
     419    var_resolve = Variable(arg).resolve
     420    decorated = [(var_resolve(item), item) for item in value]
     421    decorated.sort()
     422    return [item[1] for item in decorated]
     423dictsort.is_safe = False
     424
     425def dictsortreversed(value, arg):
     426    """
     427    Takes a list of dicts, returns that list sorted in reverse order by the
     428    property given in the argument.
     429    """
     430    var_resolve = Variable(arg).resolve
     431    decorated = [(var_resolve(item), item) for item in value]
     432    decorated.sort()
     433    decorated.reverse()
     434    return [item[1] for item in decorated]
     435dictsortreversed.is_safe = False
     436
     437def first(value):
     438    """Returns the first item in a list."""
     439    try:
     440        return value[0]
     441    except IndexError:
     442        return u''
     443first.is_safe = False
     444
     445def join(value, arg):
     446    """Joins a list with a string, like Python's ``str.join(list)``."""
     447    try:
     448        data = arg.join(map(force_unicode, value))
     449    except AttributeError: # fail silently but nicely
     450        return value
     451    safe_args = reduce(lambda lhs, rhs: lhs and isinstance(rhs, SafeData),
     452            value, True)
     453    if safe_args:
     454        return mark_safe(data)
     455    else:
     456        return data
     457join.is_safe = True
     458
     459def last(value):
     460    "Returns the last item in a list"
     461    try:
     462        return value[-1]
     463    except IndexError:
     464        return u''
     465last.is_safe = True
     466
     467def length(value):
     468    """Returns the length of the value - useful for lists."""
     469    return len(value)
     470length.is_safe = True
     471
     472def length_is(value, arg):
     473    """Returns a boolean of whether the value's length is the argument."""
     474    return len(value) == int(arg)
     475length_is.is_safe = True
     476
     477def random(value):
     478    """Returns a random item from the list."""
     479    return random_module.choice(value)
     480random.is_safe = True
     481
     482def slice_(value, arg):
     483    """
     484    Returns a slice of the list.
     485
     486    Uses the same syntax as Python's list slicing; see
     487    http://diveintopython.org/native_data_types/lists.html#odbchelper.list.slice
     488    for an introduction.
     489    """
     490    try:
     491        bits = []
     492        for x in arg.split(u':'):
     493            if len(x) == 0:
     494                bits.append(None)
     495            else:
     496                bits.append(int(x))
     497        return value[slice(*bits)]
     498
     499    except (ValueError, TypeError):
     500        return value # Fail silently.
     501slice_.is_safe = True
     502
     503def unordered_list(value, autoescape=None):
     504    """
     505    Recursively takes a self-nested list and returns an HTML unordered list --
     506    WITHOUT opening and closing <ul> tags.
     507
     508    The list is assumed to be in the proper format. For example, if ``var``
     509    contains: ``['States', ['Kansas', ['Lawrence', 'Topeka'], 'Illinois']]``,
     510    then ``{{ var|unordered_list }}`` would return::
     511
     512        <li>States
     513        <ul>
     514                <li>Kansas
     515                <ul>
     516                        <li>Lawrence</li>
     517                        <li>Topeka</li>
     518                </ul>
     519                </li>
     520                <li>Illinois</li>
     521        </ul>
     522        </li>
     523    """
     524    if autoescape:
     525        from django.utils.html import conditional_escape
     526        escaper = conditional_escape
     527    else:
     528        escaper = lambda x: x
     529    def convert_old_style_list(list_):
     530        """
     531        Converts old style lists to the new easier to understand format.
     532
     533        The old list format looked like:
     534            ['Item 1', [['Item 1.1', []], ['Item 1.2', []]]
     535
     536        And it is converted to:
     537            ['Item 1', ['Item 1.1', 'Item 1.2]]
     538        """
     539        if not isinstance(list_, (tuple, list)) or len(list_) != 2:
     540            return list_, False
     541        first_item, second_item = list_
     542        if second_item == []:
     543            return [first_item], True
     544        old_style_list = True
     545        new_second_item = []
     546        for sublist in second_item:
     547            item, old_style_list = convert_old_style_list(sublist)
     548            if not old_style_list:
     549                break
     550            new_second_item.extend(item)
     551        if old_style_list:
     552            second_item = new_second_item
     553        return [first_item, second_item], old_style_list
     554    def _helper(list_, tabs=1):
     555        indent = u'\t' * tabs
     556        output = []
     557
     558        list_length = len(list_)
     559        i = 0
     560        while i < list_length:
     561            title = list_[i]
     562            sublist = ''
     563            sublist_item = None
     564            if isinstance(title, (list, tuple)):
     565                sublist_item = title
     566                title = ''
     567            elif i < list_length - 1:
     568                next_item = list_[i+1]
     569                if next_item and isinstance(next_item, (list, tuple)):
     570                    # The next item is a sub-list.
     571                    sublist_item = next_item
     572                    # We've processed the next item now too.
     573                    i += 1
     574            if sublist_item:
     575                sublist = _helper(sublist_item, tabs+1)
     576                sublist = '\n%s<ul>\n%s\n%s</ul>\n%s' % (indent, sublist,
     577                                                         indent, indent)
     578            output.append('%s<li>%s%s</li>' % (indent,
     579                    escaper(force_unicode(title)), sublist))
     580            i += 1
     581        return '\n'.join(output)
     582    value, converted = convert_old_style_list(value)
     583    return mark_safe(_helper(value))
     584unordered_list.is_safe = True
     585unordered_list.needs_autoescape = True
     586
     587###################
     588# INTEGERS        #
     589###################
     590
     591def add(value, arg):
     592    """Adds the arg to the value."""
     593    return int(value) + int(arg)
     594add.is_safe = False
     595
     596def get_digit(value, arg):
     597    """
     598    Given a whole number, returns the requested digit of it, where 1 is the
     599    right-most digit, 2 is the second-right-most digit, etc. Returns the
     600    original value for invalid input (if input or argument is not an integer,
     601    or if argument is less than 1). Otherwise, output is always an integer.
     602    """
     603    try:
     604        arg = int(arg)
     605        value = int(value)
     606    except ValueError:
     607        return value # Fail silently for an invalid argument
     608    if arg < 1:
     609        return value
     610    try:
     611        return int(str(value)[-arg])
     612    except IndexError:
     613        return 0
     614get_digit.is_safe = False
     615
     616###################
     617# DATES           #
     618###################
     619
     620def date(value, arg=None):
     621    """Formats a date according to the given format."""
     622    from django.utils.dateformat import format
     623    if not value:
     624        return u''
     625    if arg is None:
     626        arg = settings.DATE_FORMAT
     627    return format(value, arg)
     628date.is_safe = False
     629
     630def time(value, arg=None):
     631    """Formats a time according to the given format."""
     632    from django.utils.dateformat import time_format
     633    if value in (None, u''):
     634        return u''
     635    if arg is None:
     636        arg = settings.TIME_FORMAT
     637    return time_format(value, arg)
     638time.is_safe = False
     639
     640def timesince(value, arg=None):
     641    """Formats a date as the time since that date (i.e. "4 days, 6 hours")."""
     642    from django.utils.timesince import timesince
     643    if not value:
     644        return u''
     645    if arg:
     646        return timesince(arg, value)
     647    return timesince(value)
     648timesince.is_safe = False
     649
     650def timeuntil(value, arg=None):
     651    """Formats a date as the time until that date (i.e. "4 days, 6 hours")."""
     652    from django.utils.timesince import timesince
     653    from datetime import datetime
     654    if not value:
     655        return u''
     656    if arg:
     657        return timesince(arg, value)
     658    return timesince(datetime.now(), value)
     659timeuntil.is_safe = False
     660
     661###################
     662# LOGIC           #
     663###################
     664
     665def default(value, arg):
     666    """If value is unavailable, use given default."""
     667    return value or arg
     668default.is_safe = False
     669
     670def default_if_none(value, arg):
     671    """If value is None, use given default."""
     672    if value is None:
     673        return arg
     674    return value
     675default_if_none.is_safe = False
     676
     677def divisibleby(value, arg):
     678    """Returns True if the value is devisible by the argument."""
     679    return int(value) % int(arg) == 0
     680divisibleby.is_safe = False
     681
     682def yesno(value, arg=None):
     683    """
     684    Given a string mapping values for true, false and (optionally) None,
     685    returns one of those strings accoding to the value:
     686
     687    ==========  ======================  ==================================
     688    Value       Argument                Outputs
     689    ==========  ======================  ==================================
     690    ``True``    ``"yeah,no,maybe"``     ``yeah``
     691    ``False``   ``"yeah,no,maybe"``     ``no``
     692    ``None``    ``"yeah,no,maybe"``     ``maybe``
     693    ``None``    ``"yeah,no"``           ``"no"`` (converts None to False
     694                                        if no mapping for None is given.
     695    ==========  ======================  ==================================
     696    """
     697    if arg is None:
     698        arg = ugettext('yes,no,maybe')
     699    bits = arg.split(u',')
     700    if len(bits) < 2:
     701        return value # Invalid arg.
     702    try:
     703        yes, no, maybe = bits
     704    except ValueError:
     705        # Unpack list of wrong size (no "maybe" value provided).
     706        yes, no, maybe = bits[0], bits[1], bits[1]
     707    if value is None:
     708        return maybe
     709    if value:
     710        return yes
     711    return no
     712yesno.is_safe = False
     713
     714###################
     715# MISC            #
     716###################
     717
     718def filesizeformat(bytes):
     719    """
     720    Formats the value like a 'human-readable' file size (i.e. 13 KB, 4.1 MB,
     721    102 bytes, etc).
     722    """
     723    try:
     724        bytes = float(bytes)
     725    except TypeError:
     726        return u"0 bytes"
     727
     728    if bytes < 1024:
     729        return ungettext("%(size)d byte", "%(size)d bytes", bytes) % {'size': bytes}
     730    if bytes < 1024 * 1024:
     731        return ugettext("%.1f KB") % (bytes / 1024)
     732    if bytes < 1024 * 1024 * 1024:
     733        return ugettext("%.1f MB") % (bytes / (1024 * 1024))
     734    return ugettext("%.1f GB") % (bytes / (1024 * 1024 * 1024))
     735filesizeformat.is_safe = True
     736
     737def pluralize(value, arg=u's'):
     738    """
     739    Returns a plural suffix if the value is not 1. By default, 's' is used as
     740    the suffix:
     741
     742    * If value is 0, vote{{ value|pluralize }} displays "0 votes".
     743    * If value is 1, vote{{ value|pluralize }} displays "1 vote".
     744    * If value is 2, vote{{ value|pluralize }} displays "2 votes".
     745
     746    If an argument is provided, that string is used instead:
     747
     748    * If value is 0, class{{ value|pluralize:"es" }} displays "0 classes".
     749    * If value is 1, class{{ value|pluralize:"es" }} displays "1 class".
     750    * If value is 2, class{{ value|pluralize:"es" }} displays "2 classes".
     751
     752    If the provided argument contains a comma, the text before the comma is
     753    used for the singular case and the text after the comma is used for the
     754    plural case:
     755
     756    * If value is 0, cand{{ value|pluralize:"y,ies" }} displays "0 candies".
     757    * If value is 1, cand{{ value|pluralize:"y,ies" }} displays "1 candy".
     758    * If value is 2, cand{{ value|pluralize:"y,ies" }} displays "2 candies".
     759    """
     760    if not u',' in arg:
     761        arg = u',' + arg
     762    bits = arg.split(u',')
     763    if len(bits) > 2:
     764        return u''
     765    singular_suffix, plural_suffix = bits[:2]
     766
     767    try:
     768        if int(value) != 1:
     769            return plural_suffix
     770    except ValueError: # Invalid string that's not a number.
     771        pass
     772    except TypeError: # Value isn't a string or a number; maybe it's a list?
     773        try:
     774            if len(value) != 1:
     775                return plural_suffix
     776        except TypeError: # len() of unsized object.
     777            pass
     778    return singular_suffix
     779pluralize.is_safe = False
     780
     781def phone2numeric(value):
     782    """Takes a phone number and converts it in to its numerical equivalent."""
     783    from django.utils.text import phone2numeric
     784    return phone2numeric(value)
     785phone2numeric.is_safe = True
     786
     787def pprint(value):
     788    """A wrapper around pprint.pprint -- for debugging, really."""
     789    from pprint import pformat
     790    try:
     791        return pformat(value)
     792    except Exception, e:
     793        return u"Error in formatting: %s" % force_unicode(e, errors="replace")
     794pprint.is_safe = True
     795
     796# Syntax: register.filter(name of filter, callback)
     797register.filter(add)
     798register.filter(addslashes)
     799register.filter(capfirst)
     800register.filter(center)
     801register.filter(cut)
     802register.filter(date)
     803register.filter(default)
     804register.filter(default_if_none)
     805register.filter(dictsort)
     806register.filter(dictsortreversed)
     807register.filter(divisibleby)
     808register.filter(escape)
     809register.filter(escapejs)
     810register.filter(filesizeformat)
     811register.filter(first)
     812register.filter(fix_ampersands)
     813register.filter(floatformat)
     814register.filter(force_escape)
     815register.filter(get_digit)
     816register.filter(iriencode)
     817register.filter(join)
     818register.filter(last)
     819register.filter(length)
     820register.filter(length_is)
     821register.filter(linebreaks)
     822register.filter(linebreaksbr)
     823register.filter(linenumbers)
     824register.filter(ljust)
     825register.filter(lower)
     826register.filter(make_list)
     827register.filter(phone2numeric)
     828register.filter(pluralize)
     829register.filter(pprint)
     830register.filter(removetags)
     831register.filter(random)
     832register.filter(rjust)
     833register.filter(safe)
     834register.filter('slice', slice_)
     835register.filter(slugify)
     836register.filter(stringformat)
     837register.filter(striptags)
     838register.filter(time)
     839register.filter(timesince)
     840register.filter(timeuntil)
     841register.filter(title)
     842register.filter(truncatewords)
     843register.filter(truncatewords_html)
     844register.filter(unordered_list)
     845register.filter(upper)
     846register.filter(urlencode)
     847register.filter(urlize)
     848register.filter(urlizetrunc)
     849register.filter(wordcount)
     850register.filter(wordwrap)
     851register.filter(yesno)
  • new file django/templatetags/defaulttags.py

    diff --git a/django/templatetags/defaulttags.py b/django/templatetags/defaulttags.py
    new file mode 100644
    index 0000000..535457a
    - +  
     1"""Default tags used by the template system, available to all templates."""
     2
     3import sys
     4import re
     5from itertools import cycle as itertools_cycle
     6try:
     7    reversed
     8except NameError:
     9    from django.utils.itercompat import reversed     # Python 2.3 fallback
     10
     11from django.template import Node, NodeList, Template, Context, Variable
     12from 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
     13from django.template import get_library, Library, InvalidTemplateLibrary
     14from django.conf import settings
     15from django.utils.encoding import smart_str, smart_unicode
     16from django.utils.itercompat import groupby
     17from django.utils.safestring import mark_safe
     18
     19register = Library()
     20
     21class AutoEscapeControlNode(Node):
     22    """Implements the actions of the autoescape tag."""
     23    def __init__(self, setting, nodelist):
     24        self.setting, self.nodelist = setting, nodelist
     25
     26    def render(self, context):
     27        old_setting = context.autoescape
     28        context.autoescape = self.setting
     29        output = self.nodelist.render(context)
     30        context.autoescape = old_setting
     31        if self.setting:
     32            return mark_safe(output)
     33        else:
     34            return output
     35
     36class CommentNode(Node):
     37    def render(self, context):
     38        return ''
     39
     40class CycleNode(Node):
     41    def __init__(self, cyclevars, variable_name=None):
     42        self.cycle_iter = itertools_cycle([Variable(v) for v in cyclevars])
     43        self.variable_name = variable_name
     44
     45    def render(self, context):
     46        value = self.cycle_iter.next().resolve(context)
     47        if self.variable_name:
     48            context[self.variable_name] = value
     49        return value
     50
     51class DebugNode(Node):
     52    def render(self, context):
     53        from pprint import pformat
     54        output = [pformat(val) for val in context]
     55        output.append('\n\n')
     56        output.append(pformat(sys.modules))
     57        return ''.join(output)
     58
     59class FilterNode(Node):
     60    def __init__(self, filter_expr, nodelist):
     61        self.filter_expr, self.nodelist = filter_expr, nodelist
     62
     63    def render(self, context):
     64        output = self.nodelist.render(context)
     65        # Apply filters.
     66        context.update({'var': output})
     67        filtered = self.filter_expr.resolve(context)
     68        context.pop()
     69        return filtered
     70
     71class FirstOfNode(Node):
     72    def __init__(self, vars):
     73        self.vars = map(Variable, vars)
     74
     75    def render(self, context):
     76        for var in self.vars:
     77            try:
     78                value = var.resolve(context)
     79            except VariableDoesNotExist:
     80                continue
     81            if value:
     82                return smart_unicode(value)
     83        return u''
     84
     85class ForNode(Node):
     86    def __init__(self, loopvars, sequence, is_reversed, nodelist_loop):
     87        self.loopvars, self.sequence = loopvars, sequence
     88        self.is_reversed = is_reversed
     89        self.nodelist_loop = nodelist_loop
     90
     91    def __repr__(self):
     92        reversed_text = self.is_reversed and ' reversed' or ''
     93        return "<For Node: for %s in %s, tail_len: %d%s>" % \
     94            (', '.join(self.loopvars), self.sequence, len(self.nodelist_loop),
     95             reversed_text)
     96
     97    def __iter__(self):
     98        for node in self.nodelist_loop:
     99            yield node
     100
     101    def get_nodes_by_type(self, nodetype):
     102        nodes = []
     103        if isinstance(self, nodetype):
     104            nodes.append(self)
     105        nodes.extend(self.nodelist_loop.get_nodes_by_type(nodetype))
     106        return nodes
     107
     108    def render(self, context):
     109        nodelist = NodeList()
     110        if 'forloop' in context:
     111            parentloop = context['forloop']
     112        else:
     113            parentloop = {}
     114        context.push()
     115        try:
     116            values = self.sequence.resolve(context, True)
     117        except VariableDoesNotExist:
     118            values = []
     119        if values is None:
     120            values = []
     121        if not hasattr(values, '__len__'):
     122            values = list(values)
     123        len_values = len(values)
     124        if self.is_reversed:
     125            values = reversed(values)
     126        unpack = len(self.loopvars) > 1
     127        # Create a forloop value in the context.  We'll update counters on each
     128        # iteration just below.
     129        loop_dict = context['forloop'] = {'parentloop': parentloop}
     130        for i, item in enumerate(values):
     131            # Shortcuts for current loop iteration number.
     132            loop_dict['counter0'] = i
     133            loop_dict['counter'] = i+1
     134            # Reverse counter iteration numbers.
     135            loop_dict['revcounter'] = len_values - i
     136            loop_dict['revcounter0'] = len_values - i - 1
     137            # Boolean values designating first and last times through loop.
     138            loop_dict['first'] = (i == 0)
     139            loop_dict['last'] = (i == len_values - 1)
     140
     141            if unpack:
     142                # If there are multiple loop variables, unpack the item into
     143                # them.
     144                context.update(dict(zip(self.loopvars, item)))
     145            else:
     146                context[self.loopvars[0]] = item
     147            for node in self.nodelist_loop:
     148                nodelist.append(node.render(context))
     149            if unpack:
     150                # The loop variables were pushed on to the context so pop them
     151                # off again. This is necessary because the tag lets the length
     152                # of loopvars differ to the length of each set of items and we
     153                # don't want to leave any vars from the previous loop on the
     154                # context.
     155                context.pop()
     156        context.pop()
     157        return nodelist.render(context)
     158
     159class IfChangedNode(Node):
     160    def __init__(self, nodelist, *varlist):
     161        self.nodelist = nodelist
     162        self._last_seen = None
     163        self._varlist = map(Variable, varlist)
     164        self._id = str(id(self))
     165
     166    def render(self, context):
     167        if 'forloop' in context and self._id not in context['forloop']:
     168            self._last_seen = None
     169            context['forloop'][self._id] = 1
     170        try:
     171            if self._varlist:
     172                # Consider multiple parameters.  This automatically behaves
     173                # like an OR evaluation of the multiple variables.
     174                compare_to = [var.resolve(context) for var in self._varlist]
     175            else:
     176                compare_to = self.nodelist.render(context)
     177        except VariableDoesNotExist:
     178            compare_to = None
     179
     180        if  compare_to != self._last_seen:
     181            firstloop = (self._last_seen == None)
     182            self._last_seen = compare_to
     183            context.push()
     184            context['ifchanged'] = {'firstloop': firstloop}
     185            content = self.nodelist.render(context)
     186            context.pop()
     187            return content
     188        else:
     189            return ''
     190
     191class IfEqualNode(Node):
     192    def __init__(self, var1, var2, nodelist_true, nodelist_false, negate):
     193        self.var1, self.var2 = Variable(var1), Variable(var2)
     194        self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false
     195        self.negate = negate
     196
     197    def __repr__(self):
     198        return "<IfEqualNode>"
     199
     200    def render(self, context):
     201        try:
     202            val1 = self.var1.resolve(context)
     203        except VariableDoesNotExist:
     204            val1 = None
     205        try:
     206            val2 = self.var2.resolve(context)
     207        except VariableDoesNotExist:
     208            val2 = None
     209        if (self.negate and val1 != val2) or (not self.negate and val1 == val2):
     210            return self.nodelist_true.render(context)
     211        return self.nodelist_false.render(context)
     212
     213class IfNode(Node):
     214    def __init__(self, bool_exprs, nodelist_true, nodelist_false, link_type):
     215        self.bool_exprs = bool_exprs
     216        self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false
     217        self.link_type = link_type
     218
     219    def __repr__(self):
     220        return "<If node>"
     221
     222    def __iter__(self):
     223        for node in self.nodelist_true:
     224            yield node
     225        for node in self.nodelist_false:
     226            yield node
     227
     228    def get_nodes_by_type(self, nodetype):
     229        nodes = []
     230        if isinstance(self, nodetype):
     231            nodes.append(self)
     232        nodes.extend(self.nodelist_true.get_nodes_by_type(nodetype))
     233        nodes.extend(self.nodelist_false.get_nodes_by_type(nodetype))
     234        return nodes
     235
     236    def render(self, context):
     237        if self.link_type == IfNode.LinkTypes.or_:
     238            for ifnot, bool_expr in self.bool_exprs:
     239                try:
     240                    value = bool_expr.resolve(context, True)
     241                except VariableDoesNotExist:
     242                    value = None
     243                if (value and not ifnot) or (ifnot and not value):
     244                    return self.nodelist_true.render(context)
     245            return self.nodelist_false.render(context)
     246        else:
     247            for ifnot, bool_expr in self.bool_exprs:
     248                try:
     249                    value = bool_expr.resolve(context, True)
     250                except VariableDoesNotExist:
     251                    value = None
     252                if not ((value and not ifnot) or (ifnot and not value)):
     253                    return self.nodelist_false.render(context)
     254            return self.nodelist_true.render(context)
     255
     256    class LinkTypes:
     257        and_ = 0,
     258        or_ = 1
     259
     260class RegroupNode(Node):
     261    def __init__(self, target, expression, var_name):
     262        self.target, self.expression = target, expression
     263        self.var_name = var_name
     264
     265    def render(self, context):
     266        obj_list = self.target.resolve(context, True)
     267        if obj_list == None:
     268            # target variable wasn't found in context; fail silently.
     269            context[self.var_name] = []
     270            return ''
     271        # List of dictionaries in the format:
     272        # {'grouper': 'key', 'list': [list of contents]}.
     273        context[self.var_name] = [
     274            {'grouper': key, 'list': list(val)}
     275            for key, val in
     276            groupby(obj_list, lambda v, f=self.expression.resolve: f(v, True))
     277        ]
     278        return ''
     279
     280def include_is_allowed(filepath):
     281    for root in settings.ALLOWED_INCLUDE_ROOTS:
     282        if filepath.startswith(root):
     283            return True
     284    return False
     285
     286class SsiNode(Node):
     287    def __init__(self, filepath, parsed):
     288        self.filepath, self.parsed = filepath, parsed
     289
     290    def render(self, context):
     291        if not include_is_allowed(self.filepath):
     292            if settings.DEBUG:
     293                return "[Didn't have permission to include file]"
     294            else:
     295                return '' # Fail silently for invalid includes.
     296        try:
     297            fp = open(self.filepath, 'r')
     298            output = fp.read()
     299            fp.close()
     300        except IOError:
     301            output = ''
     302        if self.parsed:
     303            try:
     304                t = Template(output, name=self.filepath)
     305                return t.render(context)
     306            except TemplateSyntaxError, e:
     307                if settings.DEBUG:
     308                    return "[Included template had syntax error: %s]" % e
     309                else:
     310                    return '' # Fail silently for invalid included templates.
     311        return output
     312
     313class LoadNode(Node):
     314    def render(self, context):
     315        return ''
     316
     317class NowNode(Node):
     318    def __init__(self, format_string):
     319        self.format_string = format_string
     320
     321    def render(self, context):
     322        from datetime import datetime
     323        from django.utils.dateformat import DateFormat
     324        df = DateFormat(datetime.now())
     325        return df.format(self.format_string)
     326
     327class SpacelessNode(Node):
     328    def __init__(self, nodelist):
     329        self.nodelist = nodelist
     330
     331    def render(self, context):
     332        from django.utils.html import strip_spaces_between_tags
     333        return strip_spaces_between_tags(self.nodelist.render(context).strip())
     334
     335class TemplateTagNode(Node):
     336    mapping = {'openblock': BLOCK_TAG_START,
     337               'closeblock': BLOCK_TAG_END,
     338               'openvariable': VARIABLE_TAG_START,
     339               'closevariable': VARIABLE_TAG_END,
     340               'openbrace': SINGLE_BRACE_START,
     341               'closebrace': SINGLE_BRACE_END,
     342               'opencomment': COMMENT_TAG_START,
     343               'closecomment': COMMENT_TAG_END,
     344               }
     345
     346    def __init__(self, tagtype):
     347        self.tagtype = tagtype
     348
     349    def render(self, context):
     350        return self.mapping.get(self.tagtype, '')
     351
     352class URLNode(Node):
     353    def __init__(self, view_name, args, kwargs):
     354        self.view_name = view_name
     355        self.args = args
     356        self.kwargs = kwargs
     357
     358    def render(self, context):
     359        from django.core.urlresolvers import reverse, NoReverseMatch
     360        args = [arg.resolve(context) for arg in self.args]
     361        kwargs = dict([(smart_str(k,'ascii'), v.resolve(context))
     362                       for k, v in self.kwargs.items()])
     363        try:
     364            return reverse(self.view_name, args=args, kwargs=kwargs)
     365        except NoReverseMatch:
     366            try:
     367                project_name = settings.SETTINGS_MODULE.split('.')[0]
     368                return reverse(project_name + '.' + self.view_name,
     369                               args=args, kwargs=kwargs)
     370            except NoReverseMatch:
     371                return ''
     372
     373class WidthRatioNode(Node):
     374    def __init__(self, val_expr, max_expr, max_width):
     375        self.val_expr = val_expr
     376        self.max_expr = max_expr
     377        self.max_width = max_width
     378
     379    def render(self, context):
     380        try:
     381            value = self.val_expr.resolve(context)
     382            maxvalue = self.max_expr.resolve(context)
     383        except VariableDoesNotExist:
     384            return ''
     385        try:
     386            value = float(value)
     387            maxvalue = float(maxvalue)
     388            ratio = (value / maxvalue) * int(self.max_width)
     389        except (ValueError, ZeroDivisionError):
     390            return ''
     391        return str(int(round(ratio)))
     392
     393class WithNode(Node):
     394    def __init__(self, var, name, nodelist):
     395        self.var = var
     396        self.name = name
     397        self.nodelist = nodelist
     398
     399    def __repr__(self):
     400        return "<WithNode>"
     401
     402    def render(self, context):
     403        val = self.var.resolve(context)
     404        context.push()
     405        context[self.name] = val
     406        output = self.nodelist.render(context)
     407        context.pop()
     408        return output
     409
     410#@register.tag
     411def autoescape(parser, token):
     412    """
     413    Force autoescape behaviour for this block.
     414    """
     415    args = token.contents.split()
     416    if len(args) != 2:
     417        raise TemplateSyntaxError("'Autoescape' tag requires exactly one argument.")
     418    arg = args[1]
     419    if arg not in (u'on', u'off'):
     420        raise TemplateSyntaxError("'Autoescape' argument should be 'on' or 'off'")
     421    nodelist = parser.parse(('endautoescape',))
     422    parser.delete_first_token()
     423    return AutoEscapeControlNode((arg == 'on'), nodelist)
     424autoescape = register.tag(autoescape)
     425
     426#@register.tag
     427def comment(parser, token):
     428    """
     429    Ignores everything between ``{% comment %}`` and ``{% endcomment %}``.
     430    """
     431    parser.skip_past('endcomment')
     432    return CommentNode()
     433comment = register.tag(comment)
     434
     435#@register.tag
     436def cycle(parser, token):
     437    """
     438    Cycles among the given strings each time this tag is encountered.
     439
     440    Within a loop, cycles among the given strings each time through
     441    the loop::
     442
     443        {% for o in some_list %}
     444            <tr class="{% cycle 'row1' 'row2' %}">
     445                ...
     446            </tr>
     447        {% endfor %}
     448
     449    Outside of a loop, give the values a unique name the first time you call
     450    it, then use that name each sucessive time through::
     451
     452            <tr class="{% cycle 'row1' 'row2' 'row3' as rowcolors %}">...</tr>
     453            <tr class="{% cycle rowcolors %}">...</tr>
     454            <tr class="{% cycle rowcolors %}">...</tr>
     455
     456    You can use any number of values, separated by spaces. Commas can also
     457    be used to separate values; if a comma is used, the cycle values are
     458    interpreted as literal strings.
     459    """
     460
     461    # Note: This returns the exact same node on each {% cycle name %} call;
     462    # that is, the node object returned from {% cycle a b c as name %} and the
     463    # one returned from {% cycle name %} are the exact same object. This
     464    # shouldn't cause problems (heh), but if it does, now you know.
     465    #
     466    # Ugly hack warning: This stuffs the named template dict into parser so
     467    # that names are only unique within each template (as opposed to using
     468    # a global variable, which would make cycle names have to be unique across
     469    # *all* templates.
     470
     471    args = token.split_contents()
     472
     473    if len(args) < 2:
     474        raise TemplateSyntaxError("'cycle' tag requires at least two arguments")
     475
     476    if ',' in args[1]:
     477        # Backwards compatibility: {% cycle a,b %} or {% cycle a,b as foo %}
     478        # case.
     479        args[1:2] = ['"%s"' % arg for arg in args[1].split(",")]
     480
     481    if len(args) == 2:
     482        # {% cycle foo %} case.
     483        name = args[1]
     484        if not hasattr(parser, '_namedCycleNodes'):
     485            raise TemplateSyntaxError("No named cycles in template. '%s' is not defined" % name)
     486        if not name in parser._namedCycleNodes:
     487            raise TemplateSyntaxError("Named cycle '%s' does not exist" % name)
     488        return parser._namedCycleNodes[name]
     489
     490    if len(args) > 4 and args[-2] == 'as':
     491        name = args[-1]
     492        node = CycleNode(args[1:-2], name)
     493        if not hasattr(parser, '_namedCycleNodes'):
     494            parser._namedCycleNodes = {}
     495        parser._namedCycleNodes[name] = node
     496    else:
     497        node = CycleNode(args[1:])
     498    return node
     499cycle = register.tag(cycle)
     500
     501def debug(parser, token):
     502    """
     503    Outputs a whole load of debugging information, including the current
     504    context and imported modules.
     505
     506    Sample usage::
     507
     508        <pre>
     509            {% debug %}
     510        </pre>
     511    """
     512    return DebugNode()
     513debug = register.tag(debug)
     514
     515#@register.tag(name="filter")
     516def do_filter(parser, token):
     517    """
     518    Filters the contents of the block through variable filters.
     519
     520    Filters can also be piped through each other, and they can have
     521    arguments -- just like in variable syntax.
     522
     523    Sample usage::
     524
     525        {% filter force_escape|lower %}
     526            This text will be HTML-escaped, and will appear in lowercase.
     527        {% endfilter %}
     528    """
     529    _, rest = token.contents.split(None, 1)
     530    filter_expr = parser.compile_filter("var|%s" % (rest))
     531    for func, unused in filter_expr.filters:
     532        if getattr(func, '_decorated_function', func).__name__ in ('escape', 'safe'):
     533            raise TemplateSyntaxError('"filter %s" is not permitted.  Use the "autoescape" tag instead.' % func.__name__)
     534    nodelist = parser.parse(('endfilter',))
     535    parser.delete_first_token()
     536    return FilterNode(filter_expr, nodelist)
     537do_filter = register.tag("filter", do_filter)
     538
     539#@register.tag
     540def firstof(parser, token):
     541    """
     542    Outputs the first variable passed that is not False.
     543
     544    Outputs nothing if all the passed variables are False.
     545
     546    Sample usage::
     547
     548        {% firstof var1 var2 var3 %}
     549
     550    This is equivalent to::
     551
     552        {% if var1 %}
     553            {{ var1 }}
     554        {% else %}{% if var2 %}
     555            {{ var2 }}
     556        {% else %}{% if var3 %}
     557            {{ var3 }}
     558        {% endif %}{% endif %}{% endif %}
     559
     560    but obviously much cleaner!
     561
     562    You can also use a literal string as a fallback value in case all
     563    passed variables are False::
     564
     565        {% firstof var1 var2 var3 "fallback value" %}
     566
     567    """
     568    bits = token.split_contents()[1:]
     569    if len(bits) < 1:
     570        raise TemplateSyntaxError("'firstof' statement requires at least one"
     571                                  " argument")
     572    return FirstOfNode(bits)
     573firstof = register.tag(firstof)
     574
     575#@register.tag(name="for")
     576def do_for(parser, token):
     577    """
     578    Loops over each item in an array.
     579
     580    For example, to display a list of athletes given ``athlete_list``::
     581
     582        <ul>
     583        {% for athlete in athlete_list %}
     584            <li>{{ athlete.name }}</li>
     585        {% endfor %}
     586        </ul>
     587
     588    You can loop over a list in reverse by using
     589    ``{% for obj in list reversed %}``.
     590
     591    You can also unpack multiple values from a two-dimensional array::
     592
     593        {% for key,value in dict.items %}
     594            {{ key }}: {{ value }}
     595        {% endfor %}
     596
     597    The for loop sets a number of variables available within the loop:
     598
     599        ==========================  ================================================
     600        Variable                    Description
     601        ==========================  ================================================
     602        ``forloop.counter``         The current iteration of the loop (1-indexed)
     603        ``forloop.counter0``        The current iteration of the loop (0-indexed)
     604        ``forloop.revcounter``      The number of iterations from the end of the
     605                                    loop (1-indexed)
     606        ``forloop.revcounter0``     The number of iterations from the end of the
     607                                    loop (0-indexed)
     608        ``forloop.first``           True if this is the first time through the loop
     609        ``forloop.last``            True if this is the last time through the loop
     610        ``forloop.parentloop``      For nested loops, this is the loop "above" the
     611                                    current one
     612        ==========================  ================================================
     613
     614    """
     615    bits = token.contents.split()
     616    if len(bits) < 4:
     617        raise TemplateSyntaxError("'for' statements should have at least four"
     618                                  " words: %s" % token.contents)
     619
     620    is_reversed = bits[-1] == 'reversed'
     621    in_index = is_reversed and -3 or -2
     622    if bits[in_index] != 'in':
     623        raise TemplateSyntaxError("'for' statements should use the format"
     624                                  " 'for x in y': %s" % token.contents)
     625
     626    loopvars = re.sub(r' *, *', ',', ' '.join(bits[1:in_index])).split(',')
     627    for var in loopvars:
     628        if not var or ' ' in var:
     629            raise TemplateSyntaxError("'for' tag received an invalid argument:"
     630                                      " %s" % token.contents)
     631
     632    sequence = parser.compile_filter(bits[in_index+1])
     633    nodelist_loop = parser.parse(('endfor',))
     634    parser.delete_first_token()
     635    return ForNode(loopvars, sequence, is_reversed, nodelist_loop)
     636do_for = register.tag("for", do_for)
     637
     638def do_ifequal(parser, token, negate):
     639    bits = list(token.split_contents())
     640    if len(bits) != 3:
     641        raise TemplateSyntaxError, "%r takes two arguments" % bits[0]
     642    end_tag = 'end' + bits[0]
     643    nodelist_true = parser.parse(('else', end_tag))
     644    token = parser.next_token()
     645    if token.contents == 'else':
     646        nodelist_false = parser.parse((end_tag,))
     647        parser.delete_first_token()
     648    else:
     649        nodelist_false = NodeList()
     650    return IfEqualNode(bits[1], bits[2], nodelist_true, nodelist_false, negate)
     651
     652#@register.tag
     653def ifequal(parser, token):
     654    """
     655    Outputs the contents of the block if the two arguments equal each other.
     656
     657    Examples::
     658
     659        {% ifequal user.id comment.user_id %}
     660            ...
     661        {% endifequal %}
     662
     663        {% ifnotequal user.id comment.user_id %}
     664            ...
     665        {% else %}
     666            ...
     667        {% endifnotequal %}
     668    """
     669    return do_ifequal(parser, token, False)
     670ifequal = register.tag(ifequal)
     671
     672#@register.tag
     673def ifnotequal(parser, token):
     674    """
     675    Outputs the contents of the block if the two arguments are not equal.
     676    See ifequal.
     677    """
     678    return do_ifequal(parser, token, True)
     679ifnotequal = register.tag(ifnotequal)
     680
     681#@register.tag(name="if")
     682def do_if(parser, token):
     683    """
     684    The ``{% if %}`` tag evaluates a variable, and if that variable is "true"
     685    (i.e., exists, is not empty, and is not a false boolean value), the
     686    contents of the block are output:
     687
     688    ::
     689
     690        {% if athlete_list %}
     691            Number of athletes: {{ athlete_list|count }}
     692        {% else %}
     693            No athletes.
     694        {% endif %}
     695
     696    In the above, if ``athlete_list`` is not empty, the number of athletes will
     697    be displayed by the ``{{ athlete_list|count }}`` variable.
     698
     699    As you can see, the ``if`` tag can take an option ``{% else %}`` clause
     700    that will be displayed if the test fails.
     701
     702    ``if`` tags may use ``or``, ``and`` or ``not`` to test a number of
     703    variables or to negate a given variable::
     704
     705        {% if not athlete_list %}
     706            There are no athletes.
     707        {% endif %}
     708
     709        {% if athlete_list or coach_list %}
     710            There are some athletes or some coaches.
     711        {% endif %}
     712
     713        {% if athlete_list and coach_list %}
     714            Both atheletes and coaches are available.
     715        {% endif %}
     716
     717        {% if not athlete_list or coach_list %}
     718            There are no athletes, or there are some coaches.
     719        {% endif %}
     720
     721        {% if athlete_list and not coach_list %}
     722            There are some athletes and absolutely no coaches.
     723        {% endif %}
     724
     725    ``if`` tags do not allow ``and`` and ``or`` clauses with the same tag,
     726    because the order of logic would be ambigous. For example, this is
     727    invalid::
     728
     729        {% if athlete_list and coach_list or cheerleader_list %}
     730
     731    If you need to combine ``and`` and ``or`` to do advanced logic, just use
     732    nested if tags. For example::
     733
     734        {% if athlete_list %}
     735            {% if coach_list or cheerleader_list %}
     736                We have athletes, and either coaches or cheerleaders!
     737            {% endif %}
     738        {% endif %}
     739    """
     740    bits = token.contents.split()
     741    del bits[0]
     742    if not bits:
     743        raise TemplateSyntaxError("'if' statement requires at least one argument")
     744    # Bits now looks something like this: ['a', 'or', 'not', 'b', 'or', 'c.d']
     745    bitstr = ' '.join(bits)
     746    boolpairs = bitstr.split(' and ')
     747    boolvars = []
     748    if len(boolpairs) == 1:
     749        link_type = IfNode.LinkTypes.or_
     750        boolpairs = bitstr.split(' or ')
     751    else:
     752        link_type = IfNode.LinkTypes.and_
     753        if ' or ' in bitstr:
     754            raise TemplateSyntaxError, "'if' tags can't mix 'and' and 'or'"
     755    for boolpair in boolpairs:
     756        if ' ' in boolpair:
     757            try:
     758                not_, boolvar = boolpair.split()
     759            except ValueError:
     760                raise TemplateSyntaxError, "'if' statement improperly formatted"
     761            if not_ != 'not':
     762                raise TemplateSyntaxError, "Expected 'not' in if statement"
     763            boolvars.append((True, parser.compile_filter(boolvar)))
     764        else:
     765            boolvars.append((False, parser.compile_filter(boolpair)))
     766    nodelist_true = parser.parse(('else', 'endif'))
     767    token = parser.next_token()
     768    if token.contents == 'else':
     769        nodelist_false = parser.parse(('endif',))
     770        parser.delete_first_token()
     771    else:
     772        nodelist_false = NodeList()
     773    return IfNode(boolvars, nodelist_true, nodelist_false, link_type)
     774do_if = register.tag("if", do_if)
     775
     776#@register.tag
     777def ifchanged(parser, token):
     778    """
     779    Checks if a value has changed from the last iteration of a loop.
     780
     781    The 'ifchanged' block tag is used within a loop. It has two possible uses.
     782
     783    1. Checks its own rendered contents against its previous state and only
     784       displays the content if it has changed. For example, this displays a
     785       list of days, only displaying the month if it changes::
     786
     787            <h1>Archive for {{ year }}</h1>
     788
     789            {% for date in days %}
     790                {% ifchanged %}<h3>{{ date|date:"F" }}</h3>{% endifchanged %}
     791                <a href="{{ date|date:"M/d"|lower }}/">{{ date|date:"j" }}</a>
     792            {% endfor %}
     793
     794    2. If given a variable, check whether that variable has changed.
     795       For example, the following shows the date every time it changes, but
     796       only shows the hour if both the hour and the date have changed::
     797
     798            {% for date in days %}
     799                {% ifchanged date.date %} {{ date.date }} {% endifchanged %}
     800                {% ifchanged date.hour date.date %}
     801                    {{ date.hour }}
     802                {% endifchanged %}
     803            {% endfor %}
     804    """
     805    bits = token.contents.split()
     806    nodelist = parser.parse(('endifchanged',))
     807    parser.delete_first_token()
     808    return IfChangedNode(nodelist, *bits[1:])
     809ifchanged = register.tag(ifchanged)
     810
     811#@register.tag
     812def ssi(parser, token):
     813    """
     814    Outputs the contents of a given file into the page.
     815
     816    Like a simple "include" tag, the ``ssi`` tag includes the contents
     817    of another file -- which must be specified using an absolute path --
     818    in the current page::
     819
     820        {% ssi /home/html/ljworld.com/includes/right_generic.html %}
     821
     822    If the optional "parsed" parameter is given, the contents of the included
     823    file are evaluated as template code, with the current context::
     824
     825        {% ssi /home/html/ljworld.com/includes/right_generic.html parsed %}
     826    """
     827    bits = token.contents.split()
     828    parsed = False
     829    if len(bits) not in (2, 3):
     830        raise TemplateSyntaxError("'ssi' tag takes one argument: the path to"
     831                                  " the file to be included")
     832    if len(bits) == 3:
     833        if bits[2] == 'parsed':
     834            parsed = True
     835        else:
     836            raise TemplateSyntaxError("Second (optional) argument to %s tag"
     837                                      " must be 'parsed'" % bits[0])
     838    return SsiNode(bits[1], parsed)
     839ssi = register.tag(ssi)
     840
     841#@register.tag
     842def load(parser, token):
     843    """
     844    Loads a custom template tag set.
     845
     846    For example, to load the template tags in
     847    ``django/templatetags/news/photos.py``::
     848
     849        {% load news.photos %}
     850    """
     851    bits = token.contents.split()
     852    for taglib in bits[1:]:
     853        # add the library to the parser
     854        try:
     855            lib = get_library(taglib)
     856            parser.add_library(lib)
     857        except InvalidTemplateLibrary, e:
     858            raise TemplateSyntaxError("'%s' is not a valid tag library: %s" %
     859                                      (taglib, e))
     860    return LoadNode()
     861load = register.tag(load)
     862
     863#@register.tag
     864def now(parser, token):
     865    """
     866    Displays the date, formatted according to the given string.
     867
     868    Uses the same format as PHP's ``date()`` function; see http://php.net/date
     869    for all the possible values.
     870
     871    Sample usage::
     872
     873        It is {% now "jS F Y H:i" %}
     874    """
     875    bits = token.contents.split('"')
     876    if len(bits) != 3:
     877        raise TemplateSyntaxError, "'now' statement takes one argument"
     878    format_string = bits[1]
     879    return NowNode(format_string)
     880now = register.tag(now)
     881
     882#@register.tag
     883def regroup(parser, token):
     884    """
     885    Regroups a list of alike objects by a common attribute.
     886
     887    This complex tag is best illustrated by use of an example:  say that
     888    ``people`` is a list of ``Person`` objects that have ``first_name``,
     889    ``last_name``, and ``gender`` attributes, and you'd like to display a list
     890    that looks like:
     891
     892        * Male:
     893            * George Bush
     894            * Bill Clinton
     895        * Female:
     896            * Margaret Thatcher
     897            * Colendeeza Rice
     898        * Unknown:
     899            * Pat Smith
     900
     901    The following snippet of template code would accomplish this dubious task::
     902
     903        {% regroup people by gender as grouped %}
     904        <ul>
     905        {% for group in grouped %}
     906            <li>{{ group.grouper }}
     907            <ul>
     908                {% for item in group.list %}
     909                <li>{{ item }}</li>
     910                {% endfor %}
     911            </ul>
     912        {% endfor %}
     913        </ul>
     914
     915    As you can see, ``{% regroup %}`` populates a variable with a list of
     916    objects with ``grouper`` and ``list`` attributes.  ``grouper`` contains the
     917    item that was grouped by; ``list`` contains the list of objects that share
     918    that ``grouper``.  In this case, ``grouper`` would be ``Male``, ``Female``
     919    and ``Unknown``, and ``list`` is the list of people with those genders.
     920
     921    Note that `{% regroup %}`` does not work when the list to be grouped is not
     922    sorted by the key you are grouping by!  This means that if your list of
     923    people was not sorted by gender, you'd need to make sure it is sorted
     924    before using it, i.e.::
     925
     926        {% regroup people|dictsort:"gender" by gender as grouped %}
     927
     928    """
     929    firstbits = token.contents.split(None, 3)
     930    if len(firstbits) != 4:
     931        raise TemplateSyntaxError, "'regroup' tag takes five arguments"
     932    target = parser.compile_filter(firstbits[1])
     933    if firstbits[2] != 'by':
     934        raise TemplateSyntaxError("second argument to 'regroup' tag must be 'by'")
     935    lastbits_reversed = firstbits[3][::-1].split(None, 2)
     936    if lastbits_reversed[1][::-1] != 'as':
     937        raise TemplateSyntaxError("next-to-last argument to 'regroup' tag must"
     938                                  " be 'as'")
     939
     940    expression = parser.compile_filter(lastbits_reversed[2][::-1])
     941
     942    var_name = lastbits_reversed[0][::-1]
     943    return RegroupNode(target, expression, var_name)
     944regroup = register.tag(regroup)
     945
     946def spaceless(parser, token):
     947    """
     948    Removes whitespace between HTML tags, including tab and newline characters.
     949
     950    Example usage::
     951
     952        {% spaceless %}
     953            <p>
     954                <a href="foo/">Foo</a>
     955            </p>
     956        {% endspaceless %}
     957
     958    This example would return this HTML::
     959
     960        <p><a href="foo/">Foo</a></p>
     961
     962    Only space between *tags* is normalized -- not space between tags and text.
     963    In this example, the space around ``Hello`` won't be stripped::
     964
     965        {% spaceless %}
     966            <strong>
     967                Hello
     968            </strong>
     969        {% endspaceless %}
     970    """
     971    nodelist = parser.parse(('endspaceless',))
     972    parser.delete_first_token()
     973    return SpacelessNode(nodelist)
     974spaceless = register.tag(spaceless)
     975
     976#@register.tag
     977def templatetag(parser, token):
     978    """
     979    Outputs one of the bits used to compose template tags.
     980
     981    Since the template system has no concept of "escaping", to display one of
     982    the bits used in template tags, you must use the ``{% templatetag %}`` tag.
     983
     984    The argument tells which template bit to output:
     985
     986        ==================  =======
     987        Argument            Outputs
     988        ==================  =======
     989        ``openblock``       ``{%``
     990        ``closeblock``      ``%}``
     991        ``openvariable``    ``{{``
     992        ``closevariable``   ``}}``
     993        ``openbrace``       ``{``
     994        ``closebrace``      ``}``
     995        ``opencomment``     ``{#``
     996        ``closecomment``    ``#}``
     997        ==================  =======
     998    """
     999    bits = token.contents.split()
     1000    if len(bits) != 2:
     1001        raise TemplateSyntaxError, "'templatetag' statement takes one argument"
     1002    tag = bits[1]
     1003    if tag not in TemplateTagNode.mapping:
     1004        raise TemplateSyntaxError("Invalid templatetag argument: '%s'."
     1005                                  " Must be one of: %s" %
     1006                                  (tag, TemplateTagNode.mapping.keys()))
     1007    return TemplateTagNode(tag)
     1008templatetag = register.tag(templatetag)
     1009
     1010def url(parser, token):
     1011    """
     1012    Returns an absolute URL matching given view with its parameters.
     1013
     1014    This is a way to define links that aren't tied to a particular URL
     1015    configuration::
     1016
     1017        {% url path.to.some_view arg1,arg2,name1=value1 %}
     1018
     1019    The first argument is a path to a view. It can be an absolute python path
     1020    or just ``app_name.view_name`` without the project name if the view is
     1021    located inside the project.  Other arguments are comma-separated values
     1022    that will be filled in place of positional and keyword arguments in the
     1023    URL. All arguments for the URL should be present.
     1024
     1025    For example if you have a view ``app_name.client`` taking client's id and
     1026    the corresponding line in a URLconf looks like this::
     1027
     1028        ('^client/(\d+)/$', 'app_name.client')
     1029
     1030    and this app's URLconf is included into the project's URLconf under some
     1031    path::
     1032
     1033        ('^clients/', include('project_name.app_name.urls'))
     1034
     1035    then in a template you can create a link for a certain client like this::
     1036
     1037        {% url app_name.client client.id %}
     1038
     1039    The URL will look like ``/clients/client/123/``.
     1040    """
     1041    bits = token.contents.split(' ', 2)
     1042    if len(bits) < 2:
     1043        raise TemplateSyntaxError("'%s' takes at least one argument"
     1044                                  " (path to a view)" % bits[0])
     1045    args = []
     1046    kwargs = {}
     1047    if len(bits) > 2:
     1048        for arg in bits[2].split(','):
     1049            if '=' in arg:
     1050                k, v = arg.split('=', 1)
     1051                k = k.strip()
     1052                kwargs[k] = parser.compile_filter(v)
     1053            else:
     1054                args.append(parser.compile_filter(arg))
     1055    return URLNode(bits[1], args, kwargs)
     1056url = register.tag(url)
     1057
     1058#@register.tag
     1059def widthratio(parser, token):
     1060    """
     1061    For creating bar charts and such, this tag calculates the ratio of a given
     1062    value to a maximum value, and then applies that ratio to a constant.
     1063
     1064    For example::
     1065
     1066        <img src='bar.gif' height='10' width='{% widthratio this_value max_value 100 %}' />
     1067
     1068    Above, if ``this_value`` is 175 and ``max_value`` is 200, the the image in
     1069    the above example will be 88 pixels wide (because 175/200 = .875;
     1070    .875 * 100 = 87.5 which is rounded up to 88).
     1071    """
     1072    bits = token.contents.split()
     1073    if len(bits) != 4:
     1074        raise TemplateSyntaxError("widthratio takes three arguments")
     1075    tag, this_value_expr, max_value_expr, max_width = bits
     1076    try:
     1077        max_width = int(max_width)
     1078    except ValueError:
     1079        raise TemplateSyntaxError("widthratio final argument must be an integer")
     1080    return WidthRatioNode(parser.compile_filter(this_value_expr),
     1081                          parser.compile_filter(max_value_expr), max_width)
     1082widthratio = register.tag(widthratio)
     1083
     1084#@register.tag
     1085def do_with(parser, token):
     1086    """
     1087    Adds a value to the context (inside of this block) for caching and easy
     1088    access.
     1089
     1090    For example::
     1091
     1092        {% with person.some_sql_method as total %}
     1093            {{ total }} object{{ total|pluralize }}
     1094        {% endwith %}
     1095    """
     1096    bits = list(token.split_contents())
     1097    if len(bits) != 4 or bits[2] != "as":
     1098        raise TemplateSyntaxError("%r expected format is 'value as name'" %
     1099                                  bits[0])
     1100    var = parser.compile_filter(bits[1])
     1101    name = bits[3]
     1102    nodelist = parser.parse(('endwith',))
     1103    parser.delete_first_token()
     1104    return WithNode(var, name, nodelist)
     1105do_with = register.tag('with', do_with)
  • tests/regressiontests/templates/tests.py

    diff --git a/tests/regressiontests/templates/tests.py b/tests/regressiontests/templates/tests.py
    index 186b8aa..ac0912d 100644
    a b def do_echo(parser, token):  
    5151
    5252register.tag("echo", do_echo)
    5353
    54 template.libraries['django.templatetags.testtags'] = register
     54template.libraries['testtags'] = register
    5555
    5656#####################################
    5757# Helper objects for template tests #
Back to Top