| OLD | NEW |
| (Empty) |
| 1 # -*- coding: utf-8 -*- | |
| 2 """ | |
| 3 jinja2.filters | |
| 4 ~~~~~~~~~~~~~~ | |
| 5 | |
| 6 Bundled jinja filters. | |
| 7 | |
| 8 :copyright: (c) 2010 by the Jinja Team. | |
| 9 :license: BSD, see LICENSE for more details. | |
| 10 """ | |
| 11 import re | |
| 12 import math | |
| 13 | |
| 14 from random import choice | |
| 15 from operator import itemgetter | |
| 16 from itertools import groupby | |
| 17 from jinja2.utils import Markup, escape, pformat, urlize, soft_unicode, \ | |
| 18 unicode_urlencode | |
| 19 from jinja2.runtime import Undefined | |
| 20 from jinja2.exceptions import FilterArgumentError | |
| 21 from jinja2._compat import next, imap, string_types, text_type, iteritems | |
| 22 | |
| 23 | |
| 24 _word_re = re.compile(r'\w+(?u)') | |
| 25 | |
| 26 | |
| 27 def contextfilter(f): | |
| 28 """Decorator for marking context dependent filters. The current | |
| 29 :class:`Context` will be passed as first argument. | |
| 30 """ | |
| 31 f.contextfilter = True | |
| 32 return f | |
| 33 | |
| 34 | |
| 35 def evalcontextfilter(f): | |
| 36 """Decorator for marking eval-context dependent filters. An eval | |
| 37 context object is passed as first argument. For more information | |
| 38 about the eval context, see :ref:`eval-context`. | |
| 39 | |
| 40 .. versionadded:: 2.4 | |
| 41 """ | |
| 42 f.evalcontextfilter = True | |
| 43 return f | |
| 44 | |
| 45 | |
| 46 def environmentfilter(f): | |
| 47 """Decorator for marking evironment dependent filters. The current | |
| 48 :class:`Environment` is passed to the filter as first argument. | |
| 49 """ | |
| 50 f.environmentfilter = True | |
| 51 return f | |
| 52 | |
| 53 | |
| 54 def make_attrgetter(environment, attribute): | |
| 55 """Returns a callable that looks up the given attribute from a | |
| 56 passed object with the rules of the environment. Dots are allowed | |
| 57 to access attributes of attributes. Integer parts in paths are | |
| 58 looked up as integers. | |
| 59 """ | |
| 60 if not isinstance(attribute, string_types) \ | |
| 61 or ('.' not in attribute and not attribute.isdigit()): | |
| 62 return lambda x: environment.getitem(x, attribute) | |
| 63 attribute = attribute.split('.') | |
| 64 def attrgetter(item): | |
| 65 for part in attribute: | |
| 66 if part.isdigit(): | |
| 67 part = int(part) | |
| 68 item = environment.getitem(item, part) | |
| 69 return item | |
| 70 return attrgetter | |
| 71 | |
| 72 | |
| 73 def do_forceescape(value): | |
| 74 """Enforce HTML escaping. This will probably double escape variables.""" | |
| 75 if hasattr(value, '__html__'): | |
| 76 value = value.__html__() | |
| 77 return escape(text_type(value)) | |
| 78 | |
| 79 | |
| 80 def do_urlencode(value): | |
| 81 """Escape strings for use in URLs (uses UTF-8 encoding). It accepts both | |
| 82 dictionaries and regular strings as well as pairwise iterables. | |
| 83 | |
| 84 .. versionadded:: 2.7 | |
| 85 """ | |
| 86 itemiter = None | |
| 87 if isinstance(value, dict): | |
| 88 itemiter = iteritems(value) | |
| 89 elif not isinstance(value, string_types): | |
| 90 try: | |
| 91 itemiter = iter(value) | |
| 92 except TypeError: | |
| 93 pass | |
| 94 if itemiter is None: | |
| 95 return unicode_urlencode(value) | |
| 96 return u'&'.join(unicode_urlencode(k) + '=' + | |
| 97 unicode_urlencode(v) for k, v in itemiter) | |
| 98 | |
| 99 | |
| 100 @evalcontextfilter | |
| 101 def do_replace(eval_ctx, s, old, new, count=None): | |
| 102 """Return a copy of the value with all occurrences of a substring | |
| 103 replaced with a new one. The first argument is the substring | |
| 104 that should be replaced, the second is the replacement string. | |
| 105 If the optional third argument ``count`` is given, only the first | |
| 106 ``count`` occurrences are replaced: | |
| 107 | |
| 108 .. sourcecode:: jinja | |
| 109 | |
| 110 {{ "Hello World"|replace("Hello", "Goodbye") }} | |
| 111 -> Goodbye World | |
| 112 | |
| 113 {{ "aaaaargh"|replace("a", "d'oh, ", 2) }} | |
| 114 -> d'oh, d'oh, aaargh | |
| 115 """ | |
| 116 if count is None: | |
| 117 count = -1 | |
| 118 if not eval_ctx.autoescape: | |
| 119 return text_type(s).replace(text_type(old), text_type(new), count) | |
| 120 if hasattr(old, '__html__') or hasattr(new, '__html__') and \ | |
| 121 not hasattr(s, '__html__'): | |
| 122 s = escape(s) | |
| 123 else: | |
| 124 s = soft_unicode(s) | |
| 125 return s.replace(soft_unicode(old), soft_unicode(new), count) | |
| 126 | |
| 127 | |
| 128 def do_upper(s): | |
| 129 """Convert a value to uppercase.""" | |
| 130 return soft_unicode(s).upper() | |
| 131 | |
| 132 | |
| 133 def do_lower(s): | |
| 134 """Convert a value to lowercase.""" | |
| 135 return soft_unicode(s).lower() | |
| 136 | |
| 137 | |
| 138 @evalcontextfilter | |
| 139 def do_xmlattr(_eval_ctx, d, autospace=True): | |
| 140 """Create an SGML/XML attribute string based on the items in a dict. | |
| 141 All values that are neither `none` nor `undefined` are automatically | |
| 142 escaped: | |
| 143 | |
| 144 .. sourcecode:: html+jinja | |
| 145 | |
| 146 <ul{{ {'class': 'my_list', 'missing': none, | |
| 147 'id': 'list-%d'|format(variable)}|xmlattr }}> | |
| 148 ... | |
| 149 </ul> | |
| 150 | |
| 151 Results in something like this: | |
| 152 | |
| 153 .. sourcecode:: html | |
| 154 | |
| 155 <ul class="my_list" id="list-42"> | |
| 156 ... | |
| 157 </ul> | |
| 158 | |
| 159 As you can see it automatically prepends a space in front of the item | |
| 160 if the filter returned something unless the second parameter is false. | |
| 161 """ | |
| 162 rv = u' '.join( | |
| 163 u'%s="%s"' % (escape(key), escape(value)) | |
| 164 for key, value in iteritems(d) | |
| 165 if value is not None and not isinstance(value, Undefined) | |
| 166 ) | |
| 167 if autospace and rv: | |
| 168 rv = u' ' + rv | |
| 169 if _eval_ctx.autoescape: | |
| 170 rv = Markup(rv) | |
| 171 return rv | |
| 172 | |
| 173 | |
| 174 def do_capitalize(s): | |
| 175 """Capitalize a value. The first character will be uppercase, all others | |
| 176 lowercase. | |
| 177 """ | |
| 178 return soft_unicode(s).capitalize() | |
| 179 | |
| 180 | |
| 181 def do_title(s): | |
| 182 """Return a titlecased version of the value. I.e. words will start with | |
| 183 uppercase letters, all remaining characters are lowercase. | |
| 184 """ | |
| 185 rv = [] | |
| 186 for item in re.compile(r'([-\s]+)(?u)').split(s): | |
| 187 if not item: | |
| 188 continue | |
| 189 rv.append(item[0].upper() + item[1:].lower()) | |
| 190 return ''.join(rv) | |
| 191 | |
| 192 | |
| 193 def do_dictsort(value, case_sensitive=False, by='key'): | |
| 194 """Sort a dict and yield (key, value) pairs. Because python dicts are | |
| 195 unsorted you may want to use this function to order them by either | |
| 196 key or value: | |
| 197 | |
| 198 .. sourcecode:: jinja | |
| 199 | |
| 200 {% for item in mydict|dictsort %} | |
| 201 sort the dict by key, case insensitive | |
| 202 | |
| 203 {% for item in mydict|dictsort(true) %} | |
| 204 sort the dict by key, case sensitive | |
| 205 | |
| 206 {% for item in mydict|dictsort(false, 'value') %} | |
| 207 sort the dict by key, case insensitive, sorted | |
| 208 normally and ordered by value. | |
| 209 """ | |
| 210 if by == 'key': | |
| 211 pos = 0 | |
| 212 elif by == 'value': | |
| 213 pos = 1 | |
| 214 else: | |
| 215 raise FilterArgumentError('You can only sort by either ' | |
| 216 '"key" or "value"') | |
| 217 def sort_func(item): | |
| 218 value = item[pos] | |
| 219 if isinstance(value, string_types) and not case_sensitive: | |
| 220 value = value.lower() | |
| 221 return value | |
| 222 | |
| 223 return sorted(value.items(), key=sort_func) | |
| 224 | |
| 225 | |
| 226 @environmentfilter | |
| 227 def do_sort(environment, value, reverse=False, case_sensitive=False, | |
| 228 attribute=None): | |
| 229 """Sort an iterable. Per default it sorts ascending, if you pass it | |
| 230 true as first argument it will reverse the sorting. | |
| 231 | |
| 232 If the iterable is made of strings the third parameter can be used to | |
| 233 control the case sensitiveness of the comparison which is disabled by | |
| 234 default. | |
| 235 | |
| 236 .. sourcecode:: jinja | |
| 237 | |
| 238 {% for item in iterable|sort %} | |
| 239 ... | |
| 240 {% endfor %} | |
| 241 | |
| 242 It is also possible to sort by an attribute (for example to sort | |
| 243 by the date of an object) by specifying the `attribute` parameter: | |
| 244 | |
| 245 .. sourcecode:: jinja | |
| 246 | |
| 247 {% for item in iterable|sort(attribute='date') %} | |
| 248 ... | |
| 249 {% endfor %} | |
| 250 | |
| 251 .. versionchanged:: 2.6 | |
| 252 The `attribute` parameter was added. | |
| 253 """ | |
| 254 if not case_sensitive: | |
| 255 def sort_func(item): | |
| 256 if isinstance(item, string_types): | |
| 257 item = item.lower() | |
| 258 return item | |
| 259 else: | |
| 260 sort_func = None | |
| 261 if attribute is not None: | |
| 262 getter = make_attrgetter(environment, attribute) | |
| 263 def sort_func(item, processor=sort_func or (lambda x: x)): | |
| 264 return processor(getter(item)) | |
| 265 return sorted(value, key=sort_func, reverse=reverse) | |
| 266 | |
| 267 | |
| 268 def do_default(value, default_value=u'', boolean=False): | |
| 269 """If the value is undefined it will return the passed default value, | |
| 270 otherwise the value of the variable: | |
| 271 | |
| 272 .. sourcecode:: jinja | |
| 273 | |
| 274 {{ my_variable|default('my_variable is not defined') }} | |
| 275 | |
| 276 This will output the value of ``my_variable`` if the variable was | |
| 277 defined, otherwise ``'my_variable is not defined'``. If you want | |
| 278 to use default with variables that evaluate to false you have to | |
| 279 set the second parameter to `true`: | |
| 280 | |
| 281 .. sourcecode:: jinja | |
| 282 | |
| 283 {{ ''|default('the string was empty', true) }} | |
| 284 """ | |
| 285 if isinstance(value, Undefined) or (boolean and not value): | |
| 286 return default_value | |
| 287 return value | |
| 288 | |
| 289 | |
| 290 @evalcontextfilter | |
| 291 def do_join(eval_ctx, value, d=u'', attribute=None): | |
| 292 """Return a string which is the concatenation of the strings in the | |
| 293 sequence. The separator between elements is an empty string per | |
| 294 default, you can define it with the optional parameter: | |
| 295 | |
| 296 .. sourcecode:: jinja | |
| 297 | |
| 298 {{ [1, 2, 3]|join('|') }} | |
| 299 -> 1|2|3 | |
| 300 | |
| 301 {{ [1, 2, 3]|join }} | |
| 302 -> 123 | |
| 303 | |
| 304 It is also possible to join certain attributes of an object: | |
| 305 | |
| 306 .. sourcecode:: jinja | |
| 307 | |
| 308 {{ users|join(', ', attribute='username') }} | |
| 309 | |
| 310 .. versionadded:: 2.6 | |
| 311 The `attribute` parameter was added. | |
| 312 """ | |
| 313 if attribute is not None: | |
| 314 value = imap(make_attrgetter(eval_ctx.environment, attribute), value) | |
| 315 | |
| 316 # no automatic escaping? joining is a lot eaiser then | |
| 317 if not eval_ctx.autoescape: | |
| 318 return text_type(d).join(imap(text_type, value)) | |
| 319 | |
| 320 # if the delimiter doesn't have an html representation we check | |
| 321 # if any of the items has. If yes we do a coercion to Markup | |
| 322 if not hasattr(d, '__html__'): | |
| 323 value = list(value) | |
| 324 do_escape = False | |
| 325 for idx, item in enumerate(value): | |
| 326 if hasattr(item, '__html__'): | |
| 327 do_escape = True | |
| 328 else: | |
| 329 value[idx] = text_type(item) | |
| 330 if do_escape: | |
| 331 d = escape(d) | |
| 332 else: | |
| 333 d = text_type(d) | |
| 334 return d.join(value) | |
| 335 | |
| 336 # no html involved, to normal joining | |
| 337 return soft_unicode(d).join(imap(soft_unicode, value)) | |
| 338 | |
| 339 | |
| 340 def do_center(value, width=80): | |
| 341 """Centers the value in a field of a given width.""" | |
| 342 return text_type(value).center(width) | |
| 343 | |
| 344 | |
| 345 @environmentfilter | |
| 346 def do_first(environment, seq): | |
| 347 """Return the first item of a sequence.""" | |
| 348 try: | |
| 349 return next(iter(seq)) | |
| 350 except StopIteration: | |
| 351 return environment.undefined('No first item, sequence was empty.') | |
| 352 | |
| 353 | |
| 354 @environmentfilter | |
| 355 def do_last(environment, seq): | |
| 356 """Return the last item of a sequence.""" | |
| 357 try: | |
| 358 return next(iter(reversed(seq))) | |
| 359 except StopIteration: | |
| 360 return environment.undefined('No last item, sequence was empty.') | |
| 361 | |
| 362 | |
| 363 @environmentfilter | |
| 364 def do_random(environment, seq): | |
| 365 """Return a random item from the sequence.""" | |
| 366 try: | |
| 367 return choice(seq) | |
| 368 except IndexError: | |
| 369 return environment.undefined('No random item, sequence was empty.') | |
| 370 | |
| 371 | |
| 372 def do_filesizeformat(value, binary=False): | |
| 373 """Format the value like a 'human-readable' file size (i.e. 13 kB, | |
| 374 4.1 MB, 102 Bytes, etc). Per default decimal prefixes are used (Mega, | |
| 375 Giga, etc.), if the second parameter is set to `True` the binary | |
| 376 prefixes are used (Mebi, Gibi). | |
| 377 """ | |
| 378 bytes = float(value) | |
| 379 base = binary and 1024 or 1000 | |
| 380 prefixes = [ | |
| 381 (binary and 'KiB' or 'kB'), | |
| 382 (binary and 'MiB' or 'MB'), | |
| 383 (binary and 'GiB' or 'GB'), | |
| 384 (binary and 'TiB' or 'TB'), | |
| 385 (binary and 'PiB' or 'PB'), | |
| 386 (binary and 'EiB' or 'EB'), | |
| 387 (binary and 'ZiB' or 'ZB'), | |
| 388 (binary and 'YiB' or 'YB') | |
| 389 ] | |
| 390 if bytes == 1: | |
| 391 return '1 Byte' | |
| 392 elif bytes < base: | |
| 393 return '%d Bytes' % bytes | |
| 394 else: | |
| 395 for i, prefix in enumerate(prefixes): | |
| 396 unit = base ** (i + 2) | |
| 397 if bytes < unit: | |
| 398 return '%.1f %s' % ((base * bytes / unit), prefix) | |
| 399 return '%.1f %s' % ((base * bytes / unit), prefix) | |
| 400 | |
| 401 | |
| 402 def do_pprint(value, verbose=False): | |
| 403 """Pretty print a variable. Useful for debugging. | |
| 404 | |
| 405 With Jinja 1.2 onwards you can pass it a parameter. If this parameter | |
| 406 is truthy the output will be more verbose (this requires `pretty`) | |
| 407 """ | |
| 408 return pformat(value, verbose=verbose) | |
| 409 | |
| 410 | |
| 411 @evalcontextfilter | |
| 412 def do_urlize(eval_ctx, value, trim_url_limit=None, nofollow=False): | |
| 413 """Converts URLs in plain text into clickable links. | |
| 414 | |
| 415 If you pass the filter an additional integer it will shorten the urls | |
| 416 to that number. Also a third argument exists that makes the urls | |
| 417 "nofollow": | |
| 418 | |
| 419 .. sourcecode:: jinja | |
| 420 | |
| 421 {{ mytext|urlize(40, true) }} | |
| 422 links are shortened to 40 chars and defined with rel="nofollow" | |
| 423 """ | |
| 424 rv = urlize(value, trim_url_limit, nofollow) | |
| 425 if eval_ctx.autoescape: | |
| 426 rv = Markup(rv) | |
| 427 return rv | |
| 428 | |
| 429 | |
| 430 def do_indent(s, width=4, indentfirst=False): | |
| 431 """Return a copy of the passed string, each line indented by | |
| 432 4 spaces. The first line is not indented. If you want to | |
| 433 change the number of spaces or indent the first line too | |
| 434 you can pass additional parameters to the filter: | |
| 435 | |
| 436 .. sourcecode:: jinja | |
| 437 | |
| 438 {{ mytext|indent(2, true) }} | |
| 439 indent by two spaces and indent the first line too. | |
| 440 """ | |
| 441 indention = u' ' * width | |
| 442 rv = (u'\n' + indention).join(s.splitlines()) | |
| 443 if indentfirst: | |
| 444 rv = indention + rv | |
| 445 return rv | |
| 446 | |
| 447 | |
| 448 def do_truncate(s, length=255, killwords=False, end='...'): | |
| 449 """Return a truncated copy of the string. The length is specified | |
| 450 with the first parameter which defaults to ``255``. If the second | |
| 451 parameter is ``true`` the filter will cut the text at length. Otherwise | |
| 452 it will discard the last word. If the text was in fact | |
| 453 truncated it will append an ellipsis sign (``"..."``). If you want a | |
| 454 different ellipsis sign than ``"..."`` you can specify it using the | |
| 455 third parameter. | |
| 456 | |
| 457 .. sourcecode:: jinja | |
| 458 | |
| 459 {{ "foo bar"|truncate(5) }} | |
| 460 -> "foo ..." | |
| 461 {{ "foo bar"|truncate(5, True) }} | |
| 462 -> "foo b..." | |
| 463 """ | |
| 464 if len(s) <= length: | |
| 465 return s | |
| 466 elif killwords: | |
| 467 return s[:length] + end | |
| 468 words = s.split(' ') | |
| 469 result = [] | |
| 470 m = 0 | |
| 471 for word in words: | |
| 472 m += len(word) + 1 | |
| 473 if m > length: | |
| 474 break | |
| 475 result.append(word) | |
| 476 result.append(end) | |
| 477 return u' '.join(result) | |
| 478 | |
| 479 @environmentfilter | |
| 480 def do_wordwrap(environment, s, width=79, break_long_words=True, | |
| 481 wrapstring=None): | |
| 482 """ | |
| 483 Return a copy of the string passed to the filter wrapped after | |
| 484 ``79`` characters. You can override this default using the first | |
| 485 parameter. If you set the second parameter to `false` Jinja will not | |
| 486 split words apart if they are longer than `width`. By default, the newlines | |
| 487 will be the default newlines for the environment, but this can be changed | |
| 488 using the wrapstring keyword argument. | |
| 489 | |
| 490 .. versionadded:: 2.7 | |
| 491 Added support for the `wrapstring` parameter. | |
| 492 """ | |
| 493 if not wrapstring: | |
| 494 wrapstring = environment.newline_sequence | |
| 495 import textwrap | |
| 496 return wrapstring.join(textwrap.wrap(s, width=width, expand_tabs=False, | |
| 497 replace_whitespace=False, | |
| 498 break_long_words=break_long_words)) | |
| 499 | |
| 500 | |
| 501 def do_wordcount(s): | |
| 502 """Count the words in that string.""" | |
| 503 return len(_word_re.findall(s)) | |
| 504 | |
| 505 | |
| 506 def do_int(value, default=0): | |
| 507 """Convert the value into an integer. If the | |
| 508 conversion doesn't work it will return ``0``. You can | |
| 509 override this default using the first parameter. | |
| 510 """ | |
| 511 try: | |
| 512 return int(value) | |
| 513 except (TypeError, ValueError): | |
| 514 # this quirk is necessary so that "42.23"|int gives 42. | |
| 515 try: | |
| 516 return int(float(value)) | |
| 517 except (TypeError, ValueError): | |
| 518 return default | |
| 519 | |
| 520 | |
| 521 def do_float(value, default=0.0): | |
| 522 """Convert the value into a floating point number. If the | |
| 523 conversion doesn't work it will return ``0.0``. You can | |
| 524 override this default using the first parameter. | |
| 525 """ | |
| 526 try: | |
| 527 return float(value) | |
| 528 except (TypeError, ValueError): | |
| 529 return default | |
| 530 | |
| 531 | |
| 532 def do_format(value, *args, **kwargs): | |
| 533 """ | |
| 534 Apply python string formatting on an object: | |
| 535 | |
| 536 .. sourcecode:: jinja | |
| 537 | |
| 538 {{ "%s - %s"|format("Hello?", "Foo!") }} | |
| 539 -> Hello? - Foo! | |
| 540 """ | |
| 541 if args and kwargs: | |
| 542 raise FilterArgumentError('can\'t handle positional and keyword ' | |
| 543 'arguments at the same time') | |
| 544 return soft_unicode(value) % (kwargs or args) | |
| 545 | |
| 546 | |
| 547 def do_trim(value): | |
| 548 """Strip leading and trailing whitespace.""" | |
| 549 return soft_unicode(value).strip() | |
| 550 | |
| 551 | |
| 552 def do_striptags(value): | |
| 553 """Strip SGML/XML tags and replace adjacent whitespace by one space. | |
| 554 """ | |
| 555 if hasattr(value, '__html__'): | |
| 556 value = value.__html__() | |
| 557 return Markup(text_type(value)).striptags() | |
| 558 | |
| 559 | |
| 560 def do_slice(value, slices, fill_with=None): | |
| 561 """Slice an iterator and return a list of lists containing | |
| 562 those items. Useful if you want to create a div containing | |
| 563 three ul tags that represent columns: | |
| 564 | |
| 565 .. sourcecode:: html+jinja | |
| 566 | |
| 567 <div class="columwrapper"> | |
| 568 {%- for column in items|slice(3) %} | |
| 569 <ul class="column-{{ loop.index }}"> | |
| 570 {%- for item in column %} | |
| 571 <li>{{ item }}</li> | |
| 572 {%- endfor %} | |
| 573 </ul> | |
| 574 {%- endfor %} | |
| 575 </div> | |
| 576 | |
| 577 If you pass it a second argument it's used to fill missing | |
| 578 values on the last iteration. | |
| 579 """ | |
| 580 seq = list(value) | |
| 581 length = len(seq) | |
| 582 items_per_slice = length // slices | |
| 583 slices_with_extra = length % slices | |
| 584 offset = 0 | |
| 585 for slice_number in range(slices): | |
| 586 start = offset + slice_number * items_per_slice | |
| 587 if slice_number < slices_with_extra: | |
| 588 offset += 1 | |
| 589 end = offset + (slice_number + 1) * items_per_slice | |
| 590 tmp = seq[start:end] | |
| 591 if fill_with is not None and slice_number >= slices_with_extra: | |
| 592 tmp.append(fill_with) | |
| 593 yield tmp | |
| 594 | |
| 595 | |
| 596 def do_batch(value, linecount, fill_with=None): | |
| 597 """ | |
| 598 A filter that batches items. It works pretty much like `slice` | |
| 599 just the other way round. It returns a list of lists with the | |
| 600 given number of items. If you provide a second parameter this | |
| 601 is used to fill up missing items. See this example: | |
| 602 | |
| 603 .. sourcecode:: html+jinja | |
| 604 | |
| 605 <table> | |
| 606 {%- for row in items|batch(3, ' ') %} | |
| 607 <tr> | |
| 608 {%- for column in row %} | |
| 609 <td>{{ column }}</td> | |
| 610 {%- endfor %} | |
| 611 </tr> | |
| 612 {%- endfor %} | |
| 613 </table> | |
| 614 """ | |
| 615 result = [] | |
| 616 tmp = [] | |
| 617 for item in value: | |
| 618 if len(tmp) == linecount: | |
| 619 yield tmp | |
| 620 tmp = [] | |
| 621 tmp.append(item) | |
| 622 if tmp: | |
| 623 if fill_with is not None and len(tmp) < linecount: | |
| 624 tmp += [fill_with] * (linecount - len(tmp)) | |
| 625 yield tmp | |
| 626 | |
| 627 | |
| 628 def do_round(value, precision=0, method='common'): | |
| 629 """Round the number to a given precision. The first | |
| 630 parameter specifies the precision (default is ``0``), the | |
| 631 second the rounding method: | |
| 632 | |
| 633 - ``'common'`` rounds either up or down | |
| 634 - ``'ceil'`` always rounds up | |
| 635 - ``'floor'`` always rounds down | |
| 636 | |
| 637 If you don't specify a method ``'common'`` is used. | |
| 638 | |
| 639 .. sourcecode:: jinja | |
| 640 | |
| 641 {{ 42.55|round }} | |
| 642 -> 43.0 | |
| 643 {{ 42.55|round(1, 'floor') }} | |
| 644 -> 42.5 | |
| 645 | |
| 646 Note that even if rounded to 0 precision, a float is returned. If | |
| 647 you need a real integer, pipe it through `int`: | |
| 648 | |
| 649 .. sourcecode:: jinja | |
| 650 | |
| 651 {{ 42.55|round|int }} | |
| 652 -> 43 | |
| 653 """ | |
| 654 if not method in ('common', 'ceil', 'floor'): | |
| 655 raise FilterArgumentError('method must be common, ceil or floor') | |
| 656 if method == 'common': | |
| 657 return round(value, precision) | |
| 658 func = getattr(math, method) | |
| 659 return func(value * (10 ** precision)) / (10 ** precision) | |
| 660 | |
| 661 | |
| 662 @environmentfilter | |
| 663 def do_groupby(environment, value, attribute): | |
| 664 """Group a sequence of objects by a common attribute. | |
| 665 | |
| 666 If you for example have a list of dicts or objects that represent persons | |
| 667 with `gender`, `first_name` and `last_name` attributes and you want to | |
| 668 group all users by genders you can do something like the following | |
| 669 snippet: | |
| 670 | |
| 671 .. sourcecode:: html+jinja | |
| 672 | |
| 673 <ul> | |
| 674 {% for group in persons|groupby('gender') %} | |
| 675 <li>{{ group.grouper }}<ul> | |
| 676 {% for person in group.list %} | |
| 677 <li>{{ person.first_name }} {{ person.last_name }}</li> | |
| 678 {% endfor %}</ul></li> | |
| 679 {% endfor %} | |
| 680 </ul> | |
| 681 | |
| 682 Additionally it's possible to use tuple unpacking for the grouper and | |
| 683 list: | |
| 684 | |
| 685 .. sourcecode:: html+jinja | |
| 686 | |
| 687 <ul> | |
| 688 {% for grouper, list in persons|groupby('gender') %} | |
| 689 ... | |
| 690 {% endfor %} | |
| 691 </ul> | |
| 692 | |
| 693 As you can see the item we're grouping by is stored in the `grouper` | |
| 694 attribute and the `list` contains all the objects that have this grouper | |
| 695 in common. | |
| 696 | |
| 697 .. versionchanged:: 2.6 | |
| 698 It's now possible to use dotted notation to group by the child | |
| 699 attribute of another attribute. | |
| 700 """ | |
| 701 expr = make_attrgetter(environment, attribute) | |
| 702 return sorted(map(_GroupTuple, groupby(sorted(value, key=expr), expr))) | |
| 703 | |
| 704 | |
| 705 class _GroupTuple(tuple): | |
| 706 __slots__ = () | |
| 707 grouper = property(itemgetter(0)) | |
| 708 list = property(itemgetter(1)) | |
| 709 | |
| 710 def __new__(cls, xxx_todo_changeme): | |
| 711 (key, value) = xxx_todo_changeme | |
| 712 return tuple.__new__(cls, (key, list(value))) | |
| 713 | |
| 714 | |
| 715 @environmentfilter | |
| 716 def do_sum(environment, iterable, attribute=None, start=0): | |
| 717 """Returns the sum of a sequence of numbers plus the value of parameter | |
| 718 'start' (which defaults to 0). When the sequence is empty it returns | |
| 719 start. | |
| 720 | |
| 721 It is also possible to sum up only certain attributes: | |
| 722 | |
| 723 .. sourcecode:: jinja | |
| 724 | |
| 725 Total: {{ items|sum(attribute='price') }} | |
| 726 | |
| 727 .. versionchanged:: 2.6 | |
| 728 The `attribute` parameter was added to allow suming up over | |
| 729 attributes. Also the `start` parameter was moved on to the right. | |
| 730 """ | |
| 731 if attribute is not None: | |
| 732 iterable = imap(make_attrgetter(environment, attribute), iterable) | |
| 733 return sum(iterable, start) | |
| 734 | |
| 735 | |
| 736 def do_list(value): | |
| 737 """Convert the value into a list. If it was a string the returned list | |
| 738 will be a list of characters. | |
| 739 """ | |
| 740 return list(value) | |
| 741 | |
| 742 | |
| 743 def do_mark_safe(value): | |
| 744 """Mark the value as safe which means that in an environment with automatic | |
| 745 escaping enabled this variable will not be escaped. | |
| 746 """ | |
| 747 return Markup(value) | |
| 748 | |
| 749 | |
| 750 def do_mark_unsafe(value): | |
| 751 """Mark a value as unsafe. This is the reverse operation for :func:`safe`."
"" | |
| 752 return text_type(value) | |
| 753 | |
| 754 | |
| 755 def do_reverse(value): | |
| 756 """Reverse the object or return an iterator the iterates over it the other | |
| 757 way round. | |
| 758 """ | |
| 759 if isinstance(value, string_types): | |
| 760 return value[::-1] | |
| 761 try: | |
| 762 return reversed(value) | |
| 763 except TypeError: | |
| 764 try: | |
| 765 rv = list(value) | |
| 766 rv.reverse() | |
| 767 return rv | |
| 768 except TypeError: | |
| 769 raise FilterArgumentError('argument must be iterable') | |
| 770 | |
| 771 | |
| 772 @environmentfilter | |
| 773 def do_attr(environment, obj, name): | |
| 774 """Get an attribute of an object. ``foo|attr("bar")`` works like | |
| 775 ``foo["bar"]`` just that always an attribute is returned and items are not | |
| 776 looked up. | |
| 777 | |
| 778 See :ref:`Notes on subscriptions <notes-on-subscriptions>` for more details. | |
| 779 """ | |
| 780 try: | |
| 781 name = str(name) | |
| 782 except UnicodeError: | |
| 783 pass | |
| 784 else: | |
| 785 try: | |
| 786 value = getattr(obj, name) | |
| 787 except AttributeError: | |
| 788 pass | |
| 789 else: | |
| 790 if environment.sandboxed and not \ | |
| 791 environment.is_safe_attribute(obj, name, value): | |
| 792 return environment.unsafe_undefined(obj, name) | |
| 793 return value | |
| 794 return environment.undefined(obj=obj, name=name) | |
| 795 | |
| 796 | |
| 797 @contextfilter | |
| 798 def do_map(*args, **kwargs): | |
| 799 """Applies a filter on a sequence of objects or looks up an attribute. | |
| 800 This is useful when dealing with lists of objects but you are really | |
| 801 only interested in a certain value of it. | |
| 802 | |
| 803 The basic usage is mapping on an attribute. Imagine you have a list | |
| 804 of users but you are only interested in a list of usernames: | |
| 805 | |
| 806 .. sourcecode:: jinja | |
| 807 | |
| 808 Users on this page: {{ users|map(attribute='username')|join(', ') }} | |
| 809 | |
| 810 Alternatively you can let it invoke a filter by passing the name of the | |
| 811 filter and the arguments afterwards. A good example would be applying a | |
| 812 text conversion filter on a sequence: | |
| 813 | |
| 814 .. sourcecode:: jinja | |
| 815 | |
| 816 Users on this page: {{ titles|map('lower')|join(', ') }} | |
| 817 | |
| 818 .. versionadded:: 2.7 | |
| 819 """ | |
| 820 context = args[0] | |
| 821 seq = args[1] | |
| 822 | |
| 823 if len(args) == 2 and 'attribute' in kwargs: | |
| 824 attribute = kwargs.pop('attribute') | |
| 825 if kwargs: | |
| 826 raise FilterArgumentError('Unexpected keyword argument %r' % | |
| 827 next(iter(kwargs))) | |
| 828 func = make_attrgetter(context.environment, attribute) | |
| 829 else: | |
| 830 try: | |
| 831 name = args[2] | |
| 832 args = args[3:] | |
| 833 except LookupError: | |
| 834 raise FilterArgumentError('map requires a filter argument') | |
| 835 func = lambda item: context.environment.call_filter( | |
| 836 name, item, args, kwargs, context=context) | |
| 837 | |
| 838 if seq: | |
| 839 for item in seq: | |
| 840 yield func(item) | |
| 841 | |
| 842 | |
| 843 @contextfilter | |
| 844 def do_select(*args, **kwargs): | |
| 845 """Filters a sequence of objects by appying a test to either the object | |
| 846 or the attribute and only selecting the ones with the test succeeding. | |
| 847 | |
| 848 Example usage: | |
| 849 | |
| 850 .. sourcecode:: jinja | |
| 851 | |
| 852 {{ numbers|select("odd") }} | |
| 853 | |
| 854 .. versionadded:: 2.7 | |
| 855 """ | |
| 856 return _select_or_reject(args, kwargs, lambda x: x, False) | |
| 857 | |
| 858 | |
| 859 @contextfilter | |
| 860 def do_reject(*args, **kwargs): | |
| 861 """Filters a sequence of objects by appying a test to either the object | |
| 862 or the attribute and rejecting the ones with the test succeeding. | |
| 863 | |
| 864 Example usage: | |
| 865 | |
| 866 .. sourcecode:: jinja | |
| 867 | |
| 868 {{ numbers|reject("odd") }} | |
| 869 | |
| 870 .. versionadded:: 2.7 | |
| 871 """ | |
| 872 return _select_or_reject(args, kwargs, lambda x: not x, False) | |
| 873 | |
| 874 | |
| 875 @contextfilter | |
| 876 def do_selectattr(*args, **kwargs): | |
| 877 """Filters a sequence of objects by appying a test to either the object | |
| 878 or the attribute and only selecting the ones with the test succeeding. | |
| 879 | |
| 880 Example usage: | |
| 881 | |
| 882 .. sourcecode:: jinja | |
| 883 | |
| 884 {{ users|selectattr("is_active") }} | |
| 885 {{ users|selectattr("email", "none") }} | |
| 886 | |
| 887 .. versionadded:: 2.7 | |
| 888 """ | |
| 889 return _select_or_reject(args, kwargs, lambda x: x, True) | |
| 890 | |
| 891 | |
| 892 @contextfilter | |
| 893 def do_rejectattr(*args, **kwargs): | |
| 894 """Filters a sequence of objects by appying a test to either the object | |
| 895 or the attribute and rejecting the ones with the test succeeding. | |
| 896 | |
| 897 .. sourcecode:: jinja | |
| 898 | |
| 899 {{ users|rejectattr("is_active") }} | |
| 900 {{ users|rejectattr("email", "none") }} | |
| 901 | |
| 902 .. versionadded:: 2.7 | |
| 903 """ | |
| 904 return _select_or_reject(args, kwargs, lambda x: not x, True) | |
| 905 | |
| 906 | |
| 907 def _select_or_reject(args, kwargs, modfunc, lookup_attr): | |
| 908 context = args[0] | |
| 909 seq = args[1] | |
| 910 if lookup_attr: | |
| 911 try: | |
| 912 attr = args[2] | |
| 913 except LookupError: | |
| 914 raise FilterArgumentError('Missing parameter for attribute name') | |
| 915 transfunc = make_attrgetter(context.environment, attr) | |
| 916 off = 1 | |
| 917 else: | |
| 918 off = 0 | |
| 919 transfunc = lambda x: x | |
| 920 | |
| 921 try: | |
| 922 name = args[2 + off] | |
| 923 args = args[3 + off:] | |
| 924 func = lambda item: context.environment.call_test( | |
| 925 name, item, args, kwargs) | |
| 926 except LookupError: | |
| 927 func = bool | |
| 928 | |
| 929 if seq: | |
| 930 for item in seq: | |
| 931 if modfunc(func(transfunc(item))): | |
| 932 yield item | |
| 933 | |
| 934 | |
| 935 FILTERS = { | |
| 936 'attr': do_attr, | |
| 937 'replace': do_replace, | |
| 938 'upper': do_upper, | |
| 939 'lower': do_lower, | |
| 940 'escape': escape, | |
| 941 'e': escape, | |
| 942 'forceescape': do_forceescape, | |
| 943 'capitalize': do_capitalize, | |
| 944 'title': do_title, | |
| 945 'default': do_default, | |
| 946 'd': do_default, | |
| 947 'join': do_join, | |
| 948 'count': len, | |
| 949 'dictsort': do_dictsort, | |
| 950 'sort': do_sort, | |
| 951 'length': len, | |
| 952 'reverse': do_reverse, | |
| 953 'center': do_center, | |
| 954 'indent': do_indent, | |
| 955 'title': do_title, | |
| 956 'capitalize': do_capitalize, | |
| 957 'first': do_first, | |
| 958 'last': do_last, | |
| 959 'map': do_map, | |
| 960 'random': do_random, | |
| 961 'reject': do_reject, | |
| 962 'rejectattr': do_rejectattr, | |
| 963 'filesizeformat': do_filesizeformat, | |
| 964 'pprint': do_pprint, | |
| 965 'truncate': do_truncate, | |
| 966 'wordwrap': do_wordwrap, | |
| 967 'wordcount': do_wordcount, | |
| 968 'int': do_int, | |
| 969 'float': do_float, | |
| 970 'string': soft_unicode, | |
| 971 'list': do_list, | |
| 972 'urlize': do_urlize, | |
| 973 'format': do_format, | |
| 974 'trim': do_trim, | |
| 975 'striptags': do_striptags, | |
| 976 'select': do_select, | |
| 977 'selectattr': do_selectattr, | |
| 978 'slice': do_slice, | |
| 979 'batch': do_batch, | |
| 980 'sum': do_sum, | |
| 981 'abs': abs, | |
| 982 'round': do_round, | |
| 983 'groupby': do_groupby, | |
| 984 'safe': do_mark_safe, | |
| 985 'xmlattr': do_xmlattr, | |
| 986 'urlencode': do_urlencode | |
| 987 } | |
| OLD | NEW |