Ticket #6587: template_libs_t6587_r12288_v1.diff

File template_libs_t6587_r12288_v1.diff, 145.9 KB (added by Andrew Badr, 14 years ago)
  • django/template/__init__.py

    diff --git a/django/template/__init__.py b/django/template/__init__.py
    index 4c386be..f16f739 100644
    a b u'<html><h1>Hello</h1></html>'  
    4949u'<html></html>'
    5050"""
    5151import re
     52import imp
    5253from inspect import getargspec
    5354
    5455from django.conf import settings
    5556from django.template.context import Context, RequestContext, ContextPopException
     57from django.templatetags import get_templatetags_modules
    5658from django.utils.importlib import import_module
    5759from django.utils.itercompat import is_iterable
    5860from django.utils.functional import curry, Promise
    class Library(object):  
    965967            self.tag(getattr(func, "_decorated_function", func).__name__, compile_func)
    966968            return func
    967969        return dec
    968 
    969 def get_library(module_name):
    970     lib = libraries.get(module_name, None)
     970def import_library(taglib_module):
     971    components = taglib_module.split('.')
     972    parent_components = components[:-1]
     973    parent_module = '.'.join(parent_components)
     974    try:
     975        mod = __import__(parent_module, {}, {}, [parent_components[-1]])
     976        imp.find_module(components[-1], mod.__path__)
     977    except ImportError:
     978        return None
     979    mod = __import__(taglib_module, {}, {}, [components[-1]])
     980    try:
     981        return mod.register
     982    except AttributeError:
     983        raise InvalidTemplateLibrary("Template library %s does not have a variable named 'register'" % taglib_module)
     984
     985def get_library(library_name):
     986    lib = libraries.get(library_name, None)
    971987    if not lib:
    972         try:
    973             mod = import_module(module_name)
    974         except ImportError, e:
    975             raise InvalidTemplateLibrary("Could not load template library from %s, %s" % (module_name, e))
    976         try:
    977             lib = mod.register
    978             libraries[module_name] = lib
    979         except AttributeError:
    980             raise InvalidTemplateLibrary("Template library %s does not have a variable named 'register'" % module_name)
     988        """
     989        If library is not already loaded loop over all templatetags modules to locate it.
     990
     991        {% load somelib %} and {% load someotherlib %} loops twice.
     992
     993        Subsequent loads eg. {% load somelib %} in the same thread will grab the cached
     994        module from libraries.
     995        """
     996        templatetags_modules = get_templatetags_modules()
     997        tried_modules = []
     998        for module in templatetags_modules:
     999            taglib_module = str('%s.%s' % (module, library_name))
     1000            tried_modules.append(taglib_module)
     1001            lib = import_library(taglib_module)
     1002            if lib:
     1003                libraries[library_name] = lib
     1004                break
     1005        if not lib:
     1006            raise InvalidTemplateLibrary("Template library %s not found, tried %s" % (library_name, ','.join(tried_modules)))
    9811007    return lib
    9821008
    983 def add_to_builtins(module_name):
    984     builtins.append(get_library(module_name))
     1009def add_to_builtins(module):
     1010    builtins.append(import_library(module))
    9851011
    986 add_to_builtins('django.template.defaulttags')
    987 add_to_builtins('django.template.defaultfilters')
     1012add_to_builtins('django.templatetags.defaulttags')
     1013add_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 0ba16bc..0000000
    + -  
    1 """Default variable filters."""
    2 
    3 import re
    4 
    5 try:
    6     from decimal import Decimal, InvalidOperation, ROUND_HALF_UP
    7 except ImportError:
    8     from django.utils._decimal import Decimal, InvalidOperation, ROUND_HALF_UP
    9 
    10 import random as random_module
    11 try:
    12     from functools import wraps
    13 except ImportError:
    14     from django.utils.functional import wraps  # Python 2.3, 2.4 fallback.
    15 
    16 from django.template import Variable, Library
    17 from django.conf import settings
    18 from django.utils import formats
    19 from django.utils.translation import ugettext, ungettext
    20 from django.utils.encoding import force_unicode, iri_to_uri
    21 from django.utils.safestring import mark_safe, SafeData
    22 
    23 register = Library()
    24 
    25 #######################
    26 # STRING DECORATOR    #
    27 #######################
    28 
    29 def stringfilter(func):
    30     """
    31     Decorator for filters which should only receive unicode objects. The object
    32     passed as the first positional argument will be converted to a unicode
    33     object.
    34     """
    35     def _dec(*args, **kwargs):
    36         if args:
    37             args = list(args)
    38             args[0] = force_unicode(args[0])
    39             if isinstance(args[0], SafeData) and getattr(func, 'is_safe', False):
    40                 return mark_safe(func(*args, **kwargs))
    41         return func(*args, **kwargs)
    42 
    43     # Include a reference to the real function (used to check original
    44     # arguments by the template parser).
    45     _dec._decorated_function = getattr(func, '_decorated_function', func)
    46     for attr in ('is_safe', 'needs_autoescape'):
    47         if hasattr(func, attr):
    48             setattr(_dec, attr, getattr(func, attr))
    49     return wraps(func)(_dec)
    50 
    51 ###################
    52 # STRINGS         #
    53 ###################
    54 
    55 def addslashes(value):
    56     """
    57     Adds slashes before quotes. Useful for escaping strings in CSV, for
    58     example. Less useful for escaping JavaScript; use the ``escapejs``
    59     filter instead.
    60     """
    61     return value.replace('\\', '\\\\').replace('"', '\\"').replace("'", "\\'")
    62 addslashes.is_safe = True
    63 addslashes = stringfilter(addslashes)
    64 
    65 def capfirst(value):
    66     """Capitalizes the first character of the value."""
    67     return value and value[0].upper() + value[1:]
    68 capfirst.is_safe=True
    69 capfirst = stringfilter(capfirst)
    70 
    71 _base_js_escapes = (
    72     ('\\', r'\x5C'),
    73     ('\'', r'\x27'),
    74     ('"', r'\x22'),
    75     ('>', r'\x3E'),
    76     ('<', r'\x3C'),
    77     ('&', r'\x26'),
    78     ('=', r'\x3D'),
    79     ('-', r'\x2D'),
    80     (';', r'\x3B'),
    81     (u'\u2028', r'\u2028'),
    82     (u'\u2029', r'\u2029')
    83 )
    84 
    85 # Escape every ASCII character with a value less than 32.
    86 _js_escapes = (_base_js_escapes +
    87                tuple([('%c' % z, '\\x%02X' % z) for z in range(32)]))
    88 
    89 def escapejs(value):
    90     """Hex encodes characters for use in JavaScript strings."""
    91     for bad, good in _js_escapes:
    92         value = value.replace(bad, good)
    93     return value
    94 escapejs = stringfilter(escapejs)
    95 
    96 def fix_ampersands(value):
    97     """Replaces ampersands with ``&amp;`` entities."""
    98     from django.utils.html import fix_ampersands
    99     return fix_ampersands(value)
    100 fix_ampersands.is_safe=True
    101 fix_ampersands = stringfilter(fix_ampersands)
    102 
    103 # Values for testing floatformat input against infinity and NaN representations,
    104 # which differ across platforms and Python versions.  Some (i.e. old Windows
    105 # ones) are not recognized by Decimal but we want to return them unchanged vs.
    106 # returning an empty string as we do for completley invalid input.  Note these
    107 # need to be built up from values that are not inf/nan, since inf/nan values do
    108 # not reload properly from .pyc files on Windows prior to some level of Python 2.5
    109 # (see Python Issue757815 and Issue1080440).
    110 pos_inf = 1e200 * 1e200
    111 neg_inf = -1e200 * 1e200
    112 nan = (1e200 * 1e200) / (1e200 * 1e200)
    113 special_floats = [str(pos_inf), str(neg_inf), str(nan)]
    114 
    115 def floatformat(text, arg=-1):
    116     """
    117     Displays a float to a specified number of decimal places.
    118 
    119     If called without an argument, it displays the floating point number with
    120     one decimal place -- but only if there's a decimal place to be displayed:
    121 
    122     * num1 = 34.23234
    123     * num2 = 34.00000
    124     * num3 = 34.26000
    125     * {{ num1|floatformat }} displays "34.2"
    126     * {{ num2|floatformat }} displays "34"
    127     * {{ num3|floatformat }} displays "34.3"
    128 
    129     If arg is positive, it will always display exactly arg number of decimal
    130     places:
    131 
    132     * {{ num1|floatformat:3 }} displays "34.232"
    133     * {{ num2|floatformat:3 }} displays "34.000"
    134     * {{ num3|floatformat:3 }} displays "34.260"
    135 
    136     If arg is negative, it will display arg number of decimal places -- but
    137     only if there are places to be displayed:
    138 
    139     * {{ num1|floatformat:"-3" }} displays "34.232"
    140     * {{ num2|floatformat:"-3" }} displays "34"
    141     * {{ num3|floatformat:"-3" }} displays "34.260"
    142 
    143     If the input float is infinity or NaN, the (platform-dependent) string
    144     representation of that value will be displayed.
    145     """
    146 
    147     try:
    148         input_val = force_unicode(text)
    149         d = Decimal(input_val)
    150     except UnicodeEncodeError:
    151         return u''
    152     except InvalidOperation:
    153         if input_val in special_floats:
    154             return input_val
    155         try:
    156             d = Decimal(force_unicode(float(text)))
    157         except (ValueError, InvalidOperation, TypeError, UnicodeEncodeError):
    158             return u''
    159     try:
    160         p = int(arg)
    161     except ValueError:
    162         return input_val
    163 
    164     try:
    165         m = int(d) - d
    166     except (ValueError, OverflowError, InvalidOperation):
    167         return input_val
    168 
    169     if not m and p < 0:
    170         return mark_safe(formats.number_format(u'%d' % (int(d)), 0))
    171 
    172     if p == 0:
    173         exp = Decimal(1)
    174     else:
    175         exp = Decimal('1.0') / (Decimal(10) ** abs(p))
    176     try:
    177         return mark_safe(formats.number_format(u'%s' % str(d.quantize(exp, ROUND_HALF_UP)), abs(p)))
    178     except InvalidOperation:
    179         return input_val
    180 floatformat.is_safe = True
    181 
    182 def iriencode(value):
    183     """Escapes an IRI value for use in a URL."""
    184     return force_unicode(iri_to_uri(value))
    185 iriencode.is_safe = True
    186 iriencode = stringfilter(iriencode)
    187 
    188 def linenumbers(value, autoescape=None):
    189     """Displays text with line numbers."""
    190     from django.utils.html import escape
    191     lines = value.split(u'\n')
    192     # Find the maximum width of the line count, for use with zero padding
    193     # string format command
    194     width = unicode(len(unicode(len(lines))))
    195     if not autoescape or isinstance(value, SafeData):
    196         for i, line in enumerate(lines):
    197             lines[i] = (u"%0" + width  + u"d. %s") % (i + 1, line)
    198     else:
    199         for i, line in enumerate(lines):
    200             lines[i] = (u"%0" + width  + u"d. %s") % (i + 1, escape(line))
    201     return mark_safe(u'\n'.join(lines))
    202 linenumbers.is_safe = True
    203 linenumbers.needs_autoescape = True
    204 linenumbers = stringfilter(linenumbers)
    205 
    206 def lower(value):
    207     """Converts a string into all lowercase."""
    208     return value.lower()
    209 lower.is_safe = True
    210 lower = stringfilter(lower)
    211 
    212 def make_list(value):
    213     """
    214     Returns the value turned into a list.
    215 
    216     For an integer, it's a list of digits.
    217     For a string, it's a list of characters.
    218     """
    219     return list(value)
    220 make_list.is_safe = False
    221 make_list = stringfilter(make_list)
    222 
    223 def slugify(value):
    224     """
    225     Normalizes string, converts to lowercase, removes non-alpha characters,
    226     and converts spaces to hyphens.
    227     """
    228     import unicodedata
    229     value = unicodedata.normalize('NFKD', value).encode('ascii', 'ignore')
    230     value = unicode(re.sub('[^\w\s-]', '', value).strip().lower())
    231     return mark_safe(re.sub('[-\s]+', '-', value))
    232 slugify.is_safe = True
    233 slugify = stringfilter(slugify)
    234 
    235 def stringformat(value, arg):
    236     """
    237     Formats the variable according to the arg, a string formatting specifier.
    238 
    239     This specifier uses Python string formating syntax, with the exception that
    240     the leading "%" is dropped.
    241 
    242     See http://docs.python.org/lib/typesseq-strings.html for documentation
    243     of Python string formatting
    244     """
    245     try:
    246         return (u"%" + unicode(arg)) % value
    247     except (ValueError, TypeError):
    248         return u""
    249 stringformat.is_safe = True
    250 
    251 def title(value):
    252     """Converts a string into titlecase."""
    253     t = re.sub("([a-z])'([A-Z])", lambda m: m.group(0).lower(), value.title())
    254     return re.sub("\d([A-Z])", lambda m: m.group(0).lower(), t)
    255 title.is_safe = True
    256 title = stringfilter(title)
    257 
    258 def truncatewords(value, arg):
    259     """
    260     Truncates a string after a certain number of words.
    261 
    262     Argument: Number of words to truncate after.
    263     """
    264     from django.utils.text import truncate_words
    265     try:
    266         length = int(arg)
    267     except ValueError: # Invalid literal for int().
    268         return value # Fail silently.
    269     return truncate_words(value, length)
    270 truncatewords.is_safe = True
    271 truncatewords = stringfilter(truncatewords)
    272 
    273 def truncatewords_html(value, arg):
    274     """
    275     Truncates HTML after a certain number of words.
    276 
    277     Argument: Number of words to truncate after.
    278     """
    279     from django.utils.text import truncate_html_words
    280     try:
    281         length = int(arg)
    282     except ValueError: # invalid literal for int()
    283         return value # Fail silently.
    284     return truncate_html_words(value, length)
    285 truncatewords_html.is_safe = True
    286 truncatewords_html = stringfilter(truncatewords_html)
    287 
    288 def upper(value):
    289     """Converts a string into all uppercase."""
    290     return value.upper()
    291 upper.is_safe = False
    292 upper = stringfilter(upper)
    293 
    294 def urlencode(value):
    295     """Escapes a value for use in a URL."""
    296     from django.utils.http import urlquote
    297     return urlquote(value)
    298 urlencode.is_safe = False
    299 urlencode = stringfilter(urlencode)
    300 
    301 def urlize(value, autoescape=None):
    302     """Converts URLs in plain text into clickable links."""
    303     from django.utils.html import urlize
    304     return mark_safe(urlize(value, nofollow=True, autoescape=autoescape))
    305 urlize.is_safe=True
    306 urlize.needs_autoescape = True
    307 urlize = stringfilter(urlize)
    308 
    309 def urlizetrunc(value, limit, autoescape=None):
    310     """
    311     Converts URLs into clickable links, truncating URLs to the given character
    312     limit, and adding 'rel=nofollow' attribute to discourage spamming.
    313 
    314     Argument: Length to truncate URLs to.
    315     """
    316     from django.utils.html import urlize
    317     return mark_safe(urlize(value, trim_url_limit=int(limit), nofollow=True,
    318                             autoescape=autoescape))
    319 urlizetrunc.is_safe = True
    320 urlizetrunc.needs_autoescape = True
    321 urlizetrunc = stringfilter(urlizetrunc)
    322 
    323 def wordcount(value):
    324     """Returns the number of words."""
    325     return len(value.split())
    326 wordcount.is_safe = False
    327 wordcount = stringfilter(wordcount)
    328 
    329 def wordwrap(value, arg):
    330     """
    331     Wraps words at specified line length.
    332 
    333     Argument: number of characters to wrap the text at.
    334     """
    335     from django.utils.text import wrap
    336     return wrap(value, int(arg))
    337 wordwrap.is_safe = True
    338 wordwrap = stringfilter(wordwrap)
    339 
    340 def ljust(value, arg):
    341     """
    342     Left-aligns the value in a field of a given width.
    343 
    344     Argument: field size.
    345     """
    346     return value.ljust(int(arg))
    347 ljust.is_safe = True
    348 ljust = stringfilter(ljust)
    349 
    350 def rjust(value, arg):
    351     """
    352     Right-aligns the value in a field of a given width.
    353 
    354     Argument: field size.
    355     """
    356     return value.rjust(int(arg))
    357 rjust.is_safe = True
    358 rjust = stringfilter(rjust)
    359 
    360 def center(value, arg):
    361     """Centers the value in a field of a given width."""
    362     return value.center(int(arg))
    363 center.is_safe = True
    364 center = stringfilter(center)
    365 
    366 def cut(value, arg):
    367     """
    368     Removes all values of arg from the given string.
    369     """
    370     safe = isinstance(value, SafeData)
    371     value = value.replace(arg, u'')
    372     if safe and arg != ';':
    373         return mark_safe(value)
    374     return value
    375 cut = stringfilter(cut)
    376 
    377 ###################
    378 # HTML STRINGS    #
    379 ###################
    380 
    381 def escape(value):
    382     """
    383     Marks the value as a string that should not be auto-escaped.
    384     """
    385     from django.utils.safestring import mark_for_escaping
    386     return mark_for_escaping(value)
    387 escape.is_safe = True
    388 escape = stringfilter(escape)
    389 
    390 def force_escape(value):
    391     """
    392     Escapes a string's HTML. This returns a new string containing the escaped
    393     characters (as opposed to "escape", which marks the content for later
    394     possible escaping).
    395     """
    396     from django.utils.html import escape
    397     return mark_safe(escape(value))
    398 force_escape = stringfilter(force_escape)
    399 force_escape.is_safe = True
    400 
    401 def linebreaks(value, autoescape=None):
    402     """
    403     Replaces line breaks in plain text with appropriate HTML; a single
    404     newline becomes an HTML line break (``<br />``) and a new line
    405     followed by a blank line becomes a paragraph break (``</p>``).
    406     """
    407     from django.utils.html import linebreaks
    408     autoescape = autoescape and not isinstance(value, SafeData)
    409     return mark_safe(linebreaks(value, autoescape))
    410 linebreaks.is_safe = True
    411 linebreaks.needs_autoescape = True
    412 linebreaks = stringfilter(linebreaks)
    413 
    414 def linebreaksbr(value, autoescape=None):
    415     """
    416     Converts all newlines in a piece of plain text to HTML line breaks
    417     (``<br />``).
    418     """
    419     if autoescape and not isinstance(value, SafeData):
    420         from django.utils.html import escape
    421         value = escape(value)
    422     return mark_safe(value.replace('\n', '<br />'))
    423 linebreaksbr.is_safe = True
    424 linebreaksbr.needs_autoescape = True
    425 linebreaksbr = stringfilter(linebreaksbr)
    426 
    427 def safe(value):
    428     """
    429     Marks the value as a string that should not be auto-escaped.
    430     """
    431     return mark_safe(value)
    432 safe.is_safe = True
    433 safe = stringfilter(safe)
    434 
    435 def safeseq(value):
    436     """
    437     A "safe" filter for sequences. Marks each element in the sequence,
    438     individually, as safe, after converting them to unicode. Returns a list
    439     with the results.
    440     """
    441     return [mark_safe(force_unicode(obj)) for obj in value]
    442 safeseq.is_safe = True
    443 
    444 def removetags(value, tags):
    445     """Removes a space separated list of [X]HTML tags from the output."""
    446     tags = [re.escape(tag) for tag in tags.split()]
    447     tags_re = u'(%s)' % u'|'.join(tags)
    448     starttag_re = re.compile(ur'<%s(/?>|(\s+[^>]*>))' % tags_re, re.U)
    449     endtag_re = re.compile(u'</%s>' % tags_re)
    450     value = starttag_re.sub(u'', value)
    451     value = endtag_re.sub(u'', value)
    452     return value
    453 removetags.is_safe = True
    454 removetags = stringfilter(removetags)
    455 
    456 def striptags(value):
    457     """Strips all [X]HTML tags."""
    458     from django.utils.html import strip_tags
    459     return strip_tags(value)
    460 striptags.is_safe = True
    461 striptags = stringfilter(striptags)
    462 
    463 ###################
    464 # LISTS           #
    465 ###################
    466 
    467 def dictsort(value, arg):
    468     """
    469     Takes a list of dicts, returns that list sorted by the property given in
    470     the argument.
    471     """
    472     var_resolve = Variable(arg).resolve
    473     decorated = [(var_resolve(item), item) for item in value]
    474     decorated.sort()
    475     return [item[1] for item in decorated]
    476 dictsort.is_safe = False
    477 
    478 def dictsortreversed(value, arg):
    479     """
    480     Takes a list of dicts, returns that list sorted in reverse order by the
    481     property given in the argument.
    482     """
    483     var_resolve = Variable(arg).resolve
    484     decorated = [(var_resolve(item), item) for item in value]
    485     decorated.sort()
    486     decorated.reverse()
    487     return [item[1] for item in decorated]
    488 dictsortreversed.is_safe = False
    489 
    490 def first(value):
    491     """Returns the first item in a list."""
    492     try:
    493         return value[0]
    494     except IndexError:
    495         return u''
    496 first.is_safe = False
    497 
    498 def join(value, arg, autoescape=None):
    499     """
    500     Joins a list with a string, like Python's ``str.join(list)``.
    501     """
    502     value = map(force_unicode, value)
    503     if autoescape:
    504         from django.utils.html import conditional_escape
    505         value = [conditional_escape(v) for v in value]
    506     try:
    507         data = arg.join(value)
    508     except AttributeError: # fail silently but nicely
    509         return value
    510     return mark_safe(data)
    511 join.is_safe = True
    512 join.needs_autoescape = True
    513 
    514 def last(value):
    515     "Returns the last item in a list"
    516     try:
    517         return value[-1]
    518     except IndexError:
    519         return u''
    520 last.is_safe = True
    521 
    522 def length(value):
    523     """Returns the length of the value - useful for lists."""
    524     try:
    525         return len(value)
    526     except (ValueError, TypeError):
    527         return ''
    528 length.is_safe = True
    529 
    530 def length_is(value, arg):
    531     """Returns a boolean of whether the value's length is the argument."""
    532     try:
    533         return len(value) == int(arg)
    534     except (ValueError, TypeError):
    535         return ''
    536 length_is.is_safe = False
    537 
    538 def random(value):
    539     """Returns a random item from the list."""
    540     return random_module.choice(value)
    541 random.is_safe = True
    542 
    543 def slice_(value, arg):
    544     """
    545     Returns a slice of the list.
    546 
    547     Uses the same syntax as Python's list slicing; see
    548     http://diveintopython.org/native_data_types/lists.html#odbchelper.list.slice
    549     for an introduction.
    550     """
    551     try:
    552         bits = []
    553         for x in arg.split(u':'):
    554             if len(x) == 0:
    555                 bits.append(None)
    556             else:
    557                 bits.append(int(x))
    558         return value[slice(*bits)]
    559 
    560     except (ValueError, TypeError):
    561         return value # Fail silently.
    562 slice_.is_safe = True
    563 
    564 def unordered_list(value, autoescape=None):
    565     """
    566     Recursively takes a self-nested list and returns an HTML unordered list --
    567     WITHOUT opening and closing <ul> tags.
    568 
    569     The list is assumed to be in the proper format. For example, if ``var``
    570     contains: ``['States', ['Kansas', ['Lawrence', 'Topeka'], 'Illinois']]``,
    571     then ``{{ var|unordered_list }}`` would return::
    572 
    573         <li>States
    574         <ul>
    575                 <li>Kansas
    576                 <ul>
    577                         <li>Lawrence</li>
    578                         <li>Topeka</li>
    579                 </ul>
    580                 </li>
    581                 <li>Illinois</li>
    582         </ul>
    583         </li>
    584     """
    585     if autoescape:
    586         from django.utils.html import conditional_escape
    587         escaper = conditional_escape
    588     else:
    589         escaper = lambda x: x
    590     def convert_old_style_list(list_):
    591         """
    592         Converts old style lists to the new easier to understand format.
    593 
    594         The old list format looked like:
    595             ['Item 1', [['Item 1.1', []], ['Item 1.2', []]]
    596 
    597         And it is converted to:
    598             ['Item 1', ['Item 1.1', 'Item 1.2]]
    599         """
    600         if not isinstance(list_, (tuple, list)) or len(list_) != 2:
    601             return list_, False
    602         first_item, second_item = list_
    603         if second_item == []:
    604             return [first_item], True
    605         old_style_list = True
    606         new_second_item = []
    607         for sublist in second_item:
    608             item, old_style_list = convert_old_style_list(sublist)
    609             if not old_style_list:
    610                 break
    611             new_second_item.extend(item)
    612         if old_style_list:
    613             second_item = new_second_item
    614         return [first_item, second_item], old_style_list
    615     def _helper(list_, tabs=1):
    616         indent = u'\t' * tabs
    617         output = []
    618 
    619         list_length = len(list_)
    620         i = 0
    621         while i < list_length:
    622             title = list_[i]
    623             sublist = ''
    624             sublist_item = None
    625             if isinstance(title, (list, tuple)):
    626                 sublist_item = title
    627                 title = ''
    628             elif i < list_length - 1:
    629                 next_item = list_[i+1]
    630                 if next_item and isinstance(next_item, (list, tuple)):
    631                     # The next item is a sub-list.
    632                     sublist_item = next_item
    633                     # We've processed the next item now too.
    634                     i += 1
    635             if sublist_item:
    636                 sublist = _helper(sublist_item, tabs+1)
    637                 sublist = '\n%s<ul>\n%s\n%s</ul>\n%s' % (indent, sublist,
    638                                                          indent, indent)
    639             output.append('%s<li>%s%s</li>' % (indent,
    640                     escaper(force_unicode(title)), sublist))
    641             i += 1
    642         return '\n'.join(output)
    643     value, converted = convert_old_style_list(value)
    644     return mark_safe(_helper(value))
    645 unordered_list.is_safe = True
    646 unordered_list.needs_autoescape = True
    647 
    648 ###################
    649 # INTEGERS        #
    650 ###################
    651 
    652 def add(value, arg):
    653     """Adds the arg to the value."""
    654     return int(value) + int(arg)
    655 add.is_safe = False
    656 
    657 def get_digit(value, arg):
    658     """
    659     Given a whole number, returns the requested digit of it, where 1 is the
    660     right-most digit, 2 is the second-right-most digit, etc. Returns the
    661     original value for invalid input (if input or argument is not an integer,
    662     or if argument is less than 1). Otherwise, output is always an integer.
    663     """
    664     try:
    665         arg = int(arg)
    666         value = int(value)
    667     except ValueError:
    668         return value # Fail silently for an invalid argument
    669     if arg < 1:
    670         return value
    671     try:
    672         return int(str(value)[-arg])
    673     except IndexError:
    674         return 0
    675 get_digit.is_safe = False
    676 
    677 ###################
    678 # DATES           #
    679 ###################
    680 
    681 def date(value, arg=None):
    682     """Formats a date according to the given format."""
    683     from django.utils.dateformat import format
    684     if not value:
    685         return u''
    686     if arg is None:
    687         arg = settings.DATE_FORMAT
    688     try:
    689         return formats.date_format(value, arg)
    690     except AttributeError:
    691         try:
    692             return format(value, arg)
    693         except AttributeError:
    694             return ''
    695 date.is_safe = False
    696 
    697 def time(value, arg=None):
    698     """Formats a time according to the given format."""
    699     from django.utils import dateformat
    700     if value in (None, u''):
    701         return u''
    702     if arg is None:
    703         arg = settings.TIME_FORMAT
    704     try:
    705         return formats.time_format(value, arg)
    706     except AttributeError:
    707         try:
    708             return dateformat.time_format(value, arg)
    709         except AttributeError:
    710             return ''
    711 time.is_safe = False
    712 
    713 def timesince(value, arg=None):
    714     """Formats a date as the time since that date (i.e. "4 days, 6 hours")."""
    715     from django.utils.timesince import timesince
    716     if not value:
    717         return u''
    718     try:
    719         if arg:
    720             return timesince(value, arg)
    721         return timesince(value)
    722     except (ValueError, TypeError):
    723         return u''
    724 timesince.is_safe = False
    725 
    726 def timeuntil(value, arg=None):
    727     """Formats a date as the time until that date (i.e. "4 days, 6 hours")."""
    728     from django.utils.timesince import timeuntil
    729     from datetime import datetime
    730     if not value:
    731         return u''
    732     try:
    733         return timeuntil(value, arg)
    734     except (ValueError, TypeError):
    735         return u''
    736 timeuntil.is_safe = False
    737 
    738 ###################
    739 # LOGIC           #
    740 ###################
    741 
    742 def default(value, arg):
    743     """If value is unavailable, use given default."""
    744     return value or arg
    745 default.is_safe = False
    746 
    747 def default_if_none(value, arg):
    748     """If value is None, use given default."""
    749     if value is None:
    750         return arg
    751     return value
    752 default_if_none.is_safe = False
    753 
    754 def divisibleby(value, arg):
    755     """Returns True if the value is devisible by the argument."""
    756     return int(value) % int(arg) == 0
    757 divisibleby.is_safe = False
    758 
    759 def yesno(value, arg=None):
    760     """
    761     Given a string mapping values for true, false and (optionally) None,
    762     returns one of those strings accoding to the value:
    763 
    764     ==========  ======================  ==================================
    765     Value       Argument                Outputs
    766     ==========  ======================  ==================================
    767     ``True``    ``"yeah,no,maybe"``     ``yeah``
    768     ``False``   ``"yeah,no,maybe"``     ``no``
    769     ``None``    ``"yeah,no,maybe"``     ``maybe``
    770     ``None``    ``"yeah,no"``           ``"no"`` (converts None to False
    771                                         if no mapping for None is given.
    772     ==========  ======================  ==================================
    773     """
    774     if arg is None:
    775         arg = ugettext('yes,no,maybe')
    776     bits = arg.split(u',')
    777     if len(bits) < 2:
    778         return value # Invalid arg.
    779     try:
    780         yes, no, maybe = bits
    781     except ValueError:
    782         # Unpack list of wrong size (no "maybe" value provided).
    783         yes, no, maybe = bits[0], bits[1], bits[1]
    784     if value is None:
    785         return maybe
    786     if value:
    787         return yes
    788     return no
    789 yesno.is_safe = False
    790 
    791 ###################
    792 # MISC            #
    793 ###################
    794 
    795 def filesizeformat(bytes):
    796     """
    797     Formats the value like a 'human-readable' file size (i.e. 13 KB, 4.1 MB,
    798     102 bytes, etc).
    799     """
    800     try:
    801         bytes = float(bytes)
    802     except TypeError:
    803         return u"0 bytes"
    804 
    805     if bytes < 1024:
    806         return ungettext("%(size)d byte", "%(size)d bytes", bytes) % {'size': bytes}
    807     if bytes < 1024 * 1024:
    808         return ugettext("%.1f KB") % (bytes / 1024)
    809     if bytes < 1024 * 1024 * 1024:
    810         return ugettext("%.1f MB") % (bytes / (1024 * 1024))
    811     return ugettext("%.1f GB") % (bytes / (1024 * 1024 * 1024))
    812 filesizeformat.is_safe = True
    813 
    814 def pluralize(value, arg=u's'):
    815     """
    816     Returns a plural suffix if the value is not 1. By default, 's' is used as
    817     the suffix:
    818 
    819     * If value is 0, vote{{ value|pluralize }} displays "0 votes".
    820     * If value is 1, vote{{ value|pluralize }} displays "1 vote".
    821     * If value is 2, vote{{ value|pluralize }} displays "2 votes".
    822 
    823     If an argument is provided, that string is used instead:
    824 
    825     * If value is 0, class{{ value|pluralize:"es" }} displays "0 classes".
    826     * If value is 1, class{{ value|pluralize:"es" }} displays "1 class".
    827     * If value is 2, class{{ value|pluralize:"es" }} displays "2 classes".
    828 
    829     If the provided argument contains a comma, the text before the comma is
    830     used for the singular case and the text after the comma is used for the
    831     plural case:
    832 
    833     * If value is 0, cand{{ value|pluralize:"y,ies" }} displays "0 candies".
    834     * If value is 1, cand{{ value|pluralize:"y,ies" }} displays "1 candy".
    835     * If value is 2, cand{{ value|pluralize:"y,ies" }} displays "2 candies".
    836     """
    837     if not u',' in arg:
    838         arg = u',' + arg
    839     bits = arg.split(u',')
    840     if len(bits) > 2:
    841         return u''
    842     singular_suffix, plural_suffix = bits[:2]
    843 
    844     try:
    845         if int(value) != 1:
    846             return plural_suffix
    847     except ValueError: # Invalid string that's not a number.
    848         pass
    849     except TypeError: # Value isn't a string or a number; maybe it's a list?
    850         try:
    851             if len(value) != 1:
    852                 return plural_suffix
    853         except TypeError: # len() of unsized object.
    854             pass
    855     return singular_suffix
    856 pluralize.is_safe = False
    857 
    858 def phone2numeric(value):
    859     """Takes a phone number and converts it in to its numerical equivalent."""
    860     from django.utils.text import phone2numeric
    861     return phone2numeric(value)
    862 phone2numeric.is_safe = True
    863 
    864 def pprint(value):
    865     """A wrapper around pprint.pprint -- for debugging, really."""
    866     from pprint import pformat
    867     try:
    868         return pformat(value)
    869     except Exception, e:
    870         return u"Error in formatting: %s" % force_unicode(e, errors="replace")
    871 pprint.is_safe = True
    872 
    873 # Syntax: register.filter(name of filter, callback)
    874 register.filter(add)
    875 register.filter(addslashes)
    876 register.filter(capfirst)
    877 register.filter(center)
    878 register.filter(cut)
    879 register.filter(date)
    880 register.filter(default)
    881 register.filter(default_if_none)
    882 register.filter(dictsort)
    883 register.filter(dictsortreversed)
    884 register.filter(divisibleby)
    885 register.filter(escape)
    886 register.filter(escapejs)
    887 register.filter(filesizeformat)
    888 register.filter(first)
    889 register.filter(fix_ampersands)
    890 register.filter(floatformat)
    891 register.filter(force_escape)
    892 register.filter(get_digit)
    893 register.filter(iriencode)
    894 register.filter(join)
    895 register.filter(last)
    896 register.filter(length)
    897 register.filter(length_is)
    898 register.filter(linebreaks)
    899 register.filter(linebreaksbr)
    900 register.filter(linenumbers)
    901 register.filter(ljust)
    902 register.filter(lower)
    903 register.filter(make_list)
    904 register.filter(phone2numeric)
    905 register.filter(pluralize)
    906 register.filter(pprint)
    907 register.filter(removetags)
    908 register.filter(random)
    909 register.filter(rjust)
    910 register.filter(safe)
    911 register.filter(safeseq)
    912 register.filter('slice', slice_)
    913 register.filter(slugify)
    914 register.filter(stringformat)
    915 register.filter(striptags)
    916 register.filter(time)
    917 register.filter(timesince)
    918 register.filter(timeuntil)
    919 register.filter(title)
    920 register.filter(truncatewords)
    921 register.filter(truncatewords_html)
    922 register.filter(unordered_list)
    923 register.filter(upper)
    924 register.filter(urlencode)
    925 register.filter(urlize)
    926 register.filter(urlizetrunc)
    927 register.filter(wordcount)
    928 register.filter(wordwrap)
    929 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 d703c6a..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.template.smartif import IfParser, Literal
    15 from django.conf import settings
    16 from django.utils.encoding import smart_str, smart_unicode
    17 from django.utils.itercompat import groupby
    18 from django.utils.safestring import mark_safe
    19 
    20 register = Library()
    21 
    22 class AutoEscapeControlNode(Node):
    23     """Implements the actions of the autoescape tag."""
    24     def __init__(self, setting, nodelist):
    25         self.setting, self.nodelist = setting, nodelist
    26 
    27     def render(self, context):
    28         old_setting = context.autoescape
    29         context.autoescape = self.setting
    30         output = self.nodelist.render(context)
    31         context.autoescape = old_setting
    32         if self.setting:
    33             return mark_safe(output)
    34         else:
    35             return output
    36 
    37 class CommentNode(Node):
    38     def render(self, context):
    39         return ''
    40 
    41 class CsrfTokenNode(Node):
    42     def render(self, context):
    43         csrf_token = context.get('csrf_token', None)
    44         if csrf_token:
    45             if csrf_token == 'NOTPROVIDED':
    46                 return mark_safe(u"")
    47             else:
    48                 return mark_safe(u"<div style='display:none'><input type='hidden' name='csrfmiddlewaretoken' value='%s' /></div>" % (csrf_token))
    49         else:
    50             # It's very probable that the token is missing because of
    51             # misconfiguration, so we raise a warning
    52             from django.conf import settings
    53             if settings.DEBUG:
    54                 import warnings
    55                 warnings.warn("A {% csrf_token %} was used in a template, but the context did not provide the value.  This is usually caused by not using RequestContext.")
    56             return u''
    57 
    58 class CycleNode(Node):
    59     def __init__(self, cyclevars, variable_name=None):
    60         self.cyclevars = cyclevars
    61         self.variable_name = variable_name
    62 
    63     def render(self, context):
    64         if self not in context.render_context:
    65             context.render_context[self] = itertools_cycle(self.cyclevars)
    66         cycle_iter = context.render_context[self]
    67         value = cycle_iter.next().resolve(context)
    68         if self.variable_name:
    69             context[self.variable_name] = value
    70         return value
    71 
    72 class DebugNode(Node):
    73     def render(self, context):
    74         from pprint import pformat
    75         output = [pformat(val) for val in context]
    76         output.append('\n\n')
    77         output.append(pformat(sys.modules))
    78         return ''.join(output)
    79 
    80 class FilterNode(Node):
    81     def __init__(self, filter_expr, nodelist):
    82         self.filter_expr, self.nodelist = filter_expr, nodelist
    83 
    84     def render(self, context):
    85         output = self.nodelist.render(context)
    86         # Apply filters.
    87         context.update({'var': output})
    88         filtered = self.filter_expr.resolve(context)
    89         context.pop()
    90         return filtered
    91 
    92 class FirstOfNode(Node):
    93     def __init__(self, vars):
    94         self.vars = vars
    95 
    96     def render(self, context):
    97         for var in self.vars:
    98             value = var.resolve(context, True)
    99             if value:
    100                 return smart_unicode(value)
    101         return u''
    102 
    103 class ForNode(Node):
    104     def __init__(self, loopvars, sequence, is_reversed, nodelist_loop, nodelist_empty=None):
    105         self.loopvars, self.sequence = loopvars, sequence
    106         self.is_reversed = is_reversed
    107         self.nodelist_loop = nodelist_loop
    108         if nodelist_empty is None:
    109             self.nodelist_empty = NodeList()
    110         else:
    111             self.nodelist_empty = nodelist_empty
    112 
    113     def __repr__(self):
    114         reversed_text = self.is_reversed and ' reversed' or ''
    115         return "<For Node: for %s in %s, tail_len: %d%s>" % \
    116             (', '.join(self.loopvars), self.sequence, len(self.nodelist_loop),
    117              reversed_text)
    118 
    119     def __iter__(self):
    120         for node in self.nodelist_loop:
    121             yield node
    122         for node in self.nodelist_empty:
    123             yield node
    124 
    125     def get_nodes_by_type(self, nodetype):
    126         nodes = []
    127         if isinstance(self, nodetype):
    128             nodes.append(self)
    129         nodes.extend(self.nodelist_loop.get_nodes_by_type(nodetype))
    130         nodes.extend(self.nodelist_empty.get_nodes_by_type(nodetype))
    131         return nodes
    132 
    133     def render(self, context):
    134         if 'forloop' in context:
    135             parentloop = context['forloop']
    136         else:
    137             parentloop = {}
    138         context.push()
    139         try:
    140             values = self.sequence.resolve(context, True)
    141         except VariableDoesNotExist:
    142             values = []
    143         if values is None:
    144             values = []
    145         if not hasattr(values, '__len__'):
    146             values = list(values)
    147         len_values = len(values)
    148         if len_values < 1:
    149             context.pop()
    150             return self.nodelist_empty.render(context)
    151         nodelist = NodeList()
    152         if self.is_reversed:
    153             values = reversed(values)
    154         unpack = len(self.loopvars) > 1
    155         # Create a forloop value in the context.  We'll update counters on each
    156         # iteration just below.
    157         loop_dict = context['forloop'] = {'parentloop': parentloop}
    158         for i, item in enumerate(values):
    159             # Shortcuts for current loop iteration number.
    160             loop_dict['counter0'] = i
    161             loop_dict['counter'] = i+1
    162             # Reverse counter iteration numbers.
    163             loop_dict['revcounter'] = len_values - i
    164             loop_dict['revcounter0'] = len_values - i - 1
    165             # Boolean values designating first and last times through loop.
    166             loop_dict['first'] = (i == 0)
    167             loop_dict['last'] = (i == len_values - 1)
    168 
    169             if unpack:
    170                 # If there are multiple loop variables, unpack the item into
    171                 # them.
    172                 context.update(dict(zip(self.loopvars, item)))
    173             else:
    174                 context[self.loopvars[0]] = item
    175             for node in self.nodelist_loop:
    176                 nodelist.append(node.render(context))
    177             if unpack:
    178                 # The loop variables were pushed on to the context so pop them
    179                 # off again. This is necessary because the tag lets the length
    180                 # of loopvars differ to the length of each set of items and we
    181                 # don't want to leave any vars from the previous loop on the
    182                 # context.
    183                 context.pop()
    184         context.pop()
    185         return nodelist.render(context)
    186 
    187 class IfChangedNode(Node):
    188     def __init__(self, nodelist_true, nodelist_false, *varlist):
    189         self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false
    190         self._last_seen = None
    191         self._varlist = varlist
    192         self._id = str(id(self))
    193 
    194     def render(self, context):
    195         if 'forloop' in context and self._id not in context['forloop']:
    196             self._last_seen = None
    197             context['forloop'][self._id] = 1
    198         try:
    199             if self._varlist:
    200                 # Consider multiple parameters.  This automatically behaves
    201                 # like an OR evaluation of the multiple variables.
    202                 compare_to = [var.resolve(context, True) for var in self._varlist]
    203             else:
    204                 compare_to = self.nodelist_true.render(context)
    205         except VariableDoesNotExist:
    206             compare_to = None
    207 
    208         if compare_to != self._last_seen:
    209             firstloop = (self._last_seen == None)
    210             self._last_seen = compare_to
    211             content = self.nodelist_true.render(context)
    212             return content
    213         elif self.nodelist_false:
    214             return self.nodelist_false.render(context)
    215         return ''
    216 
    217 class IfEqualNode(Node):
    218     def __init__(self, var1, var2, nodelist_true, nodelist_false, negate):
    219         self.var1, self.var2 = var1, var2
    220         self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false
    221         self.negate = negate
    222 
    223     def __repr__(self):
    224         return "<IfEqualNode>"
    225 
    226     def render(self, context):
    227         val1 = self.var1.resolve(context, True)
    228         val2 = self.var2.resolve(context, True)
    229         if (self.negate and val1 != val2) or (not self.negate and val1 == val2):
    230             return self.nodelist_true.render(context)
    231         return self.nodelist_false.render(context)
    232 
    233 class IfNode(Node):
    234     def __init__(self, var, nodelist_true, nodelist_false=None):
    235         self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false
    236         self.var = var
    237 
    238     def __repr__(self):
    239         return "<If node>"
    240 
    241     def __iter__(self):
    242         for node in self.nodelist_true:
    243             yield node
    244         for node in self.nodelist_false:
    245             yield node
    246 
    247     def get_nodes_by_type(self, nodetype):
    248         nodes = []
    249         if isinstance(self, nodetype):
    250             nodes.append(self)
    251         nodes.extend(self.nodelist_true.get_nodes_by_type(nodetype))
    252         nodes.extend(self.nodelist_false.get_nodes_by_type(nodetype))
    253         return nodes
    254 
    255     def render(self, context):
    256         if self.var.eval(context):
    257             return self.nodelist_true.render(context)
    258         else:
    259             return self.nodelist_false.render(context)
    260 
    261 class RegroupNode(Node):
    262     def __init__(self, target, expression, var_name):
    263         self.target, self.expression = target, expression
    264         self.var_name = var_name
    265 
    266     def render(self, context):
    267         obj_list = self.target.resolve(context, True)
    268         if obj_list == None:
    269             # target variable wasn't found in context; fail silently.
    270             context[self.var_name] = []
    271             return ''
    272         # List of dictionaries in the format:
    273         # {'grouper': 'key', 'list': [list of contents]}.
    274         context[self.var_name] = [
    275             {'grouper': key, 'list': list(val)}
    276             for key, val in
    277             groupby(obj_list, lambda v, f=self.expression.resolve: f(v, True))
    278         ]
    279         return ''
    280 
    281 def include_is_allowed(filepath):
    282     for root in settings.ALLOWED_INCLUDE_ROOTS:
    283         if filepath.startswith(root):
    284             return True
    285     return False
    286 
    287 class SsiNode(Node):
    288     def __init__(self, filepath, parsed):
    289         self.filepath, self.parsed = filepath, parsed
    290 
    291     def render(self, context):
    292         if not include_is_allowed(self.filepath):
    293             if settings.DEBUG:
    294                 return "[Didn't have permission to include file]"
    295             else:
    296                 return '' # Fail silently for invalid includes.
    297         try:
    298             fp = open(self.filepath, 'r')
    299             output = fp.read()
    300             fp.close()
    301         except IOError:
    302             output = ''
    303         if self.parsed:
    304             try:
    305                 t = Template(output, name=self.filepath)
    306                 return t.render(context)
    307             except TemplateSyntaxError, e:
    308                 if settings.DEBUG:
    309                     return "[Included template had syntax error: %s]" % e
    310                 else:
    311                     return '' # Fail silently for invalid included templates.
    312         return output
    313 
    314 class LoadNode(Node):
    315     def render(self, context):
    316         return ''
    317 
    318 class NowNode(Node):
    319     def __init__(self, format_string):
    320         self.format_string = format_string
    321 
    322     def render(self, context):
    323         from datetime import datetime
    324         from django.utils.dateformat import DateFormat
    325         df = DateFormat(datetime.now())
    326         return df.format(self.format_string)
    327 
    328 class SpacelessNode(Node):
    329     def __init__(self, nodelist):
    330         self.nodelist = nodelist
    331 
    332     def render(self, context):
    333         from django.utils.html import strip_spaces_between_tags
    334         return strip_spaces_between_tags(self.nodelist.render(context).strip())
    335 
    336 class TemplateTagNode(Node):
    337     mapping = {'openblock': BLOCK_TAG_START,
    338                'closeblock': BLOCK_TAG_END,
    339                'openvariable': VARIABLE_TAG_START,
    340                'closevariable': VARIABLE_TAG_END,
    341                'openbrace': SINGLE_BRACE_START,
    342                'closebrace': SINGLE_BRACE_END,
    343                'opencomment': COMMENT_TAG_START,
    344                'closecomment': COMMENT_TAG_END,
    345                }
    346 
    347     def __init__(self, tagtype):
    348         self.tagtype = tagtype
    349 
    350     def render(self, context):
    351         return self.mapping.get(self.tagtype, '')
    352 
    353 class URLNode(Node):
    354     def __init__(self, view_name, args, kwargs, asvar):
    355         self.view_name = view_name
    356         self.args = args
    357         self.kwargs = kwargs
    358         self.asvar = asvar
    359 
    360     def render(self, context):
    361         from django.core.urlresolvers import reverse, NoReverseMatch
    362         args = [arg.resolve(context) for arg in self.args]
    363         kwargs = dict([(smart_str(k,'ascii'), v.resolve(context))
    364                        for k, v in self.kwargs.items()])
    365 
    366         # Try to look up the URL twice: once given the view name, and again
    367         # relative to what we guess is the "main" app. If they both fail,
    368         # re-raise the NoReverseMatch unless we're using the
    369         # {% url ... as var %} construct in which cause return nothing.
    370         url = ''
    371         try:
    372             url = reverse(self.view_name, args=args, kwargs=kwargs, current_app=context.current_app)
    373         except NoReverseMatch, e:
    374             if settings.SETTINGS_MODULE:
    375                 project_name = settings.SETTINGS_MODULE.split('.')[0]
    376                 try:
    377                     url = reverse(project_name + '.' + self.view_name,
    378                               args=args, kwargs=kwargs, current_app=context.current_app)
    379                 except NoReverseMatch:
    380                     if self.asvar is None:
    381                         # Re-raise the original exception, not the one with
    382                         # the path relative to the project. This makes a
    383                         # better error message.
    384                         raise e
    385             else:
    386                 if self.asvar is None:
    387                     raise e
    388 
    389         if self.asvar:
    390             context[self.asvar] = url
    391             return ''
    392         else:
    393             return url
    394 
    395 class WidthRatioNode(Node):
    396     def __init__(self, val_expr, max_expr, max_width):
    397         self.val_expr = val_expr
    398         self.max_expr = max_expr
    399         self.max_width = max_width
    400 
    401     def render(self, context):
    402         try:
    403             value = self.val_expr.resolve(context)
    404             maxvalue = self.max_expr.resolve(context)
    405             max_width = int(self.max_width.resolve(context))
    406         except VariableDoesNotExist:
    407             return ''
    408         except ValueError:
    409             raise TemplateSyntaxError("widthratio final argument must be an number")
    410         try:
    411             value = float(value)
    412             maxvalue = float(maxvalue)
    413             ratio = (value / maxvalue) * max_width
    414         except (ValueError, ZeroDivisionError):
    415             return ''
    416         return str(int(round(ratio)))
    417 
    418 class WithNode(Node):
    419     def __init__(self, var, name, nodelist):
    420         self.var = var
    421         self.name = name
    422         self.nodelist = nodelist
    423 
    424     def __repr__(self):
    425         return "<WithNode>"
    426 
    427     def render(self, context):
    428         val = self.var.resolve(context)
    429         context.push()
    430         context[self.name] = val
    431         output = self.nodelist.render(context)
    432         context.pop()
    433         return output
    434 
    435 #@register.tag
    436 def autoescape(parser, token):
    437     """
    438     Force autoescape behaviour for this block.
    439     """
    440     args = token.contents.split()
    441     if len(args) != 2:
    442         raise TemplateSyntaxError("'autoescape' tag requires exactly one argument.")
    443     arg = args[1]
    444     if arg not in (u'on', u'off'):
    445         raise TemplateSyntaxError("'autoescape' argument should be 'on' or 'off'")
    446     nodelist = parser.parse(('endautoescape',))
    447     parser.delete_first_token()
    448     return AutoEscapeControlNode((arg == 'on'), nodelist)
    449 autoescape = register.tag(autoescape)
    450 
    451 #@register.tag
    452 def comment(parser, token):
    453     """
    454     Ignores everything between ``{% comment %}`` and ``{% endcomment %}``.
    455     """
    456     parser.skip_past('endcomment')
    457     return CommentNode()
    458 comment = register.tag(comment)
    459 
    460 #@register.tag
    461 def cycle(parser, token):
    462     """
    463     Cycles among the given strings each time this tag is encountered.
    464 
    465     Within a loop, cycles among the given strings each time through
    466     the loop::
    467 
    468         {% for o in some_list %}
    469             <tr class="{% cycle 'row1' 'row2' %}">
    470                 ...
    471             </tr>
    472         {% endfor %}
    473 
    474     Outside of a loop, give the values a unique name the first time you call
    475     it, then use that name each sucessive time through::
    476 
    477             <tr class="{% cycle 'row1' 'row2' 'row3' as rowcolors %}">...</tr>
    478             <tr class="{% cycle rowcolors %}">...</tr>
    479             <tr class="{% cycle rowcolors %}">...</tr>
    480 
    481     You can use any number of values, separated by spaces. Commas can also
    482     be used to separate values; if a comma is used, the cycle values are
    483     interpreted as literal strings.
    484     """
    485 
    486     # Note: This returns the exact same node on each {% cycle name %} call;
    487     # that is, the node object returned from {% cycle a b c as name %} and the
    488     # one returned from {% cycle name %} are the exact same object. This
    489     # shouldn't cause problems (heh), but if it does, now you know.
    490     #
    491     # Ugly hack warning: This stuffs the named template dict into parser so
    492     # that names are only unique within each template (as opposed to using
    493     # a global variable, which would make cycle names have to be unique across
    494     # *all* templates.
    495 
    496     args = token.split_contents()
    497 
    498     if len(args) < 2:
    499         raise TemplateSyntaxError("'cycle' tag requires at least two arguments")
    500 
    501     if ',' in args[1]:
    502         # Backwards compatibility: {% cycle a,b %} or {% cycle a,b as foo %}
    503         # case.
    504         args[1:2] = ['"%s"' % arg for arg in args[1].split(",")]
    505 
    506     if len(args) == 2:
    507         # {% cycle foo %} case.
    508         name = args[1]
    509         if not hasattr(parser, '_namedCycleNodes'):
    510             raise TemplateSyntaxError("No named cycles in template. '%s' is not defined" % name)
    511         if not name in parser._namedCycleNodes:
    512             raise TemplateSyntaxError("Named cycle '%s' does not exist" % name)
    513         return parser._namedCycleNodes[name]
    514 
    515     if len(args) > 4 and args[-2] == 'as':
    516         name = args[-1]
    517         values = [parser.compile_filter(arg) for arg in args[1:-2]]
    518         node = CycleNode(values, name)
    519         if not hasattr(parser, '_namedCycleNodes'):
    520             parser._namedCycleNodes = {}
    521         parser._namedCycleNodes[name] = node
    522     else:
    523         values = [parser.compile_filter(arg) for arg in args[1:]]
    524         node = CycleNode(values)
    525     return node
    526 cycle = register.tag(cycle)
    527 
    528 def csrf_token(parser, token):
    529     return CsrfTokenNode()
    530 register.tag(csrf_token)
    531 
    532 def debug(parser, token):
    533     """
    534     Outputs a whole load of debugging information, including the current
    535     context and imported modules.
    536 
    537     Sample usage::
    538 
    539         <pre>
    540             {% debug %}
    541         </pre>
    542     """
    543     return DebugNode()
    544 debug = register.tag(debug)
    545 
    546 #@register.tag(name="filter")
    547 def do_filter(parser, token):
    548     """
    549     Filters the contents of the block through variable filters.
    550 
    551     Filters can also be piped through each other, and they can have
    552     arguments -- just like in variable syntax.
    553 
    554     Sample usage::
    555 
    556         {% filter force_escape|lower %}
    557             This text will be HTML-escaped, and will appear in lowercase.
    558         {% endfilter %}
    559     """
    560     _, rest = token.contents.split(None, 1)
    561     filter_expr = parser.compile_filter("var|%s" % (rest))
    562     for func, unused in filter_expr.filters:
    563         if getattr(func, '_decorated_function', func).__name__ in ('escape', 'safe'):
    564             raise TemplateSyntaxError('"filter %s" is not permitted.  Use the "autoescape" tag instead.' % func.__name__)
    565     nodelist = parser.parse(('endfilter',))
    566     parser.delete_first_token()
    567     return FilterNode(filter_expr, nodelist)
    568 do_filter = register.tag("filter", do_filter)
    569 
    570 #@register.tag
    571 def firstof(parser, token):
    572     """
    573     Outputs the first variable passed that is not False, without escaping.
    574 
    575     Outputs nothing if all the passed variables are False.
    576 
    577     Sample usage::
    578 
    579         {% firstof var1 var2 var3 %}
    580 
    581     This is equivalent to::
    582 
    583         {% if var1 %}
    584             {{ var1|safe }}
    585         {% else %}{% if var2 %}
    586             {{ var2|safe }}
    587         {% else %}{% if var3 %}
    588             {{ var3|safe }}
    589         {% endif %}{% endif %}{% endif %}
    590 
    591     but obviously much cleaner!
    592 
    593     You can also use a literal string as a fallback value in case all
    594     passed variables are False::
    595 
    596         {% firstof var1 var2 var3 "fallback value" %}
    597 
    598     If you want to escape the output, use a filter tag::
    599 
    600         {% filter force_escape %}
    601             {% firstof var1 var2 var3 "fallback value" %}
    602         {% endfilter %}
    603 
    604     """
    605     bits = token.split_contents()[1:]
    606     if len(bits) < 1:
    607         raise TemplateSyntaxError("'firstof' statement requires at least one argument")
    608     return FirstOfNode([parser.compile_filter(bit) for bit in bits])
    609 firstof = register.tag(firstof)
    610 
    611 #@register.tag(name="for")
    612 def do_for(parser, token):
    613     """
    614     Loops over each item in an array.
    615 
    616     For example, to display a list of athletes given ``athlete_list``::
    617 
    618         <ul>
    619         {% for athlete in athlete_list %}
    620             <li>{{ athlete.name }}</li>
    621         {% endfor %}
    622         </ul>
    623 
    624     You can loop over a list in reverse by using
    625     ``{% for obj in list reversed %}``.
    626 
    627     You can also unpack multiple values from a two-dimensional array::
    628 
    629         {% for key,value in dict.items %}
    630             {{ key }}: {{ value }}
    631         {% endfor %}
    632 
    633     The ``for`` tag can take an optional ``{% empty %}`` clause that will
    634     be displayed if the given array is empty or could not be found::
    635 
    636         <ul>
    637           {% for athlete in athlete_list %}
    638             <li>{{ athlete.name }}</li>
    639           {% empty %}
    640             <li>Sorry, no athletes in this list.</li>
    641           {% endfor %}
    642         <ul>
    643 
    644     The above is equivalent to -- but shorter, cleaner, and possibly faster
    645     than -- the following::
    646 
    647         <ul>
    648           {% if althete_list %}
    649             {% for athlete in athlete_list %}
    650               <li>{{ athlete.name }}</li>
    651             {% endfor %}
    652           {% else %}
    653             <li>Sorry, no athletes in this list.</li>
    654           {% endif %}
    655         </ul>
    656 
    657     The for loop sets a number of variables available within the loop:
    658 
    659         ==========================  ================================================
    660         Variable                    Description
    661         ==========================  ================================================
    662         ``forloop.counter``         The current iteration of the loop (1-indexed)
    663         ``forloop.counter0``        The current iteration of the loop (0-indexed)
    664         ``forloop.revcounter``      The number of iterations from the end of the
    665                                     loop (1-indexed)
    666         ``forloop.revcounter0``     The number of iterations from the end of the
    667                                     loop (0-indexed)
    668         ``forloop.first``           True if this is the first time through the loop
    669         ``forloop.last``            True if this is the last time through the loop
    670         ``forloop.parentloop``      For nested loops, this is the loop "above" the
    671                                     current one
    672         ==========================  ================================================
    673 
    674     """
    675     bits = token.contents.split()
    676     if len(bits) < 4:
    677         raise TemplateSyntaxError("'for' statements should have at least four"
    678                                   " words: %s" % token.contents)
    679 
    680     is_reversed = bits[-1] == 'reversed'
    681     in_index = is_reversed and -3 or -2
    682     if bits[in_index] != 'in':
    683         raise TemplateSyntaxError("'for' statements should use the format"
    684                                   " 'for x in y': %s" % token.contents)
    685 
    686     loopvars = re.sub(r' *, *', ',', ' '.join(bits[1:in_index])).split(',')
    687     for var in loopvars:
    688         if not var or ' ' in var:
    689             raise TemplateSyntaxError("'for' tag received an invalid argument:"
    690                                       " %s" % token.contents)
    691 
    692     sequence = parser.compile_filter(bits[in_index+1])
    693     nodelist_loop = parser.parse(('empty', 'endfor',))
    694     token = parser.next_token()
    695     if token.contents == 'empty':
    696         nodelist_empty = parser.parse(('endfor',))
    697         parser.delete_first_token()
    698     else:
    699         nodelist_empty = None
    700     return ForNode(loopvars, sequence, is_reversed, nodelist_loop, nodelist_empty)
    701 do_for = register.tag("for", do_for)
    702 
    703 def do_ifequal(parser, token, negate):
    704     bits = list(token.split_contents())
    705     if len(bits) != 3:
    706         raise TemplateSyntaxError("%r takes two arguments" % bits[0])
    707     end_tag = 'end' + bits[0]
    708     nodelist_true = parser.parse(('else', end_tag))
    709     token = parser.next_token()
    710     if token.contents == 'else':
    711         nodelist_false = parser.parse((end_tag,))
    712         parser.delete_first_token()
    713     else:
    714         nodelist_false = NodeList()
    715     val1 = parser.compile_filter(bits[1])
    716     val2 = parser.compile_filter(bits[2])
    717     return IfEqualNode(val1, val2, nodelist_true, nodelist_false, negate)
    718 
    719 #@register.tag
    720 def ifequal(parser, token):
    721     """
    722     Outputs the contents of the block if the two arguments equal each other.
    723 
    724     Examples::
    725 
    726         {% ifequal user.id comment.user_id %}
    727             ...
    728         {% endifequal %}
    729 
    730         {% ifnotequal user.id comment.user_id %}
    731             ...
    732         {% else %}
    733             ...
    734         {% endifnotequal %}
    735     """
    736     return do_ifequal(parser, token, False)
    737 ifequal = register.tag(ifequal)
    738 
    739 #@register.tag
    740 def ifnotequal(parser, token):
    741     """
    742     Outputs the contents of the block if the two arguments are not equal.
    743     See ifequal.
    744     """
    745     return do_ifequal(parser, token, True)
    746 ifnotequal = register.tag(ifnotequal)
    747 
    748 class TemplateLiteral(Literal):
    749     def __init__(self, value, text):
    750         self.value = value
    751         self.text = text # for better error messages
    752 
    753     def display(self):
    754         return self.text
    755 
    756     def eval(self, context):
    757         return self.value.resolve(context, ignore_failures=True)
    758 
    759 class TemplateIfParser(IfParser):
    760     error_class = TemplateSyntaxError
    761 
    762     def __init__(self, parser, *args, **kwargs):
    763         self.template_parser = parser
    764         return super(TemplateIfParser, self).__init__(*args, **kwargs)
    765 
    766     def create_var(self, value):
    767         return TemplateLiteral(self.template_parser.compile_filter(value), value)
    768 
    769 #@register.tag(name="if")
    770 def do_if(parser, token):
    771     """
    772     The ``{% if %}`` tag evaluates a variable, and if that variable is "true"
    773     (i.e., exists, is not empty, and is not a false boolean value), the
    774     contents of the block are output:
    775 
    776     ::
    777 
    778         {% if athlete_list %}
    779             Number of athletes: {{ athlete_list|count }}
    780         {% else %}
    781             No athletes.
    782         {% endif %}
    783 
    784     In the above, if ``athlete_list`` is not empty, the number of athletes will
    785     be displayed by the ``{{ athlete_list|count }}`` variable.
    786 
    787     As you can see, the ``if`` tag can take an option ``{% else %}`` clause
    788     that will be displayed if the test fails.
    789 
    790     ``if`` tags may use ``or``, ``and`` or ``not`` to test a number of
    791     variables or to negate a given variable::
    792 
    793         {% if not athlete_list %}
    794             There are no athletes.
    795         {% endif %}
    796 
    797         {% if athlete_list or coach_list %}
    798             There are some athletes or some coaches.
    799         {% endif %}
    800 
    801         {% if athlete_list and coach_list %}
    802             Both atheletes and coaches are available.
    803         {% endif %}
    804 
    805         {% if not athlete_list or coach_list %}
    806             There are no athletes, or there are some coaches.
    807         {% endif %}
    808 
    809         {% if athlete_list and not coach_list %}
    810             There are some athletes and absolutely no coaches.
    811         {% endif %}
    812 
    813     Comparison operators are also available, and the use of filters is also
    814     allowed, for example:
    815 
    816         {% if articles|length >= 5 %}...{% endif %}
    817 
    818     Arguments and operators _must_ have a space between them, so
    819     ``{% if 1>2 %}`` is not a valid if tag.
    820 
    821     All supported operators are: ``or``, ``and``, ``in``, ``==`` (or ``=``),
    822     ``!=``, ``>``, ``>=``, ``<`` and ``<=``.
    823 
    824     Operator precedence follows Python.
    825     """
    826     bits = token.split_contents()[1:]
    827     var = TemplateIfParser(parser, bits).parse()
    828     nodelist_true = parser.parse(('else', 'endif'))
    829     token = parser.next_token()
    830     if token.contents == 'else':
    831         nodelist_false = parser.parse(('endif',))
    832         parser.delete_first_token()
    833     else:
    834         nodelist_false = NodeList()
    835     return IfNode(var, nodelist_true, nodelist_false)
    836 do_if = register.tag("if", do_if)
    837 
    838 #@register.tag
    839 def ifchanged(parser, token):
    840     """
    841     Checks if a value has changed from the last iteration of a loop.
    842 
    843     The 'ifchanged' block tag is used within a loop. It has two possible uses.
    844 
    845     1. Checks its own rendered contents against its previous state and only
    846        displays the content if it has changed. For example, this displays a
    847        list of days, only displaying the month if it changes::
    848 
    849             <h1>Archive for {{ year }}</h1>
    850 
    851             {% for date in days %}
    852                 {% ifchanged %}<h3>{{ date|date:"F" }}</h3>{% endifchanged %}
    853                 <a href="{{ date|date:"M/d"|lower }}/">{{ date|date:"j" }}</a>
    854             {% endfor %}
    855 
    856     2. If given a variable, check whether that variable has changed.
    857        For example, the following shows the date every time it changes, but
    858        only shows the hour if both the hour and the date have changed::
    859 
    860             {% for date in days %}
    861                 {% ifchanged date.date %} {{ date.date }} {% endifchanged %}
    862                 {% ifchanged date.hour date.date %}
    863                     {{ date.hour }}
    864                 {% endifchanged %}
    865             {% endfor %}
    866     """
    867     bits = token.contents.split()
    868     nodelist_true = parser.parse(('else', 'endifchanged'))
    869     token = parser.next_token()
    870     if token.contents == 'else':
    871         nodelist_false = parser.parse(('endifchanged',))
    872         parser.delete_first_token()
    873     else:
    874         nodelist_false = NodeList()
    875     values = [parser.compile_filter(bit) for bit in bits[1:]]
    876     return IfChangedNode(nodelist_true, nodelist_false, *values)
    877 ifchanged = register.tag(ifchanged)
    878 
    879 #@register.tag
    880 def ssi(parser, token):
    881     """
    882     Outputs the contents of a given file into the page.
    883 
    884     Like a simple "include" tag, the ``ssi`` tag includes the contents
    885     of another file -- which must be specified using an absolute path --
    886     in the current page::
    887 
    888         {% ssi /home/html/ljworld.com/includes/right_generic.html %}
    889 
    890     If the optional "parsed" parameter is given, the contents of the included
    891     file are evaluated as template code, with the current context::
    892 
    893         {% ssi /home/html/ljworld.com/includes/right_generic.html parsed %}
    894     """
    895     bits = token.contents.split()
    896     parsed = False
    897     if len(bits) not in (2, 3):
    898         raise TemplateSyntaxError("'ssi' tag takes one argument: the path to"
    899                                   " the file to be included")
    900     if len(bits) == 3:
    901         if bits[2] == 'parsed':
    902             parsed = True
    903         else:
    904             raise TemplateSyntaxError("Second (optional) argument to %s tag"
    905                                       " must be 'parsed'" % bits[0])
    906     return SsiNode(bits[1], parsed)
    907 ssi = register.tag(ssi)
    908 
    909 #@register.tag
    910 def load(parser, token):
    911     """
    912     Loads a custom template tag set.
    913 
    914     For example, to load the template tags in
    915     ``django/templatetags/news/photos.py``::
    916 
    917         {% load news.photos %}
    918     """
    919     bits = token.contents.split()
    920     for taglib in bits[1:]:
    921         # add the library to the parser
    922         try:
    923             lib = get_library("django.templatetags.%s" % taglib)
    924             parser.add_library(lib)
    925         except InvalidTemplateLibrary, e:
    926             raise TemplateSyntaxError("'%s' is not a valid tag library: %s" %
    927                                       (taglib, e))
    928     return LoadNode()
    929 load = register.tag(load)
    930 
    931 #@register.tag
    932 def now(parser, token):
    933     """
    934     Displays the date, formatted according to the given string.
    935 
    936     Uses the same format as PHP's ``date()`` function; see http://php.net/date
    937     for all the possible values.
    938 
    939     Sample usage::
    940 
    941         It is {% now "jS F Y H:i" %}
    942     """
    943     bits = token.contents.split('"')
    944     if len(bits) != 3:
    945         raise TemplateSyntaxError("'now' statement takes one argument")
    946     format_string = bits[1]
    947     return NowNode(format_string)
    948 now = register.tag(now)
    949 
    950 #@register.tag
    951 def regroup(parser, token):
    952     """
    953     Regroups a list of alike objects by a common attribute.
    954 
    955     This complex tag is best illustrated by use of an example:  say that
    956     ``people`` is a list of ``Person`` objects that have ``first_name``,
    957     ``last_name``, and ``gender`` attributes, and you'd like to display a list
    958     that looks like:
    959 
    960         * Male:
    961             * George Bush
    962             * Bill Clinton
    963         * Female:
    964             * Margaret Thatcher
    965             * Colendeeza Rice
    966         * Unknown:
    967             * Pat Smith
    968 
    969     The following snippet of template code would accomplish this dubious task::
    970 
    971         {% regroup people by gender as grouped %}
    972         <ul>
    973         {% for group in grouped %}
    974             <li>{{ group.grouper }}
    975             <ul>
    976                 {% for item in group.list %}
    977                 <li>{{ item }}</li>
    978                 {% endfor %}
    979             </ul>
    980         {% endfor %}
    981         </ul>
    982 
    983     As you can see, ``{% regroup %}`` populates a variable with a list of
    984     objects with ``grouper`` and ``list`` attributes.  ``grouper`` contains the
    985     item that was grouped by; ``list`` contains the list of objects that share
    986     that ``grouper``.  In this case, ``grouper`` would be ``Male``, ``Female``
    987     and ``Unknown``, and ``list`` is the list of people with those genders.
    988 
    989     Note that ``{% regroup %}`` does not work when the list to be grouped is not
    990     sorted by the key you are grouping by!  This means that if your list of
    991     people was not sorted by gender, you'd need to make sure it is sorted
    992     before using it, i.e.::
    993 
    994         {% regroup people|dictsort:"gender" by gender as grouped %}
    995 
    996     """
    997     firstbits = token.contents.split(None, 3)
    998     if len(firstbits) != 4:
    999         raise TemplateSyntaxError("'regroup' tag takes five arguments")
    1000     target = parser.compile_filter(firstbits[1])
    1001     if firstbits[2] != 'by':
    1002         raise TemplateSyntaxError("second argument to 'regroup' tag must be 'by'")
    1003     lastbits_reversed = firstbits[3][::-1].split(None, 2)
    1004     if lastbits_reversed[1][::-1] != 'as':
    1005         raise TemplateSyntaxError("next-to-last argument to 'regroup' tag must"
    1006                                   " be 'as'")
    1007 
    1008     expression = parser.compile_filter(lastbits_reversed[2][::-1])
    1009 
    1010     var_name = lastbits_reversed[0][::-1]
    1011     return RegroupNode(target, expression, var_name)
    1012 regroup = register.tag(regroup)
    1013 
    1014 def spaceless(parser, token):
    1015     """
    1016     Removes whitespace between HTML tags, including tab and newline characters.
    1017 
    1018     Example usage::
    1019 
    1020         {% spaceless %}
    1021             <p>
    1022                 <a href="foo/">Foo</a>
    1023             </p>
    1024         {% endspaceless %}
    1025 
    1026     This example would return this HTML::
    1027 
    1028         <p><a href="foo/">Foo</a></p>
    1029 
    1030     Only space between *tags* is normalized -- not space between tags and text.
    1031     In this example, the space around ``Hello`` won't be stripped::
    1032 
    1033         {% spaceless %}
    1034             <strong>
    1035                 Hello
    1036             </strong>
    1037         {% endspaceless %}
    1038     """
    1039     nodelist = parser.parse(('endspaceless',))
    1040     parser.delete_first_token()
    1041     return SpacelessNode(nodelist)
    1042 spaceless = register.tag(spaceless)
    1043 
    1044 #@register.tag
    1045 def templatetag(parser, token):
    1046     """
    1047     Outputs one of the bits used to compose template tags.
    1048 
    1049     Since the template system has no concept of "escaping", to display one of
    1050     the bits used in template tags, you must use the ``{% templatetag %}`` tag.
    1051 
    1052     The argument tells which template bit to output:
    1053 
    1054         ==================  =======
    1055         Argument            Outputs
    1056         ==================  =======
    1057         ``openblock``       ``{%``
    1058         ``closeblock``      ``%}``
    1059         ``openvariable``    ``{{``
    1060         ``closevariable``   ``}}``
    1061         ``openbrace``       ``{``
    1062         ``closebrace``      ``}``
    1063         ``opencomment``     ``{#``
    1064         ``closecomment``    ``#}``
    1065         ==================  =======
    1066     """
    1067     bits = token.contents.split()
    1068     if len(bits) != 2:
    1069         raise TemplateSyntaxError("'templatetag' statement takes one argument")
    1070     tag = bits[1]
    1071     if tag not in TemplateTagNode.mapping:
    1072         raise TemplateSyntaxError("Invalid templatetag argument: '%s'."
    1073                                   " Must be one of: %s" %
    1074                                   (tag, TemplateTagNode.mapping.keys()))
    1075     return TemplateTagNode(tag)
    1076 templatetag = register.tag(templatetag)
    1077 
    1078 def url(parser, token):
    1079     """
    1080     Returns an absolute URL matching given view with its parameters.
    1081 
    1082     This is a way to define links that aren't tied to a particular URL
    1083     configuration::
    1084 
    1085         {% url path.to.some_view arg1,arg2,name1=value1 %}
    1086 
    1087     The first argument is a path to a view. It can be an absolute python path
    1088     or just ``app_name.view_name`` without the project name if the view is
    1089     located inside the project.  Other arguments are comma-separated values
    1090     that will be filled in place of positional and keyword arguments in the
    1091     URL. All arguments for the URL should be present.
    1092 
    1093     For example if you have a view ``app_name.client`` taking client's id and
    1094     the corresponding line in a URLconf looks like this::
    1095 
    1096         ('^client/(\d+)/$', 'app_name.client')
    1097 
    1098     and this app's URLconf is included into the project's URLconf under some
    1099     path::
    1100 
    1101         ('^clients/', include('project_name.app_name.urls'))
    1102 
    1103     then in a template you can create a link for a certain client like this::
    1104 
    1105         {% url app_name.client client.id %}
    1106 
    1107     The URL will look like ``/clients/client/123/``.
    1108     """
    1109     bits = token.split_contents()
    1110     if len(bits) < 2:
    1111         raise TemplateSyntaxError("'%s' takes at least one argument"
    1112                                   " (path to a view)" % bits[0])
    1113     viewname = bits[1]
    1114     args = []
    1115     kwargs = {}
    1116     asvar = None
    1117 
    1118     if len(bits) > 2:
    1119         bits = iter(bits[2:])
    1120         for bit in bits:
    1121             if bit == 'as':
    1122                 asvar = bits.next()
    1123                 break
    1124             else:
    1125                 for arg in bit.split(","):
    1126                     if '=' in arg:
    1127                         k, v = arg.split('=', 1)
    1128                         k = k.strip()
    1129                         kwargs[k] = parser.compile_filter(v)
    1130                     elif arg:
    1131                         args.append(parser.compile_filter(arg))
    1132     return URLNode(viewname, args, kwargs, asvar)
    1133 url = register.tag(url)
    1134 
    1135 #@register.tag
    1136 def widthratio(parser, token):
    1137     """
    1138     For creating bar charts and such, this tag calculates the ratio of a given
    1139     value to a maximum value, and then applies that ratio to a constant.
    1140 
    1141     For example::
    1142 
    1143         <img src='bar.gif' height='10' width='{% widthratio this_value max_value 100 %}' />
    1144 
    1145     Above, if ``this_value`` is 175 and ``max_value`` is 200, the image in
    1146     the above example will be 88 pixels wide (because 175/200 = .875;
    1147     .875 * 100 = 87.5 which is rounded up to 88).
    1148     """
    1149     bits = token.contents.split()
    1150     if len(bits) != 4:
    1151         raise TemplateSyntaxError("widthratio takes three arguments")
    1152     tag, this_value_expr, max_value_expr, max_width = bits
    1153 
    1154     return WidthRatioNode(parser.compile_filter(this_value_expr),
    1155                           parser.compile_filter(max_value_expr),
    1156                           parser.compile_filter(max_width))
    1157 widthratio = register.tag(widthratio)
    1158 
    1159 #@register.tag
    1160 def do_with(parser, token):
    1161     """
    1162     Adds a value to the context (inside of this block) for caching and easy
    1163     access.
    1164 
    1165     For example::
    1166 
    1167         {% with person.some_sql_method as total %}
    1168             {{ total }} object{{ total|pluralize }}
    1169         {% endwith %}
    1170     """
    1171     bits = list(token.split_contents())
    1172     if len(bits) != 4 or bits[2] != "as":
    1173         raise TemplateSyntaxError("%r expected format is 'value as name'" %
    1174                                   bits[0])
    1175     var = parser.compile_filter(bits[1])
    1176     name = bits[3]
    1177     nodelist = parser.parse(('endwith',))
    1178     parser.delete_first_token()
    1179     return WithNode(var, name, nodelist)
    1180 do_with = register.tag('with', do_with)
  • django/templatetags/__init__.py

    diff --git a/django/templatetags/__init__.py b/django/templatetags/__init__.py
    index 4033e5a..8bc9454 100644
    a b  
     1import imp
     2
    13from django.conf import settings
    24from django.utils import importlib
    35
    4 for a in settings.INSTALLED_APPS:
    5     try:
    6         __path__.extend(importlib.import_module('.templatetags', a).__path__)
    7     except ImportError:
    8         pass
     6templatetags_modules= []
     7
     8def get_templatetags_modules():
     9    if not templatetags_modules:
     10        """ Populate list once per thread. """
     11        for app_module in ['django'] + list(settings.INSTALLED_APPS):
     12            try:
     13                components = app_module.split('.')
     14                mod = __import__(app_module, {}, {}, [components[-1]])
     15                imp.find_module('templatetags', mod.__path__)
     16                templatetag_module = '%s.templatetags' % app_module
     17                templatetags_modules.append(templatetag_module)
     18            except ImportError:
     19                continue
     20    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..0ba16bc
    - +  
     1"""Default variable filters."""
     2
     3import re
     4
     5try:
     6    from decimal import Decimal, InvalidOperation, ROUND_HALF_UP
     7except ImportError:
     8    from django.utils._decimal import Decimal, InvalidOperation, ROUND_HALF_UP
     9
     10import random as random_module
     11try:
     12    from functools import wraps
     13except ImportError:
     14    from django.utils.functional import wraps  # Python 2.3, 2.4 fallback.
     15
     16from django.template import Variable, Library
     17from django.conf import settings
     18from django.utils import formats
     19from django.utils.translation import ugettext, ungettext
     20from django.utils.encoding import force_unicode, iri_to_uri
     21from django.utils.safestring import mark_safe, SafeData
     22
     23register = Library()
     24
     25#######################
     26# STRING DECORATOR    #
     27#######################
     28
     29def stringfilter(func):
     30    """
     31    Decorator for filters which should only receive unicode objects. The object
     32    passed as the first positional argument will be converted to a unicode
     33    object.
     34    """
     35    def _dec(*args, **kwargs):
     36        if args:
     37            args = list(args)
     38            args[0] = force_unicode(args[0])
     39            if isinstance(args[0], SafeData) and getattr(func, 'is_safe', False):
     40                return mark_safe(func(*args, **kwargs))
     41        return func(*args, **kwargs)
     42
     43    # Include a reference to the real function (used to check original
     44    # arguments by the template parser).
     45    _dec._decorated_function = getattr(func, '_decorated_function', func)
     46    for attr in ('is_safe', 'needs_autoescape'):
     47        if hasattr(func, attr):
     48            setattr(_dec, attr, getattr(func, attr))
     49    return wraps(func)(_dec)
     50
     51###################
     52# STRINGS         #
     53###################
     54
     55def addslashes(value):
     56    """
     57    Adds slashes before quotes. Useful for escaping strings in CSV, for
     58    example. Less useful for escaping JavaScript; use the ``escapejs``
     59    filter instead.
     60    """
     61    return value.replace('\\', '\\\\').replace('"', '\\"').replace("'", "\\'")
     62addslashes.is_safe = True
     63addslashes = stringfilter(addslashes)
     64
     65def capfirst(value):
     66    """Capitalizes the first character of the value."""
     67    return value and value[0].upper() + value[1:]
     68capfirst.is_safe=True
     69capfirst = stringfilter(capfirst)
     70
     71_base_js_escapes = (
     72    ('\\', r'\x5C'),
     73    ('\'', r'\x27'),
     74    ('"', r'\x22'),
     75    ('>', r'\x3E'),
     76    ('<', r'\x3C'),
     77    ('&', r'\x26'),
     78    ('=', r'\x3D'),
     79    ('-', r'\x2D'),
     80    (';', r'\x3B'),
     81    (u'\u2028', r'\u2028'),
     82    (u'\u2029', r'\u2029')
     83)
     84
     85# Escape every ASCII character with a value less than 32.
     86_js_escapes = (_base_js_escapes +
     87               tuple([('%c' % z, '\\x%02X' % z) for z in range(32)]))
     88
     89def escapejs(value):
     90    """Hex encodes characters for use in JavaScript strings."""
     91    for bad, good in _js_escapes:
     92        value = value.replace(bad, good)
     93    return value
     94escapejs = stringfilter(escapejs)
     95
     96def fix_ampersands(value):
     97    """Replaces ampersands with ``&amp;`` entities."""
     98    from django.utils.html import fix_ampersands
     99    return fix_ampersands(value)
     100fix_ampersands.is_safe=True
     101fix_ampersands = stringfilter(fix_ampersands)
     102
     103# Values for testing floatformat input against infinity and NaN representations,
     104# which differ across platforms and Python versions.  Some (i.e. old Windows
     105# ones) are not recognized by Decimal but we want to return them unchanged vs.
     106# returning an empty string as we do for completley invalid input.  Note these
     107# need to be built up from values that are not inf/nan, since inf/nan values do
     108# not reload properly from .pyc files on Windows prior to some level of Python 2.5
     109# (see Python Issue757815 and Issue1080440).
     110pos_inf = 1e200 * 1e200
     111neg_inf = -1e200 * 1e200
     112nan = (1e200 * 1e200) / (1e200 * 1e200)
     113special_floats = [str(pos_inf), str(neg_inf), str(nan)]
     114
     115def floatformat(text, arg=-1):
     116    """
     117    Displays a float to a specified number of decimal places.
     118
     119    If called without an argument, it displays the floating point number with
     120    one decimal place -- but only if there's a decimal place to be displayed:
     121
     122    * num1 = 34.23234
     123    * num2 = 34.00000
     124    * num3 = 34.26000
     125    * {{ num1|floatformat }} displays "34.2"
     126    * {{ num2|floatformat }} displays "34"
     127    * {{ num3|floatformat }} displays "34.3"
     128
     129    If arg is positive, it will always display exactly arg number of decimal
     130    places:
     131
     132    * {{ num1|floatformat:3 }} displays "34.232"
     133    * {{ num2|floatformat:3 }} displays "34.000"
     134    * {{ num3|floatformat:3 }} displays "34.260"
     135
     136    If arg is negative, it will display arg number of decimal places -- but
     137    only if there are places to be displayed:
     138
     139    * {{ num1|floatformat:"-3" }} displays "34.232"
     140    * {{ num2|floatformat:"-3" }} displays "34"
     141    * {{ num3|floatformat:"-3" }} displays "34.260"
     142
     143    If the input float is infinity or NaN, the (platform-dependent) string
     144    representation of that value will be displayed.
     145    """
     146
     147    try:
     148        input_val = force_unicode(text)
     149        d = Decimal(input_val)
     150    except UnicodeEncodeError:
     151        return u''
     152    except InvalidOperation:
     153        if input_val in special_floats:
     154            return input_val
     155        try:
     156            d = Decimal(force_unicode(float(text)))
     157        except (ValueError, InvalidOperation, TypeError, UnicodeEncodeError):
     158            return u''
     159    try:
     160        p = int(arg)
     161    except ValueError:
     162        return input_val
     163
     164    try:
     165        m = int(d) - d
     166    except (ValueError, OverflowError, InvalidOperation):
     167        return input_val
     168
     169    if not m and p < 0:
     170        return mark_safe(formats.number_format(u'%d' % (int(d)), 0))
     171
     172    if p == 0:
     173        exp = Decimal(1)
     174    else:
     175        exp = Decimal('1.0') / (Decimal(10) ** abs(p))
     176    try:
     177        return mark_safe(formats.number_format(u'%s' % str(d.quantize(exp, ROUND_HALF_UP)), abs(p)))
     178    except InvalidOperation:
     179        return input_val
     180floatformat.is_safe = True
     181
     182def iriencode(value):
     183    """Escapes an IRI value for use in a URL."""
     184    return force_unicode(iri_to_uri(value))
     185iriencode.is_safe = True
     186iriencode = stringfilter(iriencode)
     187
     188def linenumbers(value, autoescape=None):
     189    """Displays text with line numbers."""
     190    from django.utils.html import escape
     191    lines = value.split(u'\n')
     192    # Find the maximum width of the line count, for use with zero padding
     193    # string format command
     194    width = unicode(len(unicode(len(lines))))
     195    if not autoescape or isinstance(value, SafeData):
     196        for i, line in enumerate(lines):
     197            lines[i] = (u"%0" + width  + u"d. %s") % (i + 1, line)
     198    else:
     199        for i, line in enumerate(lines):
     200            lines[i] = (u"%0" + width  + u"d. %s") % (i + 1, escape(line))
     201    return mark_safe(u'\n'.join(lines))
     202linenumbers.is_safe = True
     203linenumbers.needs_autoescape = True
     204linenumbers = stringfilter(linenumbers)
     205
     206def lower(value):
     207    """Converts a string into all lowercase."""
     208    return value.lower()
     209lower.is_safe = True
     210lower = stringfilter(lower)
     211
     212def make_list(value):
     213    """
     214    Returns the value turned into a list.
     215
     216    For an integer, it's a list of digits.
     217    For a string, it's a list of characters.
     218    """
     219    return list(value)
     220make_list.is_safe = False
     221make_list = stringfilter(make_list)
     222
     223def slugify(value):
     224    """
     225    Normalizes string, converts to lowercase, removes non-alpha characters,
     226    and converts spaces to hyphens.
     227    """
     228    import unicodedata
     229    value = unicodedata.normalize('NFKD', value).encode('ascii', 'ignore')
     230    value = unicode(re.sub('[^\w\s-]', '', value).strip().lower())
     231    return mark_safe(re.sub('[-\s]+', '-', value))
     232slugify.is_safe = True
     233slugify = stringfilter(slugify)
     234
     235def stringformat(value, arg):
     236    """
     237    Formats the variable according to the arg, a string formatting specifier.
     238
     239    This specifier uses Python string formating syntax, with the exception that
     240    the leading "%" is dropped.
     241
     242    See http://docs.python.org/lib/typesseq-strings.html for documentation
     243    of Python string formatting
     244    """
     245    try:
     246        return (u"%" + unicode(arg)) % value
     247    except (ValueError, TypeError):
     248        return u""
     249stringformat.is_safe = True
     250
     251def title(value):
     252    """Converts a string into titlecase."""
     253    t = re.sub("([a-z])'([A-Z])", lambda m: m.group(0).lower(), value.title())
     254    return re.sub("\d([A-Z])", lambda m: m.group(0).lower(), t)
     255title.is_safe = True
     256title = stringfilter(title)
     257
     258def truncatewords(value, arg):
     259    """
     260    Truncates a string after a certain number of words.
     261
     262    Argument: Number of words to truncate after.
     263    """
     264    from django.utils.text import truncate_words
     265    try:
     266        length = int(arg)
     267    except ValueError: # Invalid literal for int().
     268        return value # Fail silently.
     269    return truncate_words(value, length)
     270truncatewords.is_safe = True
     271truncatewords = stringfilter(truncatewords)
     272
     273def truncatewords_html(value, arg):
     274    """
     275    Truncates HTML after a certain number of words.
     276
     277    Argument: Number of words to truncate after.
     278    """
     279    from django.utils.text import truncate_html_words
     280    try:
     281        length = int(arg)
     282    except ValueError: # invalid literal for int()
     283        return value # Fail silently.
     284    return truncate_html_words(value, length)
     285truncatewords_html.is_safe = True
     286truncatewords_html = stringfilter(truncatewords_html)
     287
     288def upper(value):
     289    """Converts a string into all uppercase."""
     290    return value.upper()
     291upper.is_safe = False
     292upper = stringfilter(upper)
     293
     294def urlencode(value):
     295    """Escapes a value for use in a URL."""
     296    from django.utils.http import urlquote
     297    return urlquote(value)
     298urlencode.is_safe = False
     299urlencode = stringfilter(urlencode)
     300
     301def urlize(value, autoescape=None):
     302    """Converts URLs in plain text into clickable links."""
     303    from django.utils.html import urlize
     304    return mark_safe(urlize(value, nofollow=True, autoescape=autoescape))
     305urlize.is_safe=True
     306urlize.needs_autoescape = True
     307urlize = stringfilter(urlize)
     308
     309def urlizetrunc(value, limit, autoescape=None):
     310    """
     311    Converts URLs into clickable links, truncating URLs to the given character
     312    limit, and adding 'rel=nofollow' attribute to discourage spamming.
     313
     314    Argument: Length to truncate URLs to.
     315    """
     316    from django.utils.html import urlize
     317    return mark_safe(urlize(value, trim_url_limit=int(limit), nofollow=True,
     318                            autoescape=autoescape))
     319urlizetrunc.is_safe = True
     320urlizetrunc.needs_autoescape = True
     321urlizetrunc = stringfilter(urlizetrunc)
     322
     323def wordcount(value):
     324    """Returns the number of words."""
     325    return len(value.split())
     326wordcount.is_safe = False
     327wordcount = stringfilter(wordcount)
     328
     329def wordwrap(value, arg):
     330    """
     331    Wraps words at specified line length.
     332
     333    Argument: number of characters to wrap the text at.
     334    """
     335    from django.utils.text import wrap
     336    return wrap(value, int(arg))
     337wordwrap.is_safe = True
     338wordwrap = stringfilter(wordwrap)
     339
     340def ljust(value, arg):
     341    """
     342    Left-aligns the value in a field of a given width.
     343
     344    Argument: field size.
     345    """
     346    return value.ljust(int(arg))
     347ljust.is_safe = True
     348ljust = stringfilter(ljust)
     349
     350def rjust(value, arg):
     351    """
     352    Right-aligns the value in a field of a given width.
     353
     354    Argument: field size.
     355    """
     356    return value.rjust(int(arg))
     357rjust.is_safe = True
     358rjust = stringfilter(rjust)
     359
     360def center(value, arg):
     361    """Centers the value in a field of a given width."""
     362    return value.center(int(arg))
     363center.is_safe = True
     364center = stringfilter(center)
     365
     366def cut(value, arg):
     367    """
     368    Removes all values of arg from the given string.
     369    """
     370    safe = isinstance(value, SafeData)
     371    value = value.replace(arg, u'')
     372    if safe and arg != ';':
     373        return mark_safe(value)
     374    return value
     375cut = stringfilter(cut)
     376
     377###################
     378# HTML STRINGS    #
     379###################
     380
     381def escape(value):
     382    """
     383    Marks the value as a string that should not be auto-escaped.
     384    """
     385    from django.utils.safestring import mark_for_escaping
     386    return mark_for_escaping(value)
     387escape.is_safe = True
     388escape = stringfilter(escape)
     389
     390def force_escape(value):
     391    """
     392    Escapes a string's HTML. This returns a new string containing the escaped
     393    characters (as opposed to "escape", which marks the content for later
     394    possible escaping).
     395    """
     396    from django.utils.html import escape
     397    return mark_safe(escape(value))
     398force_escape = stringfilter(force_escape)
     399force_escape.is_safe = True
     400
     401def linebreaks(value, autoescape=None):
     402    """
     403    Replaces line breaks in plain text with appropriate HTML; a single
     404    newline becomes an HTML line break (``<br />``) and a new line
     405    followed by a blank line becomes a paragraph break (``</p>``).
     406    """
     407    from django.utils.html import linebreaks
     408    autoescape = autoescape and not isinstance(value, SafeData)
     409    return mark_safe(linebreaks(value, autoescape))
     410linebreaks.is_safe = True
     411linebreaks.needs_autoescape = True
     412linebreaks = stringfilter(linebreaks)
     413
     414def linebreaksbr(value, autoescape=None):
     415    """
     416    Converts all newlines in a piece of plain text to HTML line breaks
     417    (``<br />``).
     418    """
     419    if autoescape and not isinstance(value, SafeData):
     420        from django.utils.html import escape
     421        value = escape(value)
     422    return mark_safe(value.replace('\n', '<br />'))
     423linebreaksbr.is_safe = True
     424linebreaksbr.needs_autoescape = True
     425linebreaksbr = stringfilter(linebreaksbr)
     426
     427def safe(value):
     428    """
     429    Marks the value as a string that should not be auto-escaped.
     430    """
     431    return mark_safe(value)
     432safe.is_safe = True
     433safe = stringfilter(safe)
     434
     435def safeseq(value):
     436    """
     437    A "safe" filter for sequences. Marks each element in the sequence,
     438    individually, as safe, after converting them to unicode. Returns a list
     439    with the results.
     440    """
     441    return [mark_safe(force_unicode(obj)) for obj in value]
     442safeseq.is_safe = True
     443
     444def removetags(value, tags):
     445    """Removes a space separated list of [X]HTML tags from the output."""
     446    tags = [re.escape(tag) for tag in tags.split()]
     447    tags_re = u'(%s)' % u'|'.join(tags)
     448    starttag_re = re.compile(ur'<%s(/?>|(\s+[^>]*>))' % tags_re, re.U)
     449    endtag_re = re.compile(u'</%s>' % tags_re)
     450    value = starttag_re.sub(u'', value)
     451    value = endtag_re.sub(u'', value)
     452    return value
     453removetags.is_safe = True
     454removetags = stringfilter(removetags)
     455
     456def striptags(value):
     457    """Strips all [X]HTML tags."""
     458    from django.utils.html import strip_tags
     459    return strip_tags(value)
     460striptags.is_safe = True
     461striptags = stringfilter(striptags)
     462
     463###################
     464# LISTS           #
     465###################
     466
     467def dictsort(value, arg):
     468    """
     469    Takes a list of dicts, returns that list sorted by the property given in
     470    the argument.
     471    """
     472    var_resolve = Variable(arg).resolve
     473    decorated = [(var_resolve(item), item) for item in value]
     474    decorated.sort()
     475    return [item[1] for item in decorated]
     476dictsort.is_safe = False
     477
     478def dictsortreversed(value, arg):
     479    """
     480    Takes a list of dicts, returns that list sorted in reverse order by the
     481    property given in the argument.
     482    """
     483    var_resolve = Variable(arg).resolve
     484    decorated = [(var_resolve(item), item) for item in value]
     485    decorated.sort()
     486    decorated.reverse()
     487    return [item[1] for item in decorated]
     488dictsortreversed.is_safe = False
     489
     490def first(value):
     491    """Returns the first item in a list."""
     492    try:
     493        return value[0]
     494    except IndexError:
     495        return u''
     496first.is_safe = False
     497
     498def join(value, arg, autoescape=None):
     499    """
     500    Joins a list with a string, like Python's ``str.join(list)``.
     501    """
     502    value = map(force_unicode, value)
     503    if autoescape:
     504        from django.utils.html import conditional_escape
     505        value = [conditional_escape(v) for v in value]
     506    try:
     507        data = arg.join(value)
     508    except AttributeError: # fail silently but nicely
     509        return value
     510    return mark_safe(data)
     511join.is_safe = True
     512join.needs_autoescape = True
     513
     514def last(value):
     515    "Returns the last item in a list"
     516    try:
     517        return value[-1]
     518    except IndexError:
     519        return u''
     520last.is_safe = True
     521
     522def length(value):
     523    """Returns the length of the value - useful for lists."""
     524    try:
     525        return len(value)
     526    except (ValueError, TypeError):
     527        return ''
     528length.is_safe = True
     529
     530def length_is(value, arg):
     531    """Returns a boolean of whether the value's length is the argument."""
     532    try:
     533        return len(value) == int(arg)
     534    except (ValueError, TypeError):
     535        return ''
     536length_is.is_safe = False
     537
     538def random(value):
     539    """Returns a random item from the list."""
     540    return random_module.choice(value)
     541random.is_safe = True
     542
     543def slice_(value, arg):
     544    """
     545    Returns a slice of the list.
     546
     547    Uses the same syntax as Python's list slicing; see
     548    http://diveintopython.org/native_data_types/lists.html#odbchelper.list.slice
     549    for an introduction.
     550    """
     551    try:
     552        bits = []
     553        for x in arg.split(u':'):
     554            if len(x) == 0:
     555                bits.append(None)
     556            else:
     557                bits.append(int(x))
     558        return value[slice(*bits)]
     559
     560    except (ValueError, TypeError):
     561        return value # Fail silently.
     562slice_.is_safe = True
     563
     564def unordered_list(value, autoescape=None):
     565    """
     566    Recursively takes a self-nested list and returns an HTML unordered list --
     567    WITHOUT opening and closing <ul> tags.
     568
     569    The list is assumed to be in the proper format. For example, if ``var``
     570    contains: ``['States', ['Kansas', ['Lawrence', 'Topeka'], 'Illinois']]``,
     571    then ``{{ var|unordered_list }}`` would return::
     572
     573        <li>States
     574        <ul>
     575                <li>Kansas
     576                <ul>
     577                        <li>Lawrence</li>
     578                        <li>Topeka</li>
     579                </ul>
     580                </li>
     581                <li>Illinois</li>
     582        </ul>
     583        </li>
     584    """
     585    if autoescape:
     586        from django.utils.html import conditional_escape
     587        escaper = conditional_escape
     588    else:
     589        escaper = lambda x: x
     590    def convert_old_style_list(list_):
     591        """
     592        Converts old style lists to the new easier to understand format.
     593
     594        The old list format looked like:
     595            ['Item 1', [['Item 1.1', []], ['Item 1.2', []]]
     596
     597        And it is converted to:
     598            ['Item 1', ['Item 1.1', 'Item 1.2]]
     599        """
     600        if not isinstance(list_, (tuple, list)) or len(list_) != 2:
     601            return list_, False
     602        first_item, second_item = list_
     603        if second_item == []:
     604            return [first_item], True
     605        old_style_list = True
     606        new_second_item = []
     607        for sublist in second_item:
     608            item, old_style_list = convert_old_style_list(sublist)
     609            if not old_style_list:
     610                break
     611            new_second_item.extend(item)
     612        if old_style_list:
     613            second_item = new_second_item
     614        return [first_item, second_item], old_style_list
     615    def _helper(list_, tabs=1):
     616        indent = u'\t' * tabs
     617        output = []
     618
     619        list_length = len(list_)
     620        i = 0
     621        while i < list_length:
     622            title = list_[i]
     623            sublist = ''
     624            sublist_item = None
     625            if isinstance(title, (list, tuple)):
     626                sublist_item = title
     627                title = ''
     628            elif i < list_length - 1:
     629                next_item = list_[i+1]
     630                if next_item and isinstance(next_item, (list, tuple)):
     631                    # The next item is a sub-list.
     632                    sublist_item = next_item
     633                    # We've processed the next item now too.
     634                    i += 1
     635            if sublist_item:
     636                sublist = _helper(sublist_item, tabs+1)
     637                sublist = '\n%s<ul>\n%s\n%s</ul>\n%s' % (indent, sublist,
     638                                                         indent, indent)
     639            output.append('%s<li>%s%s</li>' % (indent,
     640                    escaper(force_unicode(title)), sublist))
     641            i += 1
     642        return '\n'.join(output)
     643    value, converted = convert_old_style_list(value)
     644    return mark_safe(_helper(value))
     645unordered_list.is_safe = True
     646unordered_list.needs_autoescape = True
     647
     648###################
     649# INTEGERS        #
     650###################
     651
     652def add(value, arg):
     653    """Adds the arg to the value."""
     654    return int(value) + int(arg)
     655add.is_safe = False
     656
     657def get_digit(value, arg):
     658    """
     659    Given a whole number, returns the requested digit of it, where 1 is the
     660    right-most digit, 2 is the second-right-most digit, etc. Returns the
     661    original value for invalid input (if input or argument is not an integer,
     662    or if argument is less than 1). Otherwise, output is always an integer.
     663    """
     664    try:
     665        arg = int(arg)
     666        value = int(value)
     667    except ValueError:
     668        return value # Fail silently for an invalid argument
     669    if arg < 1:
     670        return value
     671    try:
     672        return int(str(value)[-arg])
     673    except IndexError:
     674        return 0
     675get_digit.is_safe = False
     676
     677###################
     678# DATES           #
     679###################
     680
     681def date(value, arg=None):
     682    """Formats a date according to the given format."""
     683    from django.utils.dateformat import format
     684    if not value:
     685        return u''
     686    if arg is None:
     687        arg = settings.DATE_FORMAT
     688    try:
     689        return formats.date_format(value, arg)
     690    except AttributeError:
     691        try:
     692            return format(value, arg)
     693        except AttributeError:
     694            return ''
     695date.is_safe = False
     696
     697def time(value, arg=None):
     698    """Formats a time according to the given format."""
     699    from django.utils import dateformat
     700    if value in (None, u''):
     701        return u''
     702    if arg is None:
     703        arg = settings.TIME_FORMAT
     704    try:
     705        return formats.time_format(value, arg)
     706    except AttributeError:
     707        try:
     708            return dateformat.time_format(value, arg)
     709        except AttributeError:
     710            return ''
     711time.is_safe = False
     712
     713def timesince(value, arg=None):
     714    """Formats a date as the time since that date (i.e. "4 days, 6 hours")."""
     715    from django.utils.timesince import timesince
     716    if not value:
     717        return u''
     718    try:
     719        if arg:
     720            return timesince(value, arg)
     721        return timesince(value)
     722    except (ValueError, TypeError):
     723        return u''
     724timesince.is_safe = False
     725
     726def timeuntil(value, arg=None):
     727    """Formats a date as the time until that date (i.e. "4 days, 6 hours")."""
     728    from django.utils.timesince import timeuntil
     729    from datetime import datetime
     730    if not value:
     731        return u''
     732    try:
     733        return timeuntil(value, arg)
     734    except (ValueError, TypeError):
     735        return u''
     736timeuntil.is_safe = False
     737
     738###################
     739# LOGIC           #
     740###################
     741
     742def default(value, arg):
     743    """If value is unavailable, use given default."""
     744    return value or arg
     745default.is_safe = False
     746
     747def default_if_none(value, arg):
     748    """If value is None, use given default."""
     749    if value is None:
     750        return arg
     751    return value
     752default_if_none.is_safe = False
     753
     754def divisibleby(value, arg):
     755    """Returns True if the value is devisible by the argument."""
     756    return int(value) % int(arg) == 0
     757divisibleby.is_safe = False
     758
     759def yesno(value, arg=None):
     760    """
     761    Given a string mapping values for true, false and (optionally) None,
     762    returns one of those strings accoding to the value:
     763
     764    ==========  ======================  ==================================
     765    Value       Argument                Outputs
     766    ==========  ======================  ==================================
     767    ``True``    ``"yeah,no,maybe"``     ``yeah``
     768    ``False``   ``"yeah,no,maybe"``     ``no``
     769    ``None``    ``"yeah,no,maybe"``     ``maybe``
     770    ``None``    ``"yeah,no"``           ``"no"`` (converts None to False
     771                                        if no mapping for None is given.
     772    ==========  ======================  ==================================
     773    """
     774    if arg is None:
     775        arg = ugettext('yes,no,maybe')
     776    bits = arg.split(u',')
     777    if len(bits) < 2:
     778        return value # Invalid arg.
     779    try:
     780        yes, no, maybe = bits
     781    except ValueError:
     782        # Unpack list of wrong size (no "maybe" value provided).
     783        yes, no, maybe = bits[0], bits[1], bits[1]
     784    if value is None:
     785        return maybe
     786    if value:
     787        return yes
     788    return no
     789yesno.is_safe = False
     790
     791###################
     792# MISC            #
     793###################
     794
     795def filesizeformat(bytes):
     796    """
     797    Formats the value like a 'human-readable' file size (i.e. 13 KB, 4.1 MB,
     798    102 bytes, etc).
     799    """
     800    try:
     801        bytes = float(bytes)
     802    except TypeError:
     803        return u"0 bytes"
     804
     805    if bytes < 1024:
     806        return ungettext("%(size)d byte", "%(size)d bytes", bytes) % {'size': bytes}
     807    if bytes < 1024 * 1024:
     808        return ugettext("%.1f KB") % (bytes / 1024)
     809    if bytes < 1024 * 1024 * 1024:
     810        return ugettext("%.1f MB") % (bytes / (1024 * 1024))
     811    return ugettext("%.1f GB") % (bytes / (1024 * 1024 * 1024))
     812filesizeformat.is_safe = True
     813
     814def pluralize(value, arg=u's'):
     815    """
     816    Returns a plural suffix if the value is not 1. By default, 's' is used as
     817    the suffix:
     818
     819    * If value is 0, vote{{ value|pluralize }} displays "0 votes".
     820    * If value is 1, vote{{ value|pluralize }} displays "1 vote".
     821    * If value is 2, vote{{ value|pluralize }} displays "2 votes".
     822
     823    If an argument is provided, that string is used instead:
     824
     825    * If value is 0, class{{ value|pluralize:"es" }} displays "0 classes".
     826    * If value is 1, class{{ value|pluralize:"es" }} displays "1 class".
     827    * If value is 2, class{{ value|pluralize:"es" }} displays "2 classes".
     828
     829    If the provided argument contains a comma, the text before the comma is
     830    used for the singular case and the text after the comma is used for the
     831    plural case:
     832
     833    * If value is 0, cand{{ value|pluralize:"y,ies" }} displays "0 candies".
     834    * If value is 1, cand{{ value|pluralize:"y,ies" }} displays "1 candy".
     835    * If value is 2, cand{{ value|pluralize:"y,ies" }} displays "2 candies".
     836    """
     837    if not u',' in arg:
     838        arg = u',' + arg
     839    bits = arg.split(u',')
     840    if len(bits) > 2:
     841        return u''
     842    singular_suffix, plural_suffix = bits[:2]
     843
     844    try:
     845        if int(value) != 1:
     846            return plural_suffix
     847    except ValueError: # Invalid string that's not a number.
     848        pass
     849    except TypeError: # Value isn't a string or a number; maybe it's a list?
     850        try:
     851            if len(value) != 1:
     852                return plural_suffix
     853        except TypeError: # len() of unsized object.
     854            pass
     855    return singular_suffix
     856pluralize.is_safe = False
     857
     858def phone2numeric(value):
     859    """Takes a phone number and converts it in to its numerical equivalent."""
     860    from django.utils.text import phone2numeric
     861    return phone2numeric(value)
     862phone2numeric.is_safe = True
     863
     864def pprint(value):
     865    """A wrapper around pprint.pprint -- for debugging, really."""
     866    from pprint import pformat
     867    try:
     868        return pformat(value)
     869    except Exception, e:
     870        return u"Error in formatting: %s" % force_unicode(e, errors="replace")
     871pprint.is_safe = True
     872
     873# Syntax: register.filter(name of filter, callback)
     874register.filter(add)
     875register.filter(addslashes)
     876register.filter(capfirst)
     877register.filter(center)
     878register.filter(cut)
     879register.filter(date)
     880register.filter(default)
     881register.filter(default_if_none)
     882register.filter(dictsort)
     883register.filter(dictsortreversed)
     884register.filter(divisibleby)
     885register.filter(escape)
     886register.filter(escapejs)
     887register.filter(filesizeformat)
     888register.filter(first)
     889register.filter(fix_ampersands)
     890register.filter(floatformat)
     891register.filter(force_escape)
     892register.filter(get_digit)
     893register.filter(iriencode)
     894register.filter(join)
     895register.filter(last)
     896register.filter(length)
     897register.filter(length_is)
     898register.filter(linebreaks)
     899register.filter(linebreaksbr)
     900register.filter(linenumbers)
     901register.filter(ljust)
     902register.filter(lower)
     903register.filter(make_list)
     904register.filter(phone2numeric)
     905register.filter(pluralize)
     906register.filter(pprint)
     907register.filter(removetags)
     908register.filter(random)
     909register.filter(rjust)
     910register.filter(safe)
     911register.filter(safeseq)
     912register.filter('slice', slice_)
     913register.filter(slugify)
     914register.filter(stringformat)
     915register.filter(striptags)
     916register.filter(time)
     917register.filter(timesince)
     918register.filter(timeuntil)
     919register.filter(title)
     920register.filter(truncatewords)
     921register.filter(truncatewords_html)
     922register.filter(unordered_list)
     923register.filter(upper)
     924register.filter(urlencode)
     925register.filter(urlize)
     926register.filter(urlizetrunc)
     927register.filter(wordcount)
     928register.filter(wordwrap)
     929register.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..f1b1de7
    - +  
     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.template.smartif import IfParser, Literal
     15from django.conf import settings
     16from django.utils.encoding import smart_str, smart_unicode
     17from django.utils.itercompat import groupby
     18from django.utils.safestring import mark_safe
     19
     20register = Library()
     21
     22class AutoEscapeControlNode(Node):
     23    """Implements the actions of the autoescape tag."""
     24    def __init__(self, setting, nodelist):
     25        self.setting, self.nodelist = setting, nodelist
     26
     27    def render(self, context):
     28        old_setting = context.autoescape
     29        context.autoescape = self.setting
     30        output = self.nodelist.render(context)
     31        context.autoescape = old_setting
     32        if self.setting:
     33            return mark_safe(output)
     34        else:
     35            return output
     36
     37class CommentNode(Node):
     38    def render(self, context):
     39        return ''
     40
     41class CsrfTokenNode(Node):
     42    def render(self, context):
     43        csrf_token = context.get('csrf_token', None)
     44        if csrf_token:
     45            if csrf_token == 'NOTPROVIDED':
     46                return mark_safe(u"")
     47            else:
     48                return mark_safe(u"<div style='display:none'><input type='hidden' name='csrfmiddlewaretoken' value='%s' /></div>" % (csrf_token))
     49        else:
     50            # It's very probable that the token is missing because of
     51            # misconfiguration, so we raise a warning
     52            from django.conf import settings
     53            if settings.DEBUG:
     54                import warnings
     55                warnings.warn("A {% csrf_token %} was used in a template, but the context did not provide the value.  This is usually caused by not using RequestContext.")
     56            return u''
     57
     58class CycleNode(Node):
     59    def __init__(self, cyclevars, variable_name=None):
     60        self.cyclevars = cyclevars
     61        self.variable_name = variable_name
     62
     63    def render(self, context):
     64        if self not in context.render_context:
     65            context.render_context[self] = itertools_cycle(self.cyclevars)
     66        cycle_iter = context.render_context[self]
     67        value = cycle_iter.next().resolve(context)
     68        if self.variable_name:
     69            context[self.variable_name] = value
     70        return value
     71
     72class DebugNode(Node):
     73    def render(self, context):
     74        from pprint import pformat
     75        output = [pformat(val) for val in context]
     76        output.append('\n\n')
     77        output.append(pformat(sys.modules))
     78        return ''.join(output)
     79
     80class FilterNode(Node):
     81    def __init__(self, filter_expr, nodelist):
     82        self.filter_expr, self.nodelist = filter_expr, nodelist
     83
     84    def render(self, context):
     85        output = self.nodelist.render(context)
     86        # Apply filters.
     87        context.update({'var': output})
     88        filtered = self.filter_expr.resolve(context)
     89        context.pop()
     90        return filtered
     91
     92class FirstOfNode(Node):
     93    def __init__(self, vars):
     94        self.vars = vars
     95
     96    def render(self, context):
     97        for var in self.vars:
     98            value = var.resolve(context, True)
     99            if value:
     100                return smart_unicode(value)
     101        return u''
     102
     103class ForNode(Node):
     104    def __init__(self, loopvars, sequence, is_reversed, nodelist_loop, nodelist_empty=None):
     105        self.loopvars, self.sequence = loopvars, sequence
     106        self.is_reversed = is_reversed
     107        self.nodelist_loop = nodelist_loop
     108        if nodelist_empty is None:
     109            self.nodelist_empty = NodeList()
     110        else:
     111            self.nodelist_empty = nodelist_empty
     112
     113    def __repr__(self):
     114        reversed_text = self.is_reversed and ' reversed' or ''
     115        return "<For Node: for %s in %s, tail_len: %d%s>" % \
     116            (', '.join(self.loopvars), self.sequence, len(self.nodelist_loop),
     117             reversed_text)
     118
     119    def __iter__(self):
     120        for node in self.nodelist_loop:
     121            yield node
     122        for node in self.nodelist_empty:
     123            yield node
     124
     125    def get_nodes_by_type(self, nodetype):
     126        nodes = []
     127        if isinstance(self, nodetype):
     128            nodes.append(self)
     129        nodes.extend(self.nodelist_loop.get_nodes_by_type(nodetype))
     130        nodes.extend(self.nodelist_empty.get_nodes_by_type(nodetype))
     131        return nodes
     132
     133    def render(self, context):
     134        if 'forloop' in context:
     135            parentloop = context['forloop']
     136        else:
     137            parentloop = {}
     138        context.push()
     139        try:
     140            values = self.sequence.resolve(context, True)
     141        except VariableDoesNotExist:
     142            values = []
     143        if values is None:
     144            values = []
     145        if not hasattr(values, '__len__'):
     146            values = list(values)
     147        len_values = len(values)
     148        if len_values < 1:
     149            context.pop()
     150            return self.nodelist_empty.render(context)
     151        nodelist = NodeList()
     152        if self.is_reversed:
     153            values = reversed(values)
     154        unpack = len(self.loopvars) > 1
     155        # Create a forloop value in the context.  We'll update counters on each
     156        # iteration just below.
     157        loop_dict = context['forloop'] = {'parentloop': parentloop}
     158        for i, item in enumerate(values):
     159            # Shortcuts for current loop iteration number.
     160            loop_dict['counter0'] = i
     161            loop_dict['counter'] = i+1
     162            # Reverse counter iteration numbers.
     163            loop_dict['revcounter'] = len_values - i
     164            loop_dict['revcounter0'] = len_values - i - 1
     165            # Boolean values designating first and last times through loop.
     166            loop_dict['first'] = (i == 0)
     167            loop_dict['last'] = (i == len_values - 1)
     168
     169            if unpack:
     170                # If there are multiple loop variables, unpack the item into
     171                # them.
     172                context.update(dict(zip(self.loopvars, item)))
     173            else:
     174                context[self.loopvars[0]] = item
     175            for node in self.nodelist_loop:
     176                nodelist.append(node.render(context))
     177            if unpack:
     178                # The loop variables were pushed on to the context so pop them
     179                # off again. This is necessary because the tag lets the length
     180                # of loopvars differ to the length of each set of items and we
     181                # don't want to leave any vars from the previous loop on the
     182                # context.
     183                context.pop()
     184        context.pop()
     185        return nodelist.render(context)
     186
     187class IfChangedNode(Node):
     188    def __init__(self, nodelist_true, nodelist_false, *varlist):
     189        self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false
     190        self._last_seen = None
     191        self._varlist = varlist
     192        self._id = str(id(self))
     193
     194    def render(self, context):
     195        if 'forloop' in context and self._id not in context['forloop']:
     196            self._last_seen = None
     197            context['forloop'][self._id] = 1
     198        try:
     199            if self._varlist:
     200                # Consider multiple parameters.  This automatically behaves
     201                # like an OR evaluation of the multiple variables.
     202                compare_to = [var.resolve(context, True) for var in self._varlist]
     203            else:
     204                compare_to = self.nodelist_true.render(context)
     205        except VariableDoesNotExist:
     206            compare_to = None
     207
     208        if compare_to != self._last_seen:
     209            firstloop = (self._last_seen == None)
     210            self._last_seen = compare_to
     211            content = self.nodelist_true.render(context)
     212            return content
     213        elif self.nodelist_false:
     214            return self.nodelist_false.render(context)
     215        return ''
     216
     217class IfEqualNode(Node):
     218    def __init__(self, var1, var2, nodelist_true, nodelist_false, negate):
     219        self.var1, self.var2 = var1, var2
     220        self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false
     221        self.negate = negate
     222
     223    def __repr__(self):
     224        return "<IfEqualNode>"
     225
     226    def render(self, context):
     227        val1 = self.var1.resolve(context, True)
     228        val2 = self.var2.resolve(context, True)
     229        if (self.negate and val1 != val2) or (not self.negate and val1 == val2):
     230            return self.nodelist_true.render(context)
     231        return self.nodelist_false.render(context)
     232
     233class IfNode(Node):
     234    def __init__(self, var, nodelist_true, nodelist_false=None):
     235        self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false
     236        self.var = var
     237
     238    def __repr__(self):
     239        return "<If node>"
     240
     241    def __iter__(self):
     242        for node in self.nodelist_true:
     243            yield node
     244        for node in self.nodelist_false:
     245            yield node
     246
     247    def get_nodes_by_type(self, nodetype):
     248        nodes = []
     249        if isinstance(self, nodetype):
     250            nodes.append(self)
     251        nodes.extend(self.nodelist_true.get_nodes_by_type(nodetype))
     252        nodes.extend(self.nodelist_false.get_nodes_by_type(nodetype))
     253        return nodes
     254
     255    def render(self, context):
     256        if self.var.eval(context):
     257            return self.nodelist_true.render(context)
     258        else:
     259            return self.nodelist_false.render(context)
     260
     261class RegroupNode(Node):
     262    def __init__(self, target, expression, var_name):
     263        self.target, self.expression = target, expression
     264        self.var_name = var_name
     265
     266    def render(self, context):
     267        obj_list = self.target.resolve(context, True)
     268        if obj_list == None:
     269            # target variable wasn't found in context; fail silently.
     270            context[self.var_name] = []
     271            return ''
     272        # List of dictionaries in the format:
     273        # {'grouper': 'key', 'list': [list of contents]}.
     274        context[self.var_name] = [
     275            {'grouper': key, 'list': list(val)}
     276            for key, val in
     277            groupby(obj_list, lambda v, f=self.expression.resolve: f(v, True))
     278        ]
     279        return ''
     280
     281def include_is_allowed(filepath):
     282    for root in settings.ALLOWED_INCLUDE_ROOTS:
     283        if filepath.startswith(root):
     284            return True
     285    return False
     286
     287class SsiNode(Node):
     288    def __init__(self, filepath, parsed):
     289        self.filepath, self.parsed = filepath, parsed
     290
     291    def render(self, context):
     292        if not include_is_allowed(self.filepath):
     293            if settings.DEBUG:
     294                return "[Didn't have permission to include file]"
     295            else:
     296                return '' # Fail silently for invalid includes.
     297        try:
     298            fp = open(self.filepath, 'r')
     299            output = fp.read()
     300            fp.close()
     301        except IOError:
     302            output = ''
     303        if self.parsed:
     304            try:
     305                t = Template(output, name=self.filepath)
     306                return t.render(context)
     307            except TemplateSyntaxError, e:
     308                if settings.DEBUG:
     309                    return "[Included template had syntax error: %s]" % e
     310                else:
     311                    return '' # Fail silently for invalid included templates.
     312        return output
     313
     314class LoadNode(Node):
     315    def render(self, context):
     316        return ''
     317
     318class NowNode(Node):
     319    def __init__(self, format_string):
     320        self.format_string = format_string
     321
     322    def render(self, context):
     323        from datetime import datetime
     324        from django.utils.dateformat import DateFormat
     325        df = DateFormat(datetime.now())
     326        return df.format(self.format_string)
     327
     328class SpacelessNode(Node):
     329    def __init__(self, nodelist):
     330        self.nodelist = nodelist
     331
     332    def render(self, context):
     333        from django.utils.html import strip_spaces_between_tags
     334        return strip_spaces_between_tags(self.nodelist.render(context).strip())
     335
     336class TemplateTagNode(Node):
     337    mapping = {'openblock': BLOCK_TAG_START,
     338               'closeblock': BLOCK_TAG_END,
     339               'openvariable': VARIABLE_TAG_START,
     340               'closevariable': VARIABLE_TAG_END,
     341               'openbrace': SINGLE_BRACE_START,
     342               'closebrace': SINGLE_BRACE_END,
     343               'opencomment': COMMENT_TAG_START,
     344               'closecomment': COMMENT_TAG_END,
     345               }
     346
     347    def __init__(self, tagtype):
     348        self.tagtype = tagtype
     349
     350    def render(self, context):
     351        return self.mapping.get(self.tagtype, '')
     352
     353class URLNode(Node):
     354    def __init__(self, view_name, args, kwargs, asvar):
     355        self.view_name = view_name
     356        self.args = args
     357        self.kwargs = kwargs
     358        self.asvar = asvar
     359
     360    def render(self, context):
     361        from django.core.urlresolvers import reverse, NoReverseMatch
     362        args = [arg.resolve(context) for arg in self.args]
     363        kwargs = dict([(smart_str(k,'ascii'), v.resolve(context))
     364                       for k, v in self.kwargs.items()])
     365
     366        # Try to look up the URL twice: once given the view name, and again
     367        # relative to what we guess is the "main" app. If they both fail,
     368        # re-raise the NoReverseMatch unless we're using the
     369        # {% url ... as var %} construct in which cause return nothing.
     370        url = ''
     371        try:
     372            url = reverse(self.view_name, args=args, kwargs=kwargs, current_app=context.current_app)
     373        except NoReverseMatch, e:
     374            if settings.SETTINGS_MODULE:
     375                project_name = settings.SETTINGS_MODULE.split('.')[0]
     376                try:
     377                    url = reverse(project_name + '.' + self.view_name,
     378                              args=args, kwargs=kwargs, current_app=context.current_app)
     379                except NoReverseMatch:
     380                    if self.asvar is None:
     381                        # Re-raise the original exception, not the one with
     382                        # the path relative to the project. This makes a
     383                        # better error message.
     384                        raise e
     385            else:
     386                if self.asvar is None:
     387                    raise e
     388
     389        if self.asvar:
     390            context[self.asvar] = url
     391            return ''
     392        else:
     393            return url
     394
     395class WidthRatioNode(Node):
     396    def __init__(self, val_expr, max_expr, max_width):
     397        self.val_expr = val_expr
     398        self.max_expr = max_expr
     399        self.max_width = max_width
     400
     401    def render(self, context):
     402        try:
     403            value = self.val_expr.resolve(context)
     404            maxvalue = self.max_expr.resolve(context)
     405            max_width = int(self.max_width.resolve(context))
     406        except VariableDoesNotExist:
     407            return ''
     408        except ValueError:
     409            raise TemplateSyntaxError("widthratio final argument must be an number")
     410        try:
     411            value = float(value)
     412            maxvalue = float(maxvalue)
     413            ratio = (value / maxvalue) * max_width
     414        except (ValueError, ZeroDivisionError):
     415            return ''
     416        return str(int(round(ratio)))
     417
     418class WithNode(Node):
     419    def __init__(self, var, name, nodelist):
     420        self.var = var
     421        self.name = name
     422        self.nodelist = nodelist
     423
     424    def __repr__(self):
     425        return "<WithNode>"
     426
     427    def render(self, context):
     428        val = self.var.resolve(context)
     429        context.push()
     430        context[self.name] = val
     431        output = self.nodelist.render(context)
     432        context.pop()
     433        return output
     434
     435#@register.tag
     436def autoescape(parser, token):
     437    """
     438    Force autoescape behaviour for this block.
     439    """
     440    args = token.contents.split()
     441    if len(args) != 2:
     442        raise TemplateSyntaxError("'autoescape' tag requires exactly one argument.")
     443    arg = args[1]
     444    if arg not in (u'on', u'off'):
     445        raise TemplateSyntaxError("'autoescape' argument should be 'on' or 'off'")
     446    nodelist = parser.parse(('endautoescape',))
     447    parser.delete_first_token()
     448    return AutoEscapeControlNode((arg == 'on'), nodelist)
     449autoescape = register.tag(autoescape)
     450
     451#@register.tag
     452def comment(parser, token):
     453    """
     454    Ignores everything between ``{% comment %}`` and ``{% endcomment %}``.
     455    """
     456    parser.skip_past('endcomment')
     457    return CommentNode()
     458comment = register.tag(comment)
     459
     460#@register.tag
     461def cycle(parser, token):
     462    """
     463    Cycles among the given strings each time this tag is encountered.
     464
     465    Within a loop, cycles among the given strings each time through
     466    the loop::
     467
     468        {% for o in some_list %}
     469            <tr class="{% cycle 'row1' 'row2' %}">
     470                ...
     471            </tr>
     472        {% endfor %}
     473
     474    Outside of a loop, give the values a unique name the first time you call
     475    it, then use that name each sucessive time through::
     476
     477            <tr class="{% cycle 'row1' 'row2' 'row3' as rowcolors %}">...</tr>
     478            <tr class="{% cycle rowcolors %}">...</tr>
     479            <tr class="{% cycle rowcolors %}">...</tr>
     480
     481    You can use any number of values, separated by spaces. Commas can also
     482    be used to separate values; if a comma is used, the cycle values are
     483    interpreted as literal strings.
     484    """
     485
     486    # Note: This returns the exact same node on each {% cycle name %} call;
     487    # that is, the node object returned from {% cycle a b c as name %} and the
     488    # one returned from {% cycle name %} are the exact same object. This
     489    # shouldn't cause problems (heh), but if it does, now you know.
     490    #
     491    # Ugly hack warning: This stuffs the named template dict into parser so
     492    # that names are only unique within each template (as opposed to using
     493    # a global variable, which would make cycle names have to be unique across
     494    # *all* templates.
     495
     496    args = token.split_contents()
     497
     498    if len(args) < 2:
     499        raise TemplateSyntaxError("'cycle' tag requires at least two arguments")
     500
     501    if ',' in args[1]:
     502        # Backwards compatibility: {% cycle a,b %} or {% cycle a,b as foo %}
     503        # case.
     504        args[1:2] = ['"%s"' % arg for arg in args[1].split(",")]
     505
     506    if len(args) == 2:
     507        # {% cycle foo %} case.
     508        name = args[1]
     509        if not hasattr(parser, '_namedCycleNodes'):
     510            raise TemplateSyntaxError("No named cycles in template. '%s' is not defined" % name)
     511        if not name in parser._namedCycleNodes:
     512            raise TemplateSyntaxError("Named cycle '%s' does not exist" % name)
     513        return parser._namedCycleNodes[name]
     514
     515    if len(args) > 4 and args[-2] == 'as':
     516        name = args[-1]
     517        values = [parser.compile_filter(arg) for arg in args[1:-2]]
     518        node = CycleNode(values, name)
     519        if not hasattr(parser, '_namedCycleNodes'):
     520            parser._namedCycleNodes = {}
     521        parser._namedCycleNodes[name] = node
     522    else:
     523        values = [parser.compile_filter(arg) for arg in args[1:]]
     524        node = CycleNode(values)
     525    return node
     526cycle = register.tag(cycle)
     527
     528def csrf_token(parser, token):
     529    return CsrfTokenNode()
     530register.tag(csrf_token)
     531
     532def debug(parser, token):
     533    """
     534    Outputs a whole load of debugging information, including the current
     535    context and imported modules.
     536
     537    Sample usage::
     538
     539        <pre>
     540            {% debug %}
     541        </pre>
     542    """
     543    return DebugNode()
     544debug = register.tag(debug)
     545
     546#@register.tag(name="filter")
     547def do_filter(parser, token):
     548    """
     549    Filters the contents of the block through variable filters.
     550
     551    Filters can also be piped through each other, and they can have
     552    arguments -- just like in variable syntax.
     553
     554    Sample usage::
     555
     556        {% filter force_escape|lower %}
     557            This text will be HTML-escaped, and will appear in lowercase.
     558        {% endfilter %}
     559    """
     560    _, rest = token.contents.split(None, 1)
     561    filter_expr = parser.compile_filter("var|%s" % (rest))
     562    for func, unused in filter_expr.filters:
     563        if getattr(func, '_decorated_function', func).__name__ in ('escape', 'safe'):
     564            raise TemplateSyntaxError('"filter %s" is not permitted.  Use the "autoescape" tag instead.' % func.__name__)
     565    nodelist = parser.parse(('endfilter',))
     566    parser.delete_first_token()
     567    return FilterNode(filter_expr, nodelist)
     568do_filter = register.tag("filter", do_filter)
     569
     570#@register.tag
     571def firstof(parser, token):
     572    """
     573    Outputs the first variable passed that is not False, without escaping.
     574
     575    Outputs nothing if all the passed variables are False.
     576
     577    Sample usage::
     578
     579        {% firstof var1 var2 var3 %}
     580
     581    This is equivalent to::
     582
     583        {% if var1 %}
     584            {{ var1|safe }}
     585        {% else %}{% if var2 %}
     586            {{ var2|safe }}
     587        {% else %}{% if var3 %}
     588            {{ var3|safe }}
     589        {% endif %}{% endif %}{% endif %}
     590
     591    but obviously much cleaner!
     592
     593    You can also use a literal string as a fallback value in case all
     594    passed variables are False::
     595
     596        {% firstof var1 var2 var3 "fallback value" %}
     597
     598    If you want to escape the output, use a filter tag::
     599
     600        {% filter force_escape %}
     601            {% firstof var1 var2 var3 "fallback value" %}
     602        {% endfilter %}
     603
     604    """
     605    bits = token.split_contents()[1:]
     606    if len(bits) < 1:
     607        raise TemplateSyntaxError("'firstof' statement requires at least one argument")
     608    return FirstOfNode([parser.compile_filter(bit) for bit in bits])
     609firstof = register.tag(firstof)
     610
     611#@register.tag(name="for")
     612def do_for(parser, token):
     613    """
     614    Loops over each item in an array.
     615
     616    For example, to display a list of athletes given ``athlete_list``::
     617
     618        <ul>
     619        {% for athlete in athlete_list %}
     620            <li>{{ athlete.name }}</li>
     621        {% endfor %}
     622        </ul>
     623
     624    You can loop over a list in reverse by using
     625    ``{% for obj in list reversed %}``.
     626
     627    You can also unpack multiple values from a two-dimensional array::
     628
     629        {% for key,value in dict.items %}
     630            {{ key }}: {{ value }}
     631        {% endfor %}
     632
     633    The ``for`` tag can take an optional ``{% empty %}`` clause that will
     634    be displayed if the given array is empty or could not be found::
     635
     636        <ul>
     637          {% for athlete in athlete_list %}
     638            <li>{{ athlete.name }}</li>
     639          {% empty %}
     640            <li>Sorry, no athletes in this list.</li>
     641          {% endfor %}
     642        <ul>
     643
     644    The above is equivalent to -- but shorter, cleaner, and possibly faster
     645    than -- the following::
     646
     647        <ul>
     648          {% if althete_list %}
     649            {% for athlete in athlete_list %}
     650              <li>{{ athlete.name }}</li>
     651            {% endfor %}
     652          {% else %}
     653            <li>Sorry, no athletes in this list.</li>
     654          {% endif %}
     655        </ul>
     656
     657    The for loop sets a number of variables available within the loop:
     658
     659        ==========================  ================================================
     660        Variable                    Description
     661        ==========================  ================================================
     662        ``forloop.counter``         The current iteration of the loop (1-indexed)
     663        ``forloop.counter0``        The current iteration of the loop (0-indexed)
     664        ``forloop.revcounter``      The number of iterations from the end of the
     665                                    loop (1-indexed)
     666        ``forloop.revcounter0``     The number of iterations from the end of the
     667                                    loop (0-indexed)
     668        ``forloop.first``           True if this is the first time through the loop
     669        ``forloop.last``            True if this is the last time through the loop
     670        ``forloop.parentloop``      For nested loops, this is the loop "above" the
     671                                    current one
     672        ==========================  ================================================
     673
     674    """
     675    bits = token.contents.split()
     676    if len(bits) < 4:
     677        raise TemplateSyntaxError("'for' statements should have at least four"
     678                                  " words: %s" % token.contents)
     679
     680    is_reversed = bits[-1] == 'reversed'
     681    in_index = is_reversed and -3 or -2
     682    if bits[in_index] != 'in':
     683        raise TemplateSyntaxError("'for' statements should use the format"
     684                                  " 'for x in y': %s" % token.contents)
     685
     686    loopvars = re.sub(r' *, *', ',', ' '.join(bits[1:in_index])).split(',')
     687    for var in loopvars:
     688        if not var or ' ' in var:
     689            raise TemplateSyntaxError("'for' tag received an invalid argument:"
     690                                      " %s" % token.contents)
     691
     692    sequence = parser.compile_filter(bits[in_index+1])
     693    nodelist_loop = parser.parse(('empty', 'endfor',))
     694    token = parser.next_token()
     695    if token.contents == 'empty':
     696        nodelist_empty = parser.parse(('endfor',))
     697        parser.delete_first_token()
     698    else:
     699        nodelist_empty = None
     700    return ForNode(loopvars, sequence, is_reversed, nodelist_loop, nodelist_empty)
     701do_for = register.tag("for", do_for)
     702
     703def do_ifequal(parser, token, negate):
     704    bits = list(token.split_contents())
     705    if len(bits) != 3:
     706        raise TemplateSyntaxError("%r takes two arguments" % bits[0])
     707    end_tag = 'end' + bits[0]
     708    nodelist_true = parser.parse(('else', end_tag))
     709    token = parser.next_token()
     710    if token.contents == 'else':
     711        nodelist_false = parser.parse((end_tag,))
     712        parser.delete_first_token()
     713    else:
     714        nodelist_false = NodeList()
     715    val1 = parser.compile_filter(bits[1])
     716    val2 = parser.compile_filter(bits[2])
     717    return IfEqualNode(val1, val2, nodelist_true, nodelist_false, negate)
     718
     719#@register.tag
     720def ifequal(parser, token):
     721    """
     722    Outputs the contents of the block if the two arguments equal each other.
     723
     724    Examples::
     725
     726        {% ifequal user.id comment.user_id %}
     727            ...
     728        {% endifequal %}
     729
     730        {% ifnotequal user.id comment.user_id %}
     731            ...
     732        {% else %}
     733            ...
     734        {% endifnotequal %}
     735    """
     736    return do_ifequal(parser, token, False)
     737ifequal = register.tag(ifequal)
     738
     739#@register.tag
     740def ifnotequal(parser, token):
     741    """
     742    Outputs the contents of the block if the two arguments are not equal.
     743    See ifequal.
     744    """
     745    return do_ifequal(parser, token, True)
     746ifnotequal = register.tag(ifnotequal)
     747
     748class TemplateLiteral(Literal):
     749    def __init__(self, value, text):
     750        self.value = value
     751        self.text = text # for better error messages
     752
     753    def display(self):
     754        return self.text
     755
     756    def eval(self, context):
     757        return self.value.resolve(context, ignore_failures=True)
     758
     759class TemplateIfParser(IfParser):
     760    error_class = TemplateSyntaxError
     761
     762    def __init__(self, parser, *args, **kwargs):
     763        self.template_parser = parser
     764        return super(TemplateIfParser, self).__init__(*args, **kwargs)
     765
     766    def create_var(self, value):
     767        return TemplateLiteral(self.template_parser.compile_filter(value), value)
     768
     769#@register.tag(name="if")
     770def do_if(parser, token):
     771    """
     772    The ``{% if %}`` tag evaluates a variable, and if that variable is "true"
     773    (i.e., exists, is not empty, and is not a false boolean value), the
     774    contents of the block are output:
     775
     776    ::
     777
     778        {% if athlete_list %}
     779            Number of athletes: {{ athlete_list|count }}
     780        {% else %}
     781            No athletes.
     782        {% endif %}
     783
     784    In the above, if ``athlete_list`` is not empty, the number of athletes will
     785    be displayed by the ``{{ athlete_list|count }}`` variable.
     786
     787    As you can see, the ``if`` tag can take an option ``{% else %}`` clause
     788    that will be displayed if the test fails.
     789
     790    ``if`` tags may use ``or``, ``and`` or ``not`` to test a number of
     791    variables or to negate a given variable::
     792
     793        {% if not athlete_list %}
     794            There are no athletes.
     795        {% endif %}
     796
     797        {% if athlete_list or coach_list %}
     798            There are some athletes or some coaches.
     799        {% endif %}
     800
     801        {% if athlete_list and coach_list %}
     802            Both atheletes and coaches are available.
     803        {% endif %}
     804
     805        {% if not athlete_list or coach_list %}
     806            There are no athletes, or there are some coaches.
     807        {% endif %}
     808
     809        {% if athlete_list and not coach_list %}
     810            There are some athletes and absolutely no coaches.
     811        {% endif %}
     812
     813    Comparison operators are also available, and the use of filters is also
     814    allowed, for example:
     815
     816        {% if articles|length >= 5 %}...{% endif %}
     817
     818    Arguments and operators _must_ have a space between them, so
     819    ``{% if 1>2 %}`` is not a valid if tag.
     820
     821    All supported operators are: ``or``, ``and``, ``in``, ``==`` (or ``=``),
     822    ``!=``, ``>``, ``>=``, ``<`` and ``<=``.
     823
     824    Operator precedence follows Python.
     825    """
     826    bits = token.split_contents()[1:]
     827    var = TemplateIfParser(parser, bits).parse()
     828    nodelist_true = parser.parse(('else', 'endif'))
     829    token = parser.next_token()
     830    if token.contents == 'else':
     831        nodelist_false = parser.parse(('endif',))
     832        parser.delete_first_token()
     833    else:
     834        nodelist_false = NodeList()
     835    return IfNode(var, nodelist_true, nodelist_false)
     836do_if = register.tag("if", do_if)
     837
     838#@register.tag
     839def ifchanged(parser, token):
     840    """
     841    Checks if a value has changed from the last iteration of a loop.
     842
     843    The 'ifchanged' block tag is used within a loop. It has two possible uses.
     844
     845    1. Checks its own rendered contents against its previous state and only
     846       displays the content if it has changed. For example, this displays a
     847       list of days, only displaying the month if it changes::
     848
     849            <h1>Archive for {{ year }}</h1>
     850
     851            {% for date in days %}
     852                {% ifchanged %}<h3>{{ date|date:"F" }}</h3>{% endifchanged %}
     853                <a href="{{ date|date:"M/d"|lower }}/">{{ date|date:"j" }}</a>
     854            {% endfor %}
     855
     856    2. If given a variable, check whether that variable has changed.
     857       For example, the following shows the date every time it changes, but
     858       only shows the hour if both the hour and the date have changed::
     859
     860            {% for date in days %}
     861                {% ifchanged date.date %} {{ date.date }} {% endifchanged %}
     862                {% ifchanged date.hour date.date %}
     863                    {{ date.hour }}
     864                {% endifchanged %}
     865            {% endfor %}
     866    """
     867    bits = token.contents.split()
     868    nodelist_true = parser.parse(('else', 'endifchanged'))
     869    token = parser.next_token()
     870    if token.contents == 'else':
     871        nodelist_false = parser.parse(('endifchanged',))
     872        parser.delete_first_token()
     873    else:
     874        nodelist_false = NodeList()
     875    values = [parser.compile_filter(bit) for bit in bits[1:]]
     876    return IfChangedNode(nodelist_true, nodelist_false, *values)
     877ifchanged = register.tag(ifchanged)
     878
     879#@register.tag
     880def ssi(parser, token):
     881    """
     882    Outputs the contents of a given file into the page.
     883
     884    Like a simple "include" tag, the ``ssi`` tag includes the contents
     885    of another file -- which must be specified using an absolute path --
     886    in the current page::
     887
     888        {% ssi /home/html/ljworld.com/includes/right_generic.html %}
     889
     890    If the optional "parsed" parameter is given, the contents of the included
     891    file are evaluated as template code, with the current context::
     892
     893        {% ssi /home/html/ljworld.com/includes/right_generic.html parsed %}
     894    """
     895    bits = token.contents.split()
     896    parsed = False
     897    if len(bits) not in (2, 3):
     898        raise TemplateSyntaxError("'ssi' tag takes one argument: the path to"
     899                                  " the file to be included")
     900    if len(bits) == 3:
     901        if bits[2] == 'parsed':
     902            parsed = True
     903        else:
     904            raise TemplateSyntaxError("Second (optional) argument to %s tag"
     905                                      " must be 'parsed'" % bits[0])
     906    return SsiNode(bits[1], parsed)
     907ssi = register.tag(ssi)
     908
     909#@register.tag
     910def load(parser, token):
     911    """
     912    Loads a custom template tag set.
     913
     914    For example, to load the template tags in
     915    ``django/templatetags/news/photos.py``::
     916
     917        {% load news.photos %}
     918    """
     919    bits = token.contents.split()
     920    for taglib in bits[1:]:
     921        # add the library to the parser
     922        try:
     923            lib = get_library(taglib)
     924            parser.add_library(lib)
     925        except InvalidTemplateLibrary, e:
     926            raise TemplateSyntaxError("'%s' is not a valid tag library: %s" %
     927                                      (taglib, e))
     928    return LoadNode()
     929load = register.tag(load)
     930
     931#@register.tag
     932def now(parser, token):
     933    """
     934    Displays the date, formatted according to the given string.
     935
     936    Uses the same format as PHP's ``date()`` function; see http://php.net/date
     937    for all the possible values.
     938
     939    Sample usage::
     940
     941        It is {% now "jS F Y H:i" %}
     942    """
     943    bits = token.contents.split('"')
     944    if len(bits) != 3:
     945        raise TemplateSyntaxError("'now' statement takes one argument")
     946    format_string = bits[1]
     947    return NowNode(format_string)
     948now = register.tag(now)
     949
     950#@register.tag
     951def regroup(parser, token):
     952    """
     953    Regroups a list of alike objects by a common attribute.
     954
     955    This complex tag is best illustrated by use of an example:  say that
     956    ``people`` is a list of ``Person`` objects that have ``first_name``,
     957    ``last_name``, and ``gender`` attributes, and you'd like to display a list
     958    that looks like:
     959
     960        * Male:
     961            * George Bush
     962            * Bill Clinton
     963        * Female:
     964            * Margaret Thatcher
     965            * Colendeeza Rice
     966        * Unknown:
     967            * Pat Smith
     968
     969    The following snippet of template code would accomplish this dubious task::
     970
     971        {% regroup people by gender as grouped %}
     972        <ul>
     973        {% for group in grouped %}
     974            <li>{{ group.grouper }}
     975            <ul>
     976                {% for item in group.list %}
     977                <li>{{ item }}</li>
     978                {% endfor %}
     979            </ul>
     980        {% endfor %}
     981        </ul>
     982
     983    As you can see, ``{% regroup %}`` populates a variable with a list of
     984    objects with ``grouper`` and ``list`` attributes.  ``grouper`` contains the
     985    item that was grouped by; ``list`` contains the list of objects that share
     986    that ``grouper``.  In this case, ``grouper`` would be ``Male``, ``Female``
     987    and ``Unknown``, and ``list`` is the list of people with those genders.
     988
     989    Note that ``{% regroup %}`` does not work when the list to be grouped is not
     990    sorted by the key you are grouping by!  This means that if your list of
     991    people was not sorted by gender, you'd need to make sure it is sorted
     992    before using it, i.e.::
     993
     994        {% regroup people|dictsort:"gender" by gender as grouped %}
     995
     996    """
     997    firstbits = token.contents.split(None, 3)
     998    if len(firstbits) != 4:
     999        raise TemplateSyntaxError("'regroup' tag takes five arguments")
     1000    target = parser.compile_filter(firstbits[1])
     1001    if firstbits[2] != 'by':
     1002        raise TemplateSyntaxError("second argument to 'regroup' tag must be 'by'")
     1003    lastbits_reversed = firstbits[3][::-1].split(None, 2)
     1004    if lastbits_reversed[1][::-1] != 'as':
     1005        raise TemplateSyntaxError("next-to-last argument to 'regroup' tag must"
     1006                                  " be 'as'")
     1007
     1008    expression = parser.compile_filter(lastbits_reversed[2][::-1])
     1009
     1010    var_name = lastbits_reversed[0][::-1]
     1011    return RegroupNode(target, expression, var_name)
     1012regroup = register.tag(regroup)
     1013
     1014def spaceless(parser, token):
     1015    """
     1016    Removes whitespace between HTML tags, including tab and newline characters.
     1017
     1018    Example usage::
     1019
     1020        {% spaceless %}
     1021            <p>
     1022                <a href="foo/">Foo</a>
     1023            </p>
     1024        {% endspaceless %}
     1025
     1026    This example would return this HTML::
     1027
     1028        <p><a href="foo/">Foo</a></p>
     1029
     1030    Only space between *tags* is normalized -- not space between tags and text.
     1031    In this example, the space around ``Hello`` won't be stripped::
     1032
     1033        {% spaceless %}
     1034            <strong>
     1035                Hello
     1036            </strong>
     1037        {% endspaceless %}
     1038    """
     1039    nodelist = parser.parse(('endspaceless',))
     1040    parser.delete_first_token()
     1041    return SpacelessNode(nodelist)
     1042spaceless = register.tag(spaceless)
     1043
     1044#@register.tag
     1045def templatetag(parser, token):
     1046    """
     1047    Outputs one of the bits used to compose template tags.
     1048
     1049    Since the template system has no concept of "escaping", to display one of
     1050    the bits used in template tags, you must use the ``{% templatetag %}`` tag.
     1051
     1052    The argument tells which template bit to output:
     1053
     1054        ==================  =======
     1055        Argument            Outputs
     1056        ==================  =======
     1057        ``openblock``       ``{%``
     1058        ``closeblock``      ``%}``
     1059        ``openvariable``    ``{{``
     1060        ``closevariable``   ``}}``
     1061        ``openbrace``       ``{``
     1062        ``closebrace``      ``}``
     1063        ``opencomment``     ``{#``
     1064        ``closecomment``    ``#}``
     1065        ==================  =======
     1066    """
     1067    bits = token.contents.split()
     1068    if len(bits) != 2:
     1069        raise TemplateSyntaxError("'templatetag' statement takes one argument")
     1070    tag = bits[1]
     1071    if tag not in TemplateTagNode.mapping:
     1072        raise TemplateSyntaxError("Invalid templatetag argument: '%s'."
     1073                                  " Must be one of: %s" %
     1074                                  (tag, TemplateTagNode.mapping.keys()))
     1075    return TemplateTagNode(tag)
     1076templatetag = register.tag(templatetag)
     1077
     1078def url(parser, token):
     1079    """
     1080    Returns an absolute URL matching given view with its parameters.
     1081
     1082    This is a way to define links that aren't tied to a particular URL
     1083    configuration::
     1084
     1085        {% url path.to.some_view arg1,arg2,name1=value1 %}
     1086
     1087    The first argument is a path to a view. It can be an absolute python path
     1088    or just ``app_name.view_name`` without the project name if the view is
     1089    located inside the project.  Other arguments are comma-separated values
     1090    that will be filled in place of positional and keyword arguments in the
     1091    URL. All arguments for the URL should be present.
     1092
     1093    For example if you have a view ``app_name.client`` taking client's id and
     1094    the corresponding line in a URLconf looks like this::
     1095
     1096        ('^client/(\d+)/$', 'app_name.client')
     1097
     1098    and this app's URLconf is included into the project's URLconf under some
     1099    path::
     1100
     1101        ('^clients/', include('project_name.app_name.urls'))
     1102
     1103    then in a template you can create a link for a certain client like this::
     1104
     1105        {% url app_name.client client.id %}
     1106
     1107    The URL will look like ``/clients/client/123/``.
     1108    """
     1109    bits = token.split_contents()
     1110    if len(bits) < 2:
     1111        raise TemplateSyntaxError("'%s' takes at least one argument"
     1112                                  " (path to a view)" % bits[0])
     1113    viewname = bits[1]
     1114    args = []
     1115    kwargs = {}
     1116    asvar = None
     1117
     1118    if len(bits) > 2:
     1119        bits = iter(bits[2:])
     1120        for bit in bits:
     1121            if bit == 'as':
     1122                asvar = bits.next()
     1123                break
     1124            else:
     1125                for arg in bit.split(","):
     1126                    if '=' in arg:
     1127                        k, v = arg.split('=', 1)
     1128                        k = k.strip()
     1129                        kwargs[k] = parser.compile_filter(v)
     1130                    elif arg:
     1131                        args.append(parser.compile_filter(arg))
     1132    return URLNode(viewname, args, kwargs, asvar)
     1133url = register.tag(url)
     1134
     1135#@register.tag
     1136def widthratio(parser, token):
     1137    """
     1138    For creating bar charts and such, this tag calculates the ratio of a given
     1139    value to a maximum value, and then applies that ratio to a constant.
     1140
     1141    For example::
     1142
     1143        <img src='bar.gif' height='10' width='{% widthratio this_value max_value 100 %}' />
     1144
     1145    Above, if ``this_value`` is 175 and ``max_value`` is 200, the image in
     1146    the above example will be 88 pixels wide (because 175/200 = .875;
     1147    .875 * 100 = 87.5 which is rounded up to 88).
     1148    """
     1149    bits = token.contents.split()
     1150    if len(bits) != 4:
     1151        raise TemplateSyntaxError("widthratio takes three arguments")
     1152    tag, this_value_expr, max_value_expr, max_width = bits
     1153
     1154    return WidthRatioNode(parser.compile_filter(this_value_expr),
     1155                          parser.compile_filter(max_value_expr),
     1156                          parser.compile_filter(max_width))
     1157widthratio = register.tag(widthratio)
     1158
     1159#@register.tag
     1160def do_with(parser, token):
     1161    """
     1162    Adds a value to the context (inside of this block) for caching and easy
     1163    access.
     1164
     1165    For example::
     1166
     1167        {% with person.some_sql_method as total %}
     1168            {{ total }} object{{ total|pluralize }}
     1169        {% endwith %}
     1170    """
     1171    bits = list(token.split_contents())
     1172    if len(bits) != 4 or bits[2] != "as":
     1173        raise TemplateSyntaxError("%r expected format is 'value as name'" %
     1174                                  bits[0])
     1175    var = parser.compile_filter(bits[1])
     1176    name = bits[3]
     1177    nodelist = parser.parse(('endwith',))
     1178    parser.delete_first_token()
     1179    return WithNode(var, name, nodelist)
     1180do_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 2946208..88afc54 100644
    a b def do_echo(parser, token):  
    5959
    6060register.tag("echo", do_echo)
    6161
    62 template.libraries['django.templatetags.testtags'] = register
     62template.libraries['testtags'] = register
    6363
    6464#####################################
    6565# Helper objects for template tests #
Back to Top