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