Django

Code

root/django/trunk/django/template/defaultfilters.py

Revision 7336, 25.3 kB (checked in by mtredinnick, 4 months ago)

FIxed #6513 -- Handle overflows better in the floatformat filter. It's not
possible to test this automatically everywhere due to differing representations
on different platforms. Manual testing confirms it works, though.

Thanks, Karen Tracey.

  • Property svn:eol-style set to native
  • Property svn:keywords set to LastChangedRevision
Line 
1 """Default variable filters."""
2
3 import re
4 import random as random_module
5 try:
6     from functools import wraps
7 except ImportError:
8     from django.utils.functional import wraps  # Python 2.3, 2.4 fallback.
9
10 from django.template import Variable, Library
11 from django.conf import settings
12 from django.utils.translation import ugettext, ungettext
13 from django.utils.encoding import force_unicode, iri_to_uri
14 from django.utils.safestring import mark_safe, SafeData
15
16 register = Library()
17
18 #######################
19 # STRING DECORATOR    #
20 #######################
21
22 def stringfilter(func):
23     """
24     Decorator for filters which should only receive unicode objects. The object
25     passed as the first positional argument will be converted to a unicode
26     object.
27     """
28     def _dec(*args, **kwargs):
29         if args:
30             args = list(args)
31             args[0] = force_unicode(args[0])
32             if isinstance(args[0], SafeData) and getattr(func, 'is_safe', False):
33                 return mark_safe(func(*args, **kwargs))
34         return func(*args, **kwargs)
35
36     # Include a reference to the real function (used to check original
37     # arguments by the template parser).
38     _dec._decorated_function = getattr(func, '_decorated_function', func)
39     for attr in ('is_safe', 'needs_autoescape'):
40         if hasattr(func, attr):
41             setattr(_dec, attr, getattr(func, attr))
42     return wraps(func)(_dec)
43
44 ###################
45 # STRINGS         #
46 ###################
47
48
49 def addslashes(value):
50     """
51     Adds slashes before quotes. Useful for escaping strings in CSV, for
52     example. Less useful for escaping JavaScript; use the ``escapejs``
53     filter instead.
54     """
55     return value.replace('\\', '\\\\').replace('"', '\\"').replace("'", "\\'")
56 addslashes.is_safe = True
57 addslashes = stringfilter(addslashes)
58
59 def capfirst(value):
60     """Capitalizes the first character of the value."""
61     return value and value[0].upper() + value[1:]
62 capfirst.is_safe=True
63 capfirst = stringfilter(capfirst)
64
65 _js_escapes = (
66     ('\\', '\\\\'),
67     ('"', '\\"'),
68     ("'", "\\'"),
69     ('\n', '\\n'),
70     ('\r', '\\r'),
71     ('\b', '\\b'),
72     ('\f', '\\f'),
73     ('\t', '\\t'),
74     ('\v', '\\v'),
75     ('</', '<\\/'),
76 )
77 def escapejs(value):
78     """Backslash-escapes characters for use in JavaScript strings."""
79     for bad, good in _js_escapes:
80         value = value.replace(bad, good)
81     return value
82 escapejs = stringfilter(escapejs)
83
84 def fix_ampersands(value):
85     """Replaces ampersands with ``&amp;`` entities."""
86     from django.utils.html import fix_ampersands
87     return fix_ampersands(value)
88 fix_ampersands.is_safe=True
89 fix_ampersands = stringfilter(fix_ampersands)
90
91 def floatformat(text, arg=-1):
92     """
93     Displays a float to a specified number of decimal places.
94
95     If called without an argument, it displays the floating point number with
96     one decimal place -- but only if there's a decimal place to be displayed:
97
98     * num1 = 34.23234
99     * num2 = 34.00000
100     * num3 = 34.26000
101     * {{ num1|floatformat }} displays "34.2"
102     * {{ num2|floatformat }} displays "34"
103     * {{ num3|floatformat }} displays "34.3"
104
105     If arg is positive, it will always display exactly arg number of decimal
106     places:
107
108     * {{ num1|floatformat:3 }} displays "34.232"
109     * {{ num2|floatformat:3 }} displays "34.000"
110     * {{ num3|floatformat:3 }} displays "34.260"
111
112     If arg is negative, it will display arg number of decimal places -- but
113     only if there are places to be displayed:
114
115     * {{ num1|floatformat:"-3" }} displays "34.232"
116     * {{ num2|floatformat:"-3" }} displays "34"
117     * {{ num3|floatformat:"-3" }} displays "34.260"
118     """
119     try:
120         f = float(text)
121     except (ValueError, TypeError):
122         return u''
123     try:
124         d = int(arg)
125     except ValueError:
126         return force_unicode(f)
127     try:
128         m = f - int(f)
129     except OverflowError:
130         return force_unicode(f)
131     if not m and d < 0:
132         return mark_safe(u'%d' % int(f))
133     else:
134         formatstr = u'%%.%df' % abs(d)
135         return mark_safe(formatstr % f)
136 floatformat.is_safe = True
137
138 def iriencode(value):
139     """Escapes an IRI value for use in a URL."""
140     return force_unicode(iri_to_uri(value))
141 iriencode.is_safe = True
142 iriencode = stringfilter(iriencode)
143
144 def linenumbers(value, autoescape=None):
145     """Displays text with line numbers."""
146     from django.utils.html import escape
147     lines = value.split(u'\n')
148     # Find the maximum width of the line count, for use with zero padding
149     # string format command
150     width = unicode(len(unicode(len(lines))))
151     if not autoescape or isinstance(value, SafeData):
152         for i, line in enumerate(lines):
153             lines[i] = (u"%0" + width  + u"d. %s") % (i + 1, line)
154     else:
155         for i, line in enumerate(lines):
156             lines[i] = (u"%0" + width  + u"d. %s") % (i + 1, escape(line))
157     return mark_safe(u'\n'.join(lines))
158 linenumbers.is_safe = True
159 linenumbers.needs_autoescape = True
160 linenumbers = stringfilter(linenumbers)
161
162 def lower(value):
163     """Converts a string into all lowercase."""
164     return value.lower()
165 lower.is_safe = True
166 lower = stringfilter(lower)
167
168 def make_list(value):
169     """
170     Returns the value turned into a list.
171
172     For an integer, it's a list of digits.
173     For a string, it's a list of characters.
174     """
175     return list(value)
176 make_list.is_safe = False
177 make_list = stringfilter(make_list)
178
179 def slugify(value):
180     """
181     Normalizes string, converts to lowercase, removes non-alpha characters,
182     and converts spaces to hyphens.
183     """
184     import unicodedata
185     value = unicodedata.normalize('NFKD', value).encode('ascii', 'ignore')
186     value = unicode(re.sub('[^\w\s-]', '', value).strip().lower())
187     return mark_safe(re.sub('[-\s]+', '-', value))
188 slugify.is_safe = True
189 slugify = stringfilter(slugify)
190
191 def stringformat(value, arg):
192     """
193     Formats the variable according to the arg, a string formatting specifier.
194
195     This specifier uses Python string formating syntax, with the exception that
196     the leading "%" is dropped.
197
198     See http://docs.python.org/lib/typesseq-strings.html for documentation
199     of Python string formatting
200     """
201     try:
202         return (u"%" + unicode(arg)) % value
203     except (ValueError, TypeError):
204         return u""
205 stringformat.is_safe = True
206
207 def title(value):
208     """Converts a string into titlecase."""
209     return re.sub("([a-z])'([A-Z])", lambda m: m.group(0).lower(), value.title())
210 title.is_safe = True
211 title = stringfilter(title)
212
213 def truncatewords(value, arg):
214     """
215     Truncates a string after a certain number of words.
216
217     Argument: Number of words to truncate after.
218     """
219     from django.utils.text import truncate_words
220     try:
221         length = int(arg)
222     except ValueError: # Invalid literal for int().
223         return value # Fail silently.
224     return truncate_words(value, length)
225 truncatewords.is_safe = True
226 truncatewords = stringfilter(truncatewords)
227
228 def truncatewords_html(value, arg):
229     """
230     Truncates HTML after a certain number of words.
231
232     Argument: Number of words to truncate after.
233     """
234     from django.utils.text import truncate_html_words
235     try:
236         length = int(arg)
237     except ValueError: # invalid literal for int()
238         return value # Fail silently.
239     return truncate_html_words(value, length)
240 truncatewords_html.is_safe = True
241 truncatewords_html = stringfilter(truncatewords_html)
242
243 def upper(value):
244     """Converts a string into all uppercase."""
245     return value.upper()
246 upper.is_safe = False
247 upper = stringfilter(upper)
248
249 def urlencode(value):
250     """Escapes a value for use in a URL."""
251     from django.utils.http import urlquote
252     return urlquote(value)
253 urlencode.is_safe = False
254 urlencode = stringfilter(urlencode)
255
256 def urlize(value, autoescape=None):
257     """Converts URLs in plain text into clickable links."""
258     from django.utils.html import urlize
259     return mark_safe(urlize(value, nofollow=True, autoescape=autoescape))
260 urlize.is_safe=True
261 urlize.needs_autoescape = True
262 urlize = stringfilter(urlize)
263
264 def urlizetrunc(value, limit, autoescape=None):
265     """
266     Converts URLs into clickable links, truncating URLs to the given character
267     limit, and adding 'rel=nofollow' attribute to discourage spamming.
268
269     Argument: Length to truncate URLs to.
270     """
271     from django.utils.html import urlize
272     return mark_safe(urlize(value, trim_url_limit=int(limit), nofollow=True,
273                             autoescape=autoescape))
274 urlizetrunc.is_safe = True
275 urlizetrunc.needs_autoescape = True
276 urlizetrunc = stringfilter(urlizetrunc)
277
278 def wordcount(value):
279     """Returns the number of words."""
280     return len(value.split())
281 wordcount.is_safe = False
282 wordcount = stringfilter(wordcount)
283
284 def wordwrap(value, arg):
285     """
286     Wraps words at specified line length.
287
288     Argument: number of characters to wrap the text at.
289     """
290     from django.utils.text import wrap
291     return wrap(value, int(arg))
292 wordwrap.is_safe = True
293 wordwrap = stringfilter(wordwrap)
294
295 def ljust(value, arg):
296     """
297     Left-aligns the value in a field of a given width.
298
299     Argument: field size.
300     """
301     return value.ljust(int(arg))
302 ljust.is_safe = True
303 ljust = stringfilter(ljust)
304
305 def rjust(value, arg):
306     """
307     Right-aligns the value in a field of a given width.
308
309     Argument: field size.
310     """
311     return value.rjust(int(arg))
312 rjust.is_safe = True
313 rjust = stringfilter(rjust)
314
315 def center(value, arg):
316     """Centers the value in a field of a given width."""
317     return value.center(int(arg))
318 center.is_safe = True
319 center = stringfilter(center)
320
321 def cut(value, arg):
322     """
323     Removes all values of arg from the given string.
324     """
325     safe = isinstance(value, SafeData)
326     value = value.replace(arg, u'')
327     if safe and arg != ';':
328         return mark_safe(value)
329     return value
330 cut = stringfilter(cut)
331
332 ###################
333 # HTML STRINGS    #
334 ###################
335
336 def escape(value):
337     """
338     Marks the value as a string that should not be auto-escaped.
339     """
340     from django.utils.safestring import mark_for_escaping
341     return mark_for_escaping(value)
342 escape.is_safe = True
343 escape = stringfilter(escape)
344
345 def force_escape(value):
346     """
347     Escapes a string's HTML. This returns a new string containing the escaped
348     characters (as opposed to "escape", which marks the content for later
349     possible escaping).
350     """
351     from django.utils.html import escape
352     return mark_safe(escape(value))
353 force_escape = stringfilter(force_escape)
354 force_escape.is_safe = True
355
356 def linebreaks(value, autoescape=None):
357     """
358     Replaces line breaks in plain text with appropriate HTML; a single
359     newline becomes an HTML line break (``<br />``) and a new line
360     followed by a blank line becomes a paragraph break (``</p>``).
361     """
362     from django.utils.html import linebreaks
363     autoescape = autoescape and not isinstance(value, SafeData)
364     return mark_safe(linebreaks(value, autoescape))
365 linebreaks.is_safe = True
366 linebreaks.needs_autoescape = True
367 linebreaks = stringfilter(linebreaks)
368
369 def linebreaksbr(value, autoescape=None):
370     """
371     Converts all newlines in a piece of plain text to HTML line breaks
372     (``<br />``).
373     """
374     if autoescape and not isinstance(value, SafeData):
375         from django.utils.html import escape
376         value = escape(value)
377     return mark_safe(value.replace('\n', '<br />'))
378 linebreaksbr.is_safe = True
379 linebreaksbr.needs_autoescape = True
380 linebreaksbr = stringfilter(linebreaksbr)
381
382 def safe(value):
383     """
384     Marks the value as a string that should not be auto-escaped.
385     """
386     from django.utils.safestring import mark_safe
387     return mark_safe(value)
388 safe.is_safe = True
389 safe = stringfilter(safe)
390
391 def removetags(value, tags):
392     """Removes a space separated list of [X]HTML tags from the output."""
393     tags = [re.escape(tag) for tag in tags.split()]
394     tags_re = u'(%s)' % u'|'.join(tags)
395     starttag_re = re.compile(ur'<%s(/?>|(\s+[^>]*>))' % tags_re, re.U)
396     endtag_re = re.compile(u'</%s>' % tags_re)
397     value = starttag_re.sub(u'', value)
398     value = endtag_re.sub(u'', value)
399     return value
400 removetags.is_safe = True
401 removetags = stringfilter(removetags)
402
403 def striptags(value):
404     """Strips all [X]HTML tags."""
405     from django.utils.html import strip_tags
406     return strip_tags(value)
407 striptags.is_safe = True
408 striptags = stringfilter(striptags)
409
410 ###################
411 # LISTS           #
412 ###################
413
414 def dictsort(value, arg):
415     """
416     Takes a list of dicts, returns that list sorted by the property given in
417     the argument.
418     """
419     var_resolve = Variable(arg).resolve
420     decorated = [(var_resolve(item), item) for item in value]
421     decorated.sort()
422     return [item[1] for item in decorated]
423 dictsort.is_safe = False
424
425 def dictsortreversed(value, arg):
426     """
427     Takes a list of dicts, returns that list sorted in reverse order by the
428     property given in the argument.
429     """
430     var_resolve = Variable(arg).resolve
431     decorated = [(var_resolve(item), item) for item in value]
432     decorated.sort()
433     decorated.reverse()
434     return [item[1] for item in decorated]
435 dictsortreversed.is_safe = False
436
437 def first(value):
438     """Returns the first item in a list."""
439     try:
440         return value[0]
441     except IndexError:
442         return u''
443 first.is_safe = False
444
445 def join(value, arg):
446     """Joins a list with a string, like Python's ``str.join(list)``."""
447     try:
448         data = arg.join(map(force_unicode, value))
449     except AttributeError: # fail silently but nicely
450         return value
451     safe_args = reduce(lambda lhs, rhs: lhs and isinstance(rhs, SafeData),
452             value, True)
453     if safe_args:
454         return mark_safe(data)
455     else:
456         return data
457 join.is_safe = True
458
459 def last(value):
460     "Returns the last item in a list"
461     try:
462         return value[-1]
463     except IndexError:
464         return u''
465 last.is_safe = True
466
467 def length(value):
468     """Returns the length of the value - useful for lists."""
469     return len(value)
470 length.is_safe = True
471
472 def length_is(value, arg):
473     """Returns a boolean of whether the value's length is the argument."""
474     return len(value) == int(arg)
475 length_is.is_safe = True
476
477 def random(value):
478     """Returns a random item from the list."""
479     return random_module.choice(value)
480 random.is_safe = True
481
482 def slice_(value, arg):
483     """
484     Returns a slice of the list.
485
486     Uses the same syntax as Python's list slicing; see
487     http://diveintopython.org/native_data_types/lists.html#odbchelper.list.slice
488     for an introduction.
489     """
490     try:
491         bits = []
492         for x in arg.split(u':'):
493             if len(x) == 0:
494                 bits.append(None)
495             else:
496                 bits.append(int(x))
497         return value[slice(*bits)]
498
499     except (ValueError, TypeError):
500         return value # Fail silently.
501 slice_.is_safe = True
502
503 def unordered_list(value, autoescape=None):
504     """
505     Recursively takes a self-nested list and returns an HTML unordered list --
506     WITHOUT opening and closing <ul> tags.
507
508     The list is assumed to be in the proper format. For example, if ``var``
509     contains: ``['States', ['Kansas', ['Lawrence', 'Topeka'], 'Illinois']]``,
510     then ``{{ var|unordered_list }}`` would return::
511
512         <li>States
513         <ul>
514                 <li>Kansas
515                 <ul>
516                         <li>Lawrence</li>
517                         <li>Topeka</li>
518                 </ul>
519                 </li>
520                 <li>Illinois</li>
521         </ul>
522         </li>
523     """
524     if autoescape:
525         from django.utils.html import conditional_escape
526         escaper = conditional_escape
527     else:
528         escaper = lambda x: x
529     def convert_old_style_list(list_):
530         """
531         Converts old style lists to the new easier to understand format.
532
533         The old list format looked like:
534             ['Item 1', [['Item 1.1', []], ['Item 1.2', []]]
535
536         And it is converted to:
537             ['Item 1', ['Item 1.1', 'Item 1.2]]
538         """
539         if not isinstance(list_, (tuple, list)) or len(list_) != 2:
540             return list_, False
541         first_item, second_item = list_
542         if second_item == []:
543             return [first_item], True
544         old_style_list = True
545         new_second_item = []
546         for sublist in second_item:
547             item, old_style_list = convert_old_style_list(sublist)
548             if not old_style_list:
549                 break
550             new_second_item.extend(item)
551         if old_style_list:
552             second_item = new_second_item
553         return [first_item, second_item], old_style_list
554     def _helper(list_, tabs=1):
555         indent = u'\t' * tabs
556         output = []
557
558         list_length = len(list_)
559         i = 0
560         while i < list_length:
561             title = list_[i]
562             sublist = ''
563             sublist_item = None
564             if isinstance(title, (list, tuple)):
565                 sublist_item = title
566                 title = ''
567             elif i < list_length - 1:
568                 next_item = list_[i+1]
569                 if next_item and isinstance(next_item, (list, tuple)):
570                     # The next item is a sub-list.
571                     sublist_item = next_item
572                     # We've processed the next item now too.
573                     i += 1
574             if sublist_item:
575                 sublist = _helper(sublist_item, tabs+1)
576                 sublist = '\n%s<ul>\n%s\n%s</ul>\n%s' % (indent, sublist,
577                                                          indent, indent)
578             output.append('%s<li>%s%s</li>' % (indent,
579                     escaper(force_unicode(title)), sublist))
580             i += 1
581         return '\n'.join(output)
582     value, converted = convert_old_style_list(value)
583     return mark_safe(_helper(value))
584 unordered_list.is_safe = True
585 unordered_list.needs_autoescape = True
586
587 ###################
588 # INTEGERS        #
589 ###################
590
591 def add(value, arg):
592     """Adds the arg to the value."""
593     return int(value) + int(arg)
594 add.is_safe = False
595
596 def get_digit(value, arg):
597     """
598     Given a whole number, returns the requested digit of it, where 1 is the
599     right-most digit, 2 is the second-right-most digit, etc. Returns the
600     original value for invalid input (if input or argument is not an integer,