~ubuntu-branches/ubuntu/quantal/python-django/quantal

« back to all changes in this revision

Viewing changes to django/template/defaultfilters.py

  • Committer: Bazaar Package Importer
  • Author(s): Scott James Remnant, Eddy Mulyono
  • Date: 2008-09-16 12:18:47 UTC
  • mfrom: (1.1.5 upstream) (4.1.1 lenny)
  • Revision ID: james.westby@ubuntu.com-20080916121847-mg225rg5mnsdqzr0
Tags: 1.0-1ubuntu1
* Merge from Debian (LP: #264191), remaining changes:
  - Run test suite on build.

[Eddy Mulyono]
* Update patch to workaround network test case failures.

Show diffs side-by-side

added added

removed removed

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