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