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 |