1"""Default variable filters."""
2
3import random as random_module
4import re
5import types
6from decimal import ROUND_HALF_UP, Context, Decimal, InvalidOperation, getcontext
7from functools import wraps
8from inspect import unwrap
9from operator import itemgetter
10from pprint import pformat
11from urllib.parse import quote
12
13from django.utils import formats
14from django.utils.dateformat import format, time_format
15from django.utils.encoding import iri_to_uri
16from django.utils.html import avoid_wrapping, conditional_escape, escape, escapejs
17from django.utils.html import json_script as _json_script
18from django.utils.html import linebreaks, strip_tags
19from django.utils.html import urlize as _urlize
20from django.utils.safestring import SafeData, mark_safe
21from django.utils.text import Truncator, normalize_newlines, phone2numeric
22from django.utils.text import slugify as _slugify
23from django.utils.text import wrap
24from django.utils.timesince import timesince, timeuntil
25from django.utils.translation import gettext, ngettext
26
27from .base import VARIABLE_ATTRIBUTE_SEPARATOR
28from .library import Library
29
30register = Library()
31
32
33#######################
34# STRING DECORATOR #
35#######################
36
37
38def stringfilter(func):
39 """
40 Decorator for filters which should only receive strings. The object
41 passed as the first positional argument will be converted to a string.
42 """
43
44 @wraps(func)
45 def _dec(first, *args, **kwargs):
46 first = str(first)
47 result = func(first, *args, **kwargs)
48 if isinstance(first, SafeData) and getattr(unwrap(func), "is_safe", False):
49 result = mark_safe(result)
50 return result
51
52 return _dec
53
54
55###################
56# STRINGS #
57###################
58
59
60@register.filter(is_safe=True)
61@stringfilter
62def addslashes(value):
63 """
64 Add slashes before quotes. Useful for escaping strings in CSV, for
65 example. Less useful for escaping JavaScript; use the ``escapejs``
66 filter instead.
67 """
68 return value.replace("\\", "\\\\").replace('"', '\\"').replace("'", "\\'")
69
70
71@register.filter(is_safe=True)
72@stringfilter
73def capfirst(value):
74 """Capitalize the first character of the value."""
75 return value and value[0].upper() + value[1:]
76
77
78@register.filter("escapejs")
79@stringfilter
80def escapejs_filter(value):
81 """Hex encode characters for use in JavaScript strings."""
82 return escapejs(value)
83
84
85@register.filter(is_safe=True)
86def json_script(value, element_id=None):
87 """
88 Output value JSON-encoded, wrapped in a <script type="application/json">
89 tag (with an optional id).
90 """
91 return _json_script(value, element_id)
92
93
94@register.filter(is_safe=True)
95def floatformat(text, arg=-1):
96 """
97 Display a float to a specified number of decimal places.
98
99 If called without an argument, display the floating point number with one
100 decimal place -- but only if there's a decimal place to be displayed:
101
102 * num1 = 34.23234
103 * num2 = 34.00000
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, always display exactly arg number of decimal places:
110
111 * {{ num1|floatformat:3 }} displays "34.232"
112 * {{ num2|floatformat:3 }} displays "34.000"
113 * {{ num3|floatformat:3 }} displays "34.260"
114
115 If arg is negative, display arg number of decimal places -- but only if
116 there are places to be displayed:
117
118 * {{ num1|floatformat:"-3" }} displays "34.232"
119 * {{ num2|floatformat:"-3" }} displays "34"
120 * {{ num3|floatformat:"-3" }} displays "34.260"
121
122 If arg has the 'g' suffix, force the result to be grouped by the
123 THOUSAND_SEPARATOR for the active locale. When the active locale is
124 en (English):
125
126 * {{ 6666.6666|floatformat:"2g" }} displays "6,666.67"
127 * {{ 10000|floatformat:"g" }} displays "10,000"
128
129 If arg has the 'u' suffix, force the result to be unlocalized. When the
130 active locale is pl (Polish):
131
132 * {{ 66666.6666|floatformat:"2" }} displays "66666,67"
133 * {{ 66666.6666|floatformat:"2u" }} displays "66666.67"
134
135 If the input float is infinity or NaN, display the string representation
136 of that value.
137 """
138 force_grouping = False
139 use_l10n = True
140 if isinstance(arg, str):
141 last_char = arg[-1]
142 if arg[-2:] in {"gu", "ug"}:
143 force_grouping = True
144 use_l10n = False
145 arg = arg[:-2] or -1
146 elif last_char == "g":
147 force_grouping = True
148 arg = arg[:-1] or -1
149 elif last_char == "u":
150 use_l10n = False
151 arg = arg[:-1] or -1
152 try:
153 input_val = str(text)
154 d = Decimal(input_val)
155 except InvalidOperation:
156 try:
157 d = Decimal(str(float(text)))
158 except (ValueError, InvalidOperation, TypeError):
159 return ""
160 try:
161 p = int(arg)
162 except ValueError:
163 return input_val
164
165 _, digits, exponent = d.as_tuple()
166 try:
167 number_of_digits_and_exponent_sum = len(digits) + abs(exponent)
168 except TypeError:
169 # Exponent values can be "F", "n", "N".
170 number_of_digits_and_exponent_sum = 0
171
172 # Values with more than 200 digits, or with a large exponent, are returned "as is"
173 # to avoid high memory consumption and potential denial-of-service attacks.
174 # The cut-off of 200 is consistent with django.utils.numberformat.floatformat().
175 if number_of_digits_and_exponent_sum > 200:
176 return input_val
177
178 try:
179 m = int(d) - d
180 except (ValueError, OverflowError, InvalidOperation):
181 return input_val
182
183 if not m and p <= 0:
184 return mark_safe(
185 formats.number_format(
186 "%d" % (int(d)),
187 0,
188 use_l10n=use_l10n,
189 force_grouping=force_grouping,
190 )
191 )
192
193 exp = Decimal(1).scaleb(-abs(p))
194 # Set the precision high enough to avoid an exception (#15789).
195 tupl = d.as_tuple()
196 units = len(tupl[1])
197 units += -tupl[2] if m else tupl[2]
198 prec = abs(p) + units + 1
199 prec = max(getcontext().prec, prec)
200
201 # Avoid conversion to scientific notation by accessing `sign`, `digits`,
202 # and `exponent` from Decimal.as_tuple() directly.
203 rounded_d = d.quantize(exp, ROUND_HALF_UP, Context(prec=prec))
204 sign, digits, exponent = rounded_d.as_tuple()
205 digits = [str(digit) for digit in reversed(digits)]
206 while len(digits) <= abs(exponent):
207 digits.append("0")
208 digits.insert(-exponent, ".")
209 if sign and rounded_d:
210 digits.append("-")
211 number = "".join(reversed(digits))
212 return mark_safe(
213 formats.number_format(
214 number,
215 abs(p),
216 use_l10n=use_l10n,
217 force_grouping=force_grouping,
218 )
219 )
220
221
222@register.filter(is_safe=True)
223@stringfilter
224def iriencode(value):
225 """Escape an IRI value for use in a URL."""
226 return iri_to_uri(value)
227
228
229@register.filter(is_safe=True, needs_autoescape=True)
230@stringfilter
231def linenumbers(value, autoescape=True):
232 """Display text with line numbers."""
233 lines = value.split("\n")
234 # Find the maximum width of the line count, for use with zero padding
235 # string format command
236 width = str(len(str(len(lines))))
237 if not autoescape or isinstance(value, SafeData):
238 for i, line in enumerate(lines):
239 lines[i] = ("%0" + width + "d. %s") % (i + 1, line)
240 else:
241 for i, line in enumerate(lines):
242 lines[i] = ("%0" + width + "d. %s") % (i + 1, escape(line))
243 return mark_safe("\n".join(lines))
244
245
246@register.filter(is_safe=True)
247@stringfilter
248def lower(value):
249 """Convert a string into all lowercase."""
250 return value.lower()
251
252
253@register.filter(is_safe=False)
254@stringfilter
255def make_list(value):
256 """
257 Return the value turned into a list.
258
259 For an integer, it's a list of digits.
260 For a string, it's a list of characters.
261 """
262 return list(value)
263
264
265@register.filter(is_safe=True)
266@stringfilter
267def slugify(value):
268 """
269 Convert to ASCII. Convert spaces to hyphens. Remove characters that aren't
270 alphanumerics, underscores, or hyphens. Convert to lowercase. Also strip
271 leading and trailing whitespace.
272 """
273 return _slugify(value)
274
275
276@register.filter(is_safe=True)
277def stringformat(value, arg):
278 """
279 Format the variable according to the arg, a string formatting specifier.
280
281 This specifier uses Python string formatting syntax, with the exception
282 that the leading "%" is dropped.
283
284 See https://docs.python.org/library/stdtypes.html#printf-style-string-formatting
285 for documentation of Python string formatting.
286 """
287 if isinstance(value, tuple):
288 value = str(value)
289 try:
290 return ("%" + str(arg)) % value
291 except (ValueError, TypeError):
292 return ""
293
294
295@register.filter(is_safe=True)
296@stringfilter
297def title(value):
298 """Convert a string into titlecase."""
299 t = re.sub("([a-z])'([A-Z])", lambda m: m[0].lower(), value.title())
300 return re.sub(r"\d([A-Z])", lambda m: m[0].lower(), t)
301
302
303@register.filter(is_safe=True)
304@stringfilter
305def truncatechars(value, arg):
306 """Truncate a string after `arg` number of characters."""
307 try:
308 length = int(arg)
309 except ValueError: # Invalid literal for int().
310 return value # Fail silently.
311 return Truncator(value).chars(length)
312
313
314@register.filter(is_safe=True)
315@stringfilter
316def truncatechars_html(value, arg):
317 """
318 Truncate HTML after `arg` number of chars.
319 Preserve newlines in the HTML.
320 """
321 try:
322 length = int(arg)
323 except ValueError: # invalid literal for int()
324 return value # Fail silently.
325 return Truncator(value).chars(length, html=True)
326
327
328@register.filter(is_safe=True)
329@stringfilter
330def truncatewords(value, arg):
331 """
332 Truncate a string after `arg` number of words.
333 Remove newlines within the string.
334 """
335 try:
336 length = int(arg)
337 except ValueError: # Invalid literal for int().
338 return value # Fail silently.
339 return Truncator(value).words(length, truncate=" …")
340
341
342@register.filter(is_safe=True)
343@stringfilter
344def truncatewords_html(value, arg):
345 """
346 Truncate HTML after `arg` number of words.
347 Preserve newlines in the HTML.
348 """
349 try:
350 length = int(arg)
351 except ValueError: # invalid literal for int()
352 return value # Fail silently.
353 return Truncator(value).words(length, html=True, truncate=" …")
354
355
356@register.filter(is_safe=False)
357@stringfilter
358def upper(value):
359 """Convert a string into all uppercase."""
360 return value.upper()
361
362
363@register.filter(is_safe=False)
364@stringfilter
365def urlencode(value, safe=None):
366 """
367 Escape a value for use in a URL.
368
369 The ``safe`` parameter determines the characters which should not be
370 escaped by Python's quote() function. If not provided, use the default safe
371 characters (but an empty string can be provided when *all* characters
372 should be escaped).
373 """
374 kwargs = {}
375 if safe is not None:
376 kwargs["safe"] = safe
377 return quote(value, **kwargs)
378
379
380@register.filter(is_safe=True, needs_autoescape=True)
381@stringfilter
382def urlize(value, autoescape=True):
383 """Convert URLs in plain text into clickable links."""
384 return mark_safe(_urlize(value, nofollow=True, autoescape=autoescape))
385
386
387@register.filter(is_safe=True, needs_autoescape=True)
388@stringfilter
389def urlizetrunc(value, limit, autoescape=True):
390 """
391 Convert URLs into clickable links, truncating URLs to the given character
392 limit, and adding 'rel=nofollow' attribute to discourage spamming.
393
394 Argument: Length to truncate URLs to.
395 """
396 return mark_safe(
397 _urlize(value, trim_url_limit=int(limit), nofollow=True, autoescape=autoescape)
398 )
399
400
401@register.filter(is_safe=False)
402@stringfilter
403def wordcount(value):
404 """Return the number of words."""
405 return len(value.split())
406
407
408@register.filter(is_safe=True)
409@stringfilter
410def wordwrap(value, arg):
411 """Wrap words at `arg` line length."""
412 return wrap(value, int(arg))
413
414
415@register.filter(is_safe=True)
416@stringfilter
417def ljust(value, arg):
418 """Left-align the value in a field of a given width."""
419 return value.ljust(int(arg))
420
421
422@register.filter(is_safe=True)
423@stringfilter
424def rjust(value, arg):
425 """Right-align the value in a field of a given width."""
426 return value.rjust(int(arg))
427
428
429@register.filter(is_safe=True)
430@stringfilter
431def center(value, arg):
432 """Center the value in a field of a given width."""
433 return value.center(int(arg))
434
435
436@register.filter
437@stringfilter
438def cut(value, arg):
439 """Remove all values of arg from the given string."""
440 safe = isinstance(value, SafeData)
441 value = value.replace(arg, "")
442 if safe and arg != ";":
443 return mark_safe(value)
444 return value
445
446
447###################
448# HTML STRINGS #
449###################
450
451
452@register.filter("escape", is_safe=True)
453@stringfilter
454def escape_filter(value):
455 """Mark the value as a string that should be auto-escaped."""
456 return conditional_escape(value)
457
458
459@register.filter(is_safe=True)
460def escapeseq(value):
461 """
462 An "escape" filter for sequences. Mark each element in the sequence,
463 individually, as a string that should be auto-escaped. Return a list with
464 the results.
465 """
466 return [conditional_escape(obj) for obj in value]
467
468
469@register.filter(is_safe=True)
470@stringfilter
471def force_escape(value):
472 """
473 Escape a string's HTML. Return a new string containing the escaped
474 characters (as opposed to "escape", which marks the content for later
475 possible escaping).
476 """
477 return escape(value)
478
479
480@register.filter("linebreaks", is_safe=True, needs_autoescape=True)
481@stringfilter
482def linebreaks_filter(value, autoescape=True):
483 """
484 Replace line breaks in plain text with appropriate HTML; a single
485 newline becomes an HTML line break (``<br>``) and a new line
486 followed by a blank line becomes a paragraph break (``</p>``).
487 """
488 autoescape = autoescape and not isinstance(value, SafeData)
489 return mark_safe(linebreaks(value, autoescape))
490
491
492@register.filter(is_safe=True, needs_autoescape=True)
493@stringfilter
494def linebreaksbr(value, autoescape=True):
495 """
496 Convert all newlines in a piece of plain text to HTML line breaks
497 (``<br>``).
498 """
499 autoescape = autoescape and not isinstance(value, SafeData)
500 value = normalize_newlines(value)
501 if autoescape:
502 value = escape(value)
503 return mark_safe(value.replace("\n", "<br>"))
504
505
506@register.filter(is_safe=True)
507@stringfilter
508def safe(value):
509 """Mark the value as a string that should not be auto-escaped."""
510 return mark_safe(value)
511
512
513@register.filter(is_safe=True)
514def safeseq(value):
515 """
516 A "safe" filter for sequences. Mark each element in the sequence,
517 individually, as safe, after converting them to strings. Return a list
518 with the results.
519 """
520 return [mark_safe(obj) for obj in value]
521
522
523@register.filter(is_safe=True)
524@stringfilter
525def striptags(value):
526 """Strip all [X]HTML tags."""
527 return strip_tags(value)
528
529
530###################
531# LISTS #
532###################
533
534
535def _property_resolver(arg):
536 """
537 When arg is convertible to float, behave like operator.itemgetter(arg)
538 Otherwise, chain __getitem__() and getattr().
539
540 >>> _property_resolver(1)('abc')
541 'b'
542 >>> _property_resolver('1')('abc')
543 Traceback (most recent call last):
544 ...
545 TypeError: string indices must be integers
546 >>> class Foo:
547 ... a = 42
548 ... b = 3.14
549 ... c = 'Hey!'
550 >>> _property_resolver('b')(Foo())
551 3.14
552 """
553 try:
554 float(arg)
555 except ValueError:
556 if VARIABLE_ATTRIBUTE_SEPARATOR + "_" in arg or arg[0] == "_":
557 raise AttributeError("Access to private variables is forbidden.")
558 parts = arg.split(VARIABLE_ATTRIBUTE_SEPARATOR)
559
560 def resolve(value):
561 for part in parts:
562 try:
563 value = value[part]
564 except (AttributeError, IndexError, KeyError, TypeError, ValueError):
565 value = getattr(value, part)
566 return value
567
568 return resolve
569 else:
570 return itemgetter(arg)
571
572
573@register.filter(is_safe=False)
574def dictsort(value, arg):
575 """
576 Given a list of dicts, return that list sorted by the property given in
577 the argument.
578 """
579 try:
580 return sorted(value, key=_property_resolver(arg))
581 except (AttributeError, TypeError):
582 return ""
583
584
585@register.filter(is_safe=False)
586def dictsortreversed(value, arg):
587 """
588 Given a list of dicts, return that list sorted in reverse order by the
589 property given in the argument.
590 """
591 try:
592 return sorted(value, key=_property_resolver(arg), reverse=True)
593 except (AttributeError, TypeError):
594 return ""
595
596
597@register.filter(is_safe=False)
598def first(value):
599 """Return the first item in a list."""
600 try:
601 return value[0]
602 except IndexError:
603 return ""
604
605
606@register.filter(is_safe=True, needs_autoescape=True)
607def join(value, arg, autoescape=True):
608 """Join a list with a string, like Python's ``str.join(list)``."""
609 try:
610 if autoescape:
611 data = conditional_escape(arg).join([conditional_escape(v) for v in value])
612 else:
613 data = arg.join(value)
614 except TypeError: # Fail silently if arg isn't iterable.
615 return value
616 return mark_safe(data)
617
618
619@register.filter(is_safe=True)
620def last(value):
621 """Return the last item in a list."""
622 try:
623 return value[-1]
624 except IndexError:
625 return ""
626
627
628@register.filter(is_safe=False)
629def length(value):
630 """Return the length of the value - useful for lists."""
631 try:
632 return len(value)
633 except (ValueError, TypeError):
634 return 0
635
636
637@register.filter(is_safe=True)
638def random(value):
639 """Return a random item from the list."""
640 try:
641 return random_module.choice(value)
642 except IndexError:
643 return ""
644
645
646@register.filter("slice", is_safe=True)
647def slice_filter(value, arg):
648 """
649 Return a slice of the list using the same syntax as Python's list slicing.
650 """
651 try:
652 bits = []
653 for x in str(arg).split(":"):
654 if not x:
655 bits.append(None)
656 else:
657 bits.append(int(x))
658 return value[slice(*bits)]
659
660 except (ValueError, TypeError, KeyError):
661 return value # Fail silently.
662
663
664@register.filter(is_safe=True, needs_autoescape=True)
665def unordered_list(value, autoescape=True):
666 """
667 Recursively take a self-nested list and return an HTML unordered list --
668 WITHOUT opening and closing <ul> tags.
669
670 Assume the list is in the proper format. For example, if ``var`` contains:
671 ``['States', ['Kansas', ['Lawrence', 'Topeka'], 'Illinois']]``, then
672 ``{{ var|unordered_list }}`` returns::
673
674 <li>States
675 <ul>
676 <li>Kansas
677 <ul>
678 <li>Lawrence</li>
679 <li>Topeka</li>
680 </ul>
681 </li>
682 <li>Illinois</li>
683 </ul>
684 </li>
685 """
686 if autoescape:
687 escaper = conditional_escape
688 else:
689
690 def escaper(x):
691 return x
692
693 def walk_items(item_list):
694 item_iterator = iter(item_list)
695 try:
696 item = next(item_iterator)
697 while True:
698 try:
699 next_item = next(item_iterator)
700 except StopIteration:
701 yield item, None
702 break
703 if isinstance(next_item, (list, tuple, types.GeneratorType)):
704 try:
705 iter(next_item)
706 except TypeError:
707 pass
708 else:
709 yield item, next_item
710 item = next(item_iterator)
711 continue
712 yield item, None
713 item = next_item
714 except StopIteration:
715 pass
716
717 def list_formatter(item_list, tabs=1):
718 indent = "\t" * tabs
719 output = []
720 for item, children in walk_items(item_list):
721 sublist = ""
722 if children:
723 sublist = "\n%s<ul>\n%s\n%s</ul>\n%s" % (
724 indent,
725 list_formatter(children, tabs + 1),
726 indent,
727 indent,
728 )
729 output.append("%s<li>%s%s</li>" % (indent, escaper(item), sublist))
730 return "\n".join(output)
731
732 return mark_safe(list_formatter(value))
733
734
735###################
736# INTEGERS #
737###################
738
739
740@register.filter(is_safe=False)
741def add(value, arg):
742 """Add the arg to the value."""
743 try:
744 return int(value) + int(arg)
745 except (ValueError, TypeError):
746 try:
747 return value + arg
748 except Exception:
749 return ""
750
751
752@register.filter(is_safe=False)
753def get_digit(value, arg):
754 """
755 Given a whole number, return the requested digit of it, where 1 is the
756 right-most digit, 2 is the second-right-most digit, etc. Return the
757 original value for invalid input (if input or argument is not an integer,
758 or if argument is less than 1). Otherwise, output is always an integer.
759 """
760 try:
761 arg = int(arg)
762 value = int(value)
763 except ValueError:
764 return value # Fail silently for an invalid argument
765 if arg < 1:
766 return value
767 try:
768 return int(str(value)[-arg])
769 except IndexError:
770 return 0
771
772
773###################
774# DATES #
775###################
776
777
778@register.filter(expects_localtime=True, is_safe=False)
779def date(value, arg=None):
780 """Format a date according to the given format."""
781 if value in (None, ""):
782 return ""
783 try:
784 return formats.date_format(value, arg)
785 except AttributeError:
786 try:
787 return format(value, arg)
788 except AttributeError:
789 return ""
790
791
792@register.filter(expects_localtime=True, is_safe=False)
793def time(value, arg=None):
794 """Format a time according to the given format."""
795 if value in (None, ""):
796 return ""
797 try:
798 return formats.time_format(value, arg)
799 except (AttributeError, TypeError):
800 try:
801 return time_format(value, arg)
802 except (AttributeError, TypeError):
803 return ""
804
805
806@register.filter("timesince", is_safe=False)
807def timesince_filter(value, arg=None):
808 """Format a date as the time since that date (i.e. "4 days, 6 hours")."""
809 if not value:
810 return ""
811 try:
812 if arg:
813 return timesince(value, arg)
814 return timesince(value)
815 except (ValueError, TypeError):
816 return ""
817
818
819@register.filter("timeuntil", is_safe=False)
820def timeuntil_filter(value, arg=None):
821 """Format a date as the time until that date (i.e. "4 days, 6 hours")."""
822 if not value:
823 return ""
824 try:
825 return timeuntil(value, arg)
826 except (ValueError, TypeError):
827 return ""
828
829
830###################
831# LOGIC #
832###################
833
834
835@register.filter(is_safe=False)
836def default(value, arg):
837 """If value is unavailable, use given default."""
838 return value or arg
839
840
841@register.filter(is_safe=False)
842def default_if_none(value, arg):
843 """If value is None, use given default."""
844 if value is None:
845 return arg
846 return value
847
848
849@register.filter(is_safe=False)
850def divisibleby(value, arg):
851 """Return True if the value is divisible by the argument."""
852 return int(value) % int(arg) == 0
853
854
855@register.filter(is_safe=False)
856def yesno(value, arg=None):
857 """
858 Given a string mapping values for true, false, and (optionally) None,
859 return one of those strings according to the value:
860
861 ========== ====================== ==================================
862 Value Argument Outputs
863 ========== ====================== ==================================
864 ``True`` ``"yeah,no,maybe"`` ``yeah``
865 ``False`` ``"yeah,no,maybe"`` ``no``
866 ``None`` ``"yeah,no,maybe"`` ``maybe``
867 ``None`` ``"yeah,no"`` ``"no"`` (converts None to False
868 if no mapping for None is given.
869 ========== ====================== ==================================
870 """
871 if arg is None:
872 # Translators: Please do not add spaces around commas.
873 arg = gettext("yes,no,maybe")
874 bits = arg.split(",")
875 if len(bits) < 2:
876 return value # Invalid arg.
877 try:
878 yes, no, maybe = bits
879 except ValueError:
880 # Unpack list of wrong size (no "maybe" value provided).
881 yes, no, maybe = bits[0], bits[1], bits[1]
882 if value is None:
883 return maybe
884 if value:
885 return yes
886 return no
887
888
889###################
890# MISC #
891###################
892
893
894@register.filter(is_safe=True)
895def filesizeformat(bytes_):
896 """
897 Format the value like a 'human-readable' file size (i.e. 13 KB, 4.1 MB,
898 102 bytes, etc.).
899 """
900 try:
901 bytes_ = int(bytes_)
902 except (TypeError, ValueError, UnicodeDecodeError):
903 value = ngettext("%(size)d byte", "%(size)d bytes", 0) % {"size": 0}
904 return avoid_wrapping(value)
905
906 def filesize_number_format(value):
907 return formats.number_format(round(value, 1), 1)
908
909 KB = 1 << 10
910 MB = 1 << 20
911 GB = 1 << 30
912 TB = 1 << 40
913 PB = 1 << 50
914
915 negative = bytes_ < 0
916 if negative:
917 bytes_ = -bytes_ # Allow formatting of negative numbers.
918
919 if bytes_ < KB:
920 value = ngettext("%(size)d byte", "%(size)d bytes", bytes_) % {"size": bytes_}
921 elif bytes_ < MB:
922 value = gettext("%s KB") % filesize_number_format(bytes_ / KB)
923 elif bytes_ < GB:
924 value = gettext("%s MB") % filesize_number_format(bytes_ / MB)
925 elif bytes_ < TB:
926 value = gettext("%s GB") % filesize_number_format(bytes_ / GB)
927 elif bytes_ < PB:
928 value = gettext("%s TB") % filesize_number_format(bytes_ / TB)
929 else:
930 value = gettext("%s PB") % filesize_number_format(bytes_ / PB)
931
932 if negative:
933 value = "-%s" % value
934 return avoid_wrapping(value)
935
936
937@register.filter(is_safe=False)
938def pluralize(value, arg="s"):
939 """
940 Return a plural suffix if the value is not 1, '1', or an object of
941 length 1. By default, use 's' as the suffix:
942
943 * If value is 0, vote{{ value|pluralize }} display "votes".
944 * If value is 1, vote{{ value|pluralize }} display "vote".
945 * If value is 2, vote{{ value|pluralize }} display "votes".
946
947 If an argument is provided, use that string instead:
948
949 * If value is 0, class{{ value|pluralize:"es" }} display "classes".
950 * If value is 1, class{{ value|pluralize:"es" }} display "class".
951 * If value is 2, class{{ value|pluralize:"es" }} display "classes".
952
953 If the provided argument contains a comma, use the text before the comma
954 for the singular case and the text after the comma for the plural case:
955
956 * If value is 0, cand{{ value|pluralize:"y,ies" }} display "candies".
957 * If value is 1, cand{{ value|pluralize:"y,ies" }} display "candy".
958 * If value is 2, cand{{ value|pluralize:"y,ies" }} display "candies".
959 """
960 if "," not in arg:
961 arg = "," + arg
962 bits = arg.split(",")
963 if len(bits) > 2:
964 return ""
965 singular_suffix, plural_suffix = bits[:2]
966
967 try:
968 return singular_suffix if float(value) == 1 else plural_suffix
969 except ValueError: # Invalid string that's not a number.
970 pass
971 except TypeError: # Value isn't a string or a number; maybe it's a list?
972 try:
973 return singular_suffix if len(value) == 1 else plural_suffix
974 except TypeError: # len() of unsized object.
975 pass
976 return ""
977
978
979@register.filter("phone2numeric", is_safe=True)
980def phone2numeric_filter(value):
981 """Take a phone number and converts it in to its numerical equivalent."""
982 return phone2numeric(value)
983
984
985@register.filter(is_safe=True)
986def pprint(value):
987 """A wrapper around pprint.pprint -- for debugging, really."""
988 try:
989 return pformat(value)
990 except Exception as e:
991 return "Error in formatting: %s: %s" % (e.__class__.__name__, e)