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