blob: 176685d6837c19ec3f4da5b40fa96346856fa101 [file] [log] [blame]
Armin Ronacher92f572f2007-02-26 22:17:32 +01001# -*- coding: utf-8 -*-
2"""
Armin Ronacher26c0f512008-04-17 11:16:01 +02003 jinja2.filters
4 ~~~~~~~~~~~~~~
Armin Ronacher92f572f2007-02-26 22:17:32 +01005
6 Bundled jinja filters.
7
Christoph Hack2751d942008-04-09 12:14:55 +02008 :copyright: 2008 by Armin Ronacher, Christoph Hack.
Armin Ronacher92f572f2007-02-26 22:17:32 +01009 :license: BSD, see LICENSE for more details.
10"""
Georg Brandlaf31e4d2007-04-15 00:47:37 +020011import re
Armin Ronacher9a027f42008-04-17 11:13:40 +020012import math
Armin Ronacherfed86c12007-02-27 10:31:14 +010013from random import choice
Armin Ronacher1dcdac52007-12-09 22:53:46 +010014try:
15 from operator import itemgetter
16except ImportError:
17 itemgetter = lambda a: lambda b: b[a]
Armin Ronacherfed86c12007-02-27 10:31:14 +010018from urllib import urlencode, quote
Armin Ronacher9a027f42008-04-17 11:13:40 +020019from itertools import imap
20from jinja2.utils import Markup, escape, pformat, urlize, soft_unicode
Christoph Hack80909862008-04-14 01:35:10 +020021from jinja2.runtime import Undefined
Christoph Hacke9e43bb2008-04-13 23:35:48 +020022
Armin Ronacherfed86c12007-02-27 10:31:14 +010023
Armin Ronachereaf493e2007-10-01 22:31:16 +020024_striptags_re = re.compile(r'(<!--.*?-->|<[^>]*>)')
Armin Ronacher9bcd4112007-05-29 14:17:24 +020025
26
Christoph Hack2751d942008-04-09 12:14:55 +020027def contextfilter(f):
Armin Ronacher8edbe492008-04-10 20:43:43 +020028 """Decorator for marking context dependent filters. The current context
Christoph Hack2751d942008-04-09 12:14:55 +020029 argument will be passed as first argument.
Armin Ronacher92f572f2007-02-26 22:17:32 +010030 """
Armin Ronacher9a027f42008-04-17 11:13:40 +020031 if getattr(f, 'environmentfilter', False):
32 raise TypeError('filter already marked as environment filter')
Christoph Hack2751d942008-04-09 12:14:55 +020033 f.contextfilter = True
34 return f
Armin Ronacher9bcd4112007-05-29 14:17:24 +020035
36
Armin Ronacher9a027f42008-04-17 11:13:40 +020037def environmentfilter(f):
38 """Decorator for marking evironment dependent filters. The environment
39 used for the template is passed to the filter as first argument.
Armin Ronacher92f572f2007-02-26 22:17:32 +010040 """
Armin Ronacher9a027f42008-04-17 11:13:40 +020041 if getattr(f, 'contextfilter', False):
42 raise TypeError('filter already marked as context filter')
43 f.environmentfilter = True
44 return f
45
46
47def do_replace(s, old, new, count=None):
48 """Return a copy of the value with all occurrences of a substring
Armin Ronacher37a88512007-03-02 20:42:18 +010049 replaced with a new one. The first argument is the substring
50 that should be replaced, the second is the replacement string.
51 If the optional third argument ``count`` is given, only the first
52 ``count`` occurrences are replaced:
Armin Ronacher92f572f2007-02-26 22:17:32 +010053
Armin Ronacher37a88512007-03-02 20:42:18 +010054 .. sourcecode:: jinja
55
56 {{ "Hello World"|replace("Hello", "Goodbye") }}
57 -> Goodbye World
58
59 {{ "aaaaargh"|replace("a", "d'oh, ", 2) }}
60 -> d'oh, d'oh, aaargh
Armin Ronacher92f572f2007-02-26 22:17:32 +010061 """
Armin Ronacher2da479f2007-04-02 10:50:18 +020062 if count is None:
Armin Ronacher9a027f42008-04-17 11:13:40 +020063 count = -1
64 if hasattr(old, '__html__') or hasattr(new, '__html__') and \
65 not hasattr(s, '__html__'):
66 s = escape(s)
67 else:
68 s = soft_unicode(s)
Armin Ronacher92f572f2007-02-26 22:17:32 +010069 return s.replace(old, new, count)
Armin Ronacher92f572f2007-02-26 22:17:32 +010070
71
72def do_upper(s):
Armin Ronacher8edbe492008-04-10 20:43:43 +020073 """Convert a value to uppercase."""
Armin Ronacher9a027f42008-04-17 11:13:40 +020074 return soft_unicode(s).upper()
Armin Ronacher92f572f2007-02-26 22:17:32 +010075
76
Christoph Hack2751d942008-04-09 12:14:55 +020077def do_lower(s):
Armin Ronacher8edbe492008-04-10 20:43:43 +020078 """Convert a value to lowercase."""
Armin Ronacher9a027f42008-04-17 11:13:40 +020079 return soft_unicode(s).lower()
Armin Ronacher92f572f2007-02-26 22:17:32 +010080
81
Christoph Hack2751d942008-04-09 12:14:55 +020082def do_xmlattr(d, autospace=False):
Armin Ronacher9a027f42008-04-17 11:13:40 +020083 """Create an SGML/XML attribute string based on the items in a dict.
Armin Ronacher450756b2007-04-15 15:13:59 +020084 All values that are neither `none` nor `undefined` are automatically
85 escaped:
86
Armin Ronacher8ca55df2007-04-15 15:16:08 +020087 .. sourcecode:: html+jinja
Armin Ronacher450756b2007-04-15 15:13:59 +020088
Armin Ronacherc7ddd1d2007-04-28 15:56:27 +020089 <ul{{ {'class': 'my_list', 'missing': None,
Armin Ronacherd459e272007-04-15 15:31:05 +020090 'id': 'list-%d'|format(variable)}|xmlattr }}>
Armin Ronacher450756b2007-04-15 15:13:59 +020091 ...
92 </ul>
93
94 Results in something like this:
95
Armin Ronacher8ca55df2007-04-15 15:16:08 +020096 .. sourcecode:: html
97
Armin Ronacher450756b2007-04-15 15:13:59 +020098 <ul class="my_list" id="list-42">
99 ...
100 </ul>
101
Armin Ronacher9bcd4112007-05-29 14:17:24 +0200102 As you can see it automatically prepends a space in front of the item
Armin Ronacherc7ddd1d2007-04-28 15:56:27 +0200103 if the filter returned something. You can disable this by passing
104 `false` as only argument to the filter.
Armin Ronacher450756b2007-04-15 15:13:59 +0200105 """
Christoph Hack2751d942008-04-09 12:14:55 +0200106 if not hasattr(d, 'iteritems'):
107 raise TypeError('a dict is required')
108 result = []
109 for key, value in d.iteritems():
Armin Ronacher5f514882008-04-16 15:29:52 +0200110 if value is not None and not isinstance(value, Undefined):
Christoph Hack2751d942008-04-09 12:14:55 +0200111 result.append(u'%s="%s"' % (
112 escape(env.to_unicode(key)),
113 escape(env.to_unicode(value), True)
114 ))
Armin Ronacher9a027f42008-04-17 11:13:40 +0200115 rv = u' '.join(
116 u'%s="%s"' % (escape(key), escape(value))
117 for key, value in d.iteritems()
118 if value is not None and not isinstance(value, Undefined)
119 )
Christoph Hack2751d942008-04-09 12:14:55 +0200120 if autospace:
121 rv = ' ' + rv
Armin Ronacher9a027f42008-04-17 11:13:40 +0200122 return Markup(rv)
Armin Ronacher450756b2007-04-15 15:13:59 +0200123
124
Armin Ronacher92f572f2007-02-26 22:17:32 +0100125def do_capitalize(s):
Armin Ronacher9a027f42008-04-17 11:13:40 +0200126 """Capitalize a value. The first character will be uppercase, all others
Armin Ronacher37a88512007-03-02 20:42:18 +0100127 lowercase.
Armin Ronacher92f572f2007-02-26 22:17:32 +0100128 """
Armin Ronacher9a027f42008-04-17 11:13:40 +0200129 return soft_unicode(s).capitalize()
Armin Ronacher92f572f2007-02-26 22:17:32 +0100130
131
132def do_title(s):
Armin Ronacher9a027f42008-04-17 11:13:40 +0200133 """Return a titlecased version of the value. I.e. words will start with
Armin Ronacher37a88512007-03-02 20:42:18 +0100134 uppercase letters, all remaining characters are lowercase.
Armin Ronacher92f572f2007-02-26 22:17:32 +0100135 """
Armin Ronacher9a027f42008-04-17 11:13:40 +0200136 return soft_unicode(s).title()
Armin Ronacher92f572f2007-02-26 22:17:32 +0100137
138
Christoph Hacke9e43bb2008-04-13 23:35:48 +0200139def do_dictsort(value, case_sensitive=False, by='key'):
Armin Ronacher9a027f42008-04-17 11:13:40 +0200140 """ Sort a dict and yield (key, value) pairs. Because python dicts are
Armin Ronacher2b765132007-03-13 16:48:10 +0100141 unsorted you may want to use this function to order them by either
142 key or value:
143
144 .. sourcecode:: jinja
145
146 {% for item in mydict|dictsort %}
147 sort the dict by key, case insensitive
148
149 {% for item in mydict|dicsort(true) %}
150 sort the dict by key, case sensitive
151
152 {% for item in mydict|dictsort(false, 'value') %}
153 sort the dict by key, case insensitive, sorted
154 normally and ordered by value.
155 """
156 if by == 'key':
157 pos = 0
158 elif by == 'value':
159 pos = 1
160 else:
161 raise FilterArgumentError('You can only sort by either '
162 '"key" or "value"')
Armin Ronacher9a027f42008-04-17 11:13:40 +0200163 def sort_func(item):
164 value = item[pos]
Armin Ronacher2b765132007-03-13 16:48:10 +0100165 if isinstance(value, basestring):
Christoph Hacke9e43bb2008-04-13 23:35:48 +0200166 value = unicode(value)
Armin Ronacher2b765132007-03-13 16:48:10 +0100167 if not case_sensitive:
168 value = value.lower()
169 return value
170
Armin Ronacher9a027f42008-04-17 11:13:40 +0200171 return sorted(value.items(), key=sort_func)
Armin Ronacher2b765132007-03-13 16:48:10 +0100172
173
Christoph Hack2751d942008-04-09 12:14:55 +0200174def do_default(value, default_value=u'', boolean=False):
Armin Ronacher9a027f42008-04-17 11:13:40 +0200175 """If the value is undefined it will return the passed default value,
Armin Ronacher37a88512007-03-02 20:42:18 +0100176 otherwise the value of the variable:
Armin Ronacher92f572f2007-02-26 22:17:32 +0100177
Armin Ronacher37a88512007-03-02 20:42:18 +0100178 .. sourcecode:: jinja
179
180 {{ my_variable|default('my_variable is not defined') }}
181
182 This will output the value of ``my_variable`` if the variable was
183 defined, otherwise ``'my_variable is not defined'``. If you want
184 to use default with variables that evaluate to false you have to
185 set the second parameter to `true`:
186
187 .. sourcecode:: jinja
188
189 {{ ''|default('the string was empty', true) }}
Armin Ronacher92f572f2007-02-26 22:17:32 +0100190 """
Christoph Hacke9e43bb2008-04-13 23:35:48 +0200191 if (boolean and not value) or isinstance(value, Undefined):
Christoph Hack2751d942008-04-09 12:14:55 +0200192 return default_value
193 return value
Armin Ronacher92f572f2007-02-26 22:17:32 +0100194
195
Christoph Hack2751d942008-04-09 12:14:55 +0200196def do_join(value, d=u''):
Armin Ronacher9a027f42008-04-17 11:13:40 +0200197 """Return a string which is the concatenation of the strings in the
Armin Ronacher37a88512007-03-02 20:42:18 +0100198 sequence. The separator between elements is an empty string per
199 default, you can define ith with the optional parameter:
200
201 .. sourcecode:: jinja
202
203 {{ [1, 2, 3]|join('|') }}
204 -> 1|2|3
205
206 {{ [1, 2, 3]|join }}
207 -> 123
Armin Ronacher92f572f2007-02-26 22:17:32 +0100208 """
Armin Ronacher9a027f42008-04-17 11:13:40 +0200209 # if the delimiter doesn't have an html representation we check
210 # if any of the items has. If yes we do a coercion to Markup
211 if not hasttr(d, '__html__'):
212 value = list(value)
213 do_escape = False
214 for idx, item in enumerate(value):
215 if hasattr(item, '__html__'):
216 do_escape = True
217 else:
218 value[idx] = unicode(item)
219 if do_escape:
220 d = escape(d)
221 else:
222 d = unicode(d)
223 return d.join(value)
224
225 # no html involved, to normal joining
226 return soft_unicode(d).join(imap(soft_unicode, value))
Armin Ronacher92f572f2007-02-26 22:17:32 +0100227
228
Armin Ronacherfed86c12007-02-27 10:31:14 +0100229def do_center(value, width=80):
Armin Ronacher9a027f42008-04-17 11:13:40 +0200230 """Centers the value in a field of a given width."""
Christoph Hack2751d942008-04-09 12:14:55 +0200231 return unicode(value).center(width)
Armin Ronacherfed86c12007-02-27 10:31:14 +0100232
233
Armin Ronacher9a027f42008-04-17 11:13:40 +0200234@environmentfilter
235def do_first(environment, seq):
236 """Return the frist item of a sequence."""
Christoph Hack2751d942008-04-09 12:14:55 +0200237 try:
238 return iter(seq).next()
239 except StopIteration:
Armin Ronacher9a027f42008-04-17 11:13:40 +0200240 return environment.undefined('seq|first',
Armin Ronacher5f514882008-04-16 15:29:52 +0200241 extra='the sequence was empty')
Armin Ronacherfed86c12007-02-27 10:31:14 +0100242
243
Armin Ronacher9a027f42008-04-17 11:13:40 +0200244@environmentfilter
245def do_last(environment, seq):
246 """Return the last item of a sequence."""
Christoph Hack2751d942008-04-09 12:14:55 +0200247 try:
248 return iter(reversed(seq)).next()
249 except StopIteration:
Armin Ronacher9a027f42008-04-17 11:13:40 +0200250 return environment.undefined('seq|last',
Armin Ronacher5f514882008-04-16 15:29:52 +0200251 extra='the sequence was empty')
Armin Ronacherfed86c12007-02-27 10:31:14 +0100252
253
Armin Ronacher9a027f42008-04-17 11:13:40 +0200254@environmentfilter
255def do_random(environment, seq):
256 """Return a random item from the sequence."""
Christoph Hack2751d942008-04-09 12:14:55 +0200257 try:
258 return choice(seq)
259 except IndexError:
Armin Ronacher9a027f42008-04-17 11:13:40 +0200260 return environment.undefined('seq|random',
Armin Ronacher5f514882008-04-16 15:29:52 +0200261 extra='the sequence was empty')
Armin Ronacherfed86c12007-02-27 10:31:14 +0100262
263
Christoph Hacke9e43bb2008-04-13 23:35:48 +0200264def do_filesizeformat(value):
Armin Ronacher9a027f42008-04-17 11:13:40 +0200265 """Format the value like a 'human-readable' file size (i.e. 13 KB,
Armin Ronacher5f514882008-04-16 15:29:52 +0200266 4.1 MB, 102 bytes, etc).
Armin Ronacher2b765132007-03-13 16:48:10 +0100267 """
Christoph Hacke9e43bb2008-04-13 23:35:48 +0200268 # fail silently
269 try:
270 bytes = float(value)
271 except TypeError:
272 bytes = 0
Armin Ronacher2b765132007-03-13 16:48:10 +0100273
Christoph Hacke9e43bb2008-04-13 23:35:48 +0200274 if bytes < 1024:
275 return "%d Byte%s" % (bytes, bytes != 1 and 's' or '')
276 elif bytes < 1024 * 1024:
277 return "%.1f KB" % (bytes / 1024)
278 elif bytes < 1024 * 1024 * 1024:
279 return "%.1f MB" % (bytes / (1024 * 1024))
280 return "%.1f GB" % (bytes / (1024 * 1024 * 1024))
Armin Ronacher2b765132007-03-13 16:48:10 +0100281
282
Christoph Hack2751d942008-04-09 12:14:55 +0200283def do_pprint(value, verbose=False):
Armin Ronacher9a027f42008-04-17 11:13:40 +0200284 """Pretty print a variable. Useful for debugging.
Armin Ronacherf2ce1262007-10-21 21:51:51 +0200285
286 With Jinja 1.2 onwards you can pass it a parameter. If this parameter
Armin Ronacher5f3f1362007-10-21 22:15:04 +0200287 is truthy the output will be more verbose (this requires `pretty`)
Armin Ronacher2b765132007-03-13 16:48:10 +0100288 """
Christoph Hack2751d942008-04-09 12:14:55 +0200289 return pformat(value, verbose=verbose)
Armin Ronacher2b765132007-03-13 16:48:10 +0100290
291
292def do_urlize(value, trim_url_limit=None, nofollow=False):
Armin Ronacher9a027f42008-04-17 11:13:40 +0200293 """Converts URLs in plain text into clickable links.
Armin Ronacher2b765132007-03-13 16:48:10 +0100294
295 If you pass the filter an additional integer it will shorten the urls
296 to that number. Also a third argument exists that makes the urls
297 "nofollow":
298
299 .. sourcecode:: jinja
300
301 {{ mytext|urlize(40, True) }}
302 links are shortened to 40 chars and defined with rel="nofollow"
303 """
Armin Ronacher9a027f42008-04-17 11:13:40 +0200304 return urlize(soft_unicode(value), trim_url_limit, nofollow)
Armin Ronacher2b765132007-03-13 16:48:10 +0100305
306
307def do_indent(s, width=4, indentfirst=False):
308 """
309 {{ s|indent[ width[ indentfirst[ usetab]]] }}
310
311 Return a copy of the passed string, each line indented by
312 4 spaces. The first line is not indented. If you want to
313 change the number of spaces or indent the first line too
314 you can pass additional parameters to the filter:
315
316 .. sourcecode:: jinja
317
318 {{ mytext|indent(2, True) }}
319 indent by two spaces and indent the first line too.
320 """
321 indention = ' ' * width
322 if indentfirst:
Armin Ronacher9a027f42008-04-17 11:13:40 +0200323 return u'\n'.join(indention + line for line in s.splitlines())
Armin Ronacher2b765132007-03-13 16:48:10 +0100324 return s.replace('\n', '\n' + indention)
Armin Ronacher2b765132007-03-13 16:48:10 +0100325
326
327def do_truncate(s, length=255, killwords=False, end='...'):
328 """
Armin Ronacher2b765132007-03-13 16:48:10 +0100329 Return a truncated copy of the string. The length is specified
330 with the first parameter which defaults to ``255``. If the second
331 parameter is ``true`` the filter will cut the text at length. Otherwise
332 it will try to save the last word. If the text was in fact
333 truncated it will append an ellipsis sign (``"..."``). If you want a
334 different ellipsis sign than ``"..."`` you can specify it using the
335 third parameter.
336
337 .. sourcecode jinja::
338
339 {{ mytext|truncate(300, false, '&raquo;') }}
340 truncate mytext to 300 chars, don't split up words, use a
341 right pointing double arrow as ellipsis sign.
342 """
343 if len(s) <= length:
344 return s
345 elif killwords:
346 return s[:length] + end
347 words = s.split(' ')
348 result = []
349 m = 0
350 for word in words:
351 m += len(word) + 1
352 if m > length:
353 break
354 result.append(word)
355 result.append(end)
356 return u' '.join(result)
Armin Ronacher2b765132007-03-13 16:48:10 +0100357
358
359def do_wordwrap(s, pos=79, hard=False):
360 """
361 Return a copy of the string passed to the filter wrapped after
362 ``79`` characters. You can override this default using the first
363 parameter. If you set the second parameter to `true` Jinja will
364 also split words apart (usually a bad idea because it makes
365 reading hard).
366 """
367 if len(s) < pos:
368 return s
369 if hard:
Armin Ronacher9a027f42008-04-17 11:13:40 +0200370 return u'\n'.join(s[idx:idx + pos] for idx in
371 xrange(0, len(s), pos))
372
373 # TODO: switch to wordwrap.wrap
Armin Ronacher2b765132007-03-13 16:48:10 +0100374 # code from http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/148061
375 return reduce(lambda line, word, pos=pos: u'%s%s%s' %
376 (line, u' \n'[(len(line)-line.rfind('\n') - 1 +
377 len(word.split('\n', 1)[0]) >= pos)],
378 word), s.split(' '))
Armin Ronacher2b765132007-03-13 16:48:10 +0100379
380
381def do_wordcount(s):
Armin Ronacher9a027f42008-04-17 11:13:40 +0200382 """Count the words in that string."""
383 return len(x for x in s.split() if x)
Armin Ronacher2b765132007-03-13 16:48:10 +0100384
Christoph Hacke9e43bb2008-04-13 23:35:48 +0200385
386def do_int(value, default=0):
Armin Ronacher9a027f42008-04-17 11:13:40 +0200387 """Convert the value into an integer. If the
Armin Ronacherab45b842007-03-18 20:47:50 +0100388 conversion doesn't work it will return ``0``. You can
389 override this default using the first parameter.
Armin Ronacher2b765132007-03-13 16:48:10 +0100390 """
Christoph Hacke9e43bb2008-04-13 23:35:48 +0200391 try:
392 return int(value)
393 except (TypeError, ValueError):
Armin Ronacher814f6c22008-04-17 15:52:23 +0200394 return default
Armin Ronacher2b765132007-03-13 16:48:10 +0100395
396
Christoph Hacke9e43bb2008-04-13 23:35:48 +0200397def do_float(value, default=0.0):
Armin Ronacher9a027f42008-04-17 11:13:40 +0200398 """Convert the value into a floating point number. If the
Armin Ronacherab45b842007-03-18 20:47:50 +0100399 conversion doesn't work it will return ``0.0``. You can
400 override this default using the first parameter.
Armin Ronacher2b765132007-03-13 16:48:10 +0100401 """
Christoph Hacke9e43bb2008-04-13 23:35:48 +0200402 try:
403 return float(value)
404 except (TypeError, ValueError):
405 return default
Armin Ronacher2b765132007-03-13 16:48:10 +0100406
407
Christoph Hacke9e43bb2008-04-13 23:35:48 +0200408def do_string(value):
Armin Ronacher9a027f42008-04-17 11:13:40 +0200409 """Convert the value into an string."""
410 return soft_unicode(value)
Armin Ronacher2b765132007-03-13 16:48:10 +0100411
412
Armin Ronacher5f514882008-04-16 15:29:52 +0200413def do_format(value, *args, **kwargs):
Armin Ronacherf0f4deb2007-03-18 23:10:15 +0100414 """
415 Apply python string formatting on an object:
416
417 .. sourcecode:: jinja
418
419 {{ "%s - %s"|format("Hello?", "Foo!") }}
420 -> Hello? - Foo!
Armin Ronacherf0f4deb2007-03-18 23:10:15 +0100421 """
Armin Ronacher5f514882008-04-16 15:29:52 +0200422 if kwargs:
423 kwargs.update(idx, arg in enumerate(args))
424 args = kwargs
Armin Ronacher9a027f42008-04-17 11:13:40 +0200425 return soft_unicode(value) % args
Armin Ronacherf0f4deb2007-03-18 23:10:15 +0100426
427
Armin Ronacher566295e2007-03-19 13:19:34 +0100428def do_trim(value):
Armin Ronacher9a027f42008-04-17 11:13:40 +0200429 """Strip leading and trailing whitespace."""
430 return soft_unicode(value).strip()
Armin Ronacher566295e2007-03-19 13:19:34 +0100431
432
Armin Ronacher9bcd4112007-05-29 14:17:24 +0200433def do_striptags(value):
Armin Ronacher9a027f42008-04-17 11:13:40 +0200434 """Strip SGML/XML tags and replace adjacent whitespace by one space.
Georg Brandlaf31e4d2007-04-15 00:47:37 +0200435 """
Armin Ronacher9bcd4112007-05-29 14:17:24 +0200436 return ' '.join(_striptags_re.sub('', value).split())
Georg Brandlaf31e4d2007-04-15 00:47:37 +0200437
438
Christoph Hacke9e43bb2008-04-13 23:35:48 +0200439def do_slice(value, slices, fill_with=None):
Armin Ronacher9a027f42008-04-17 11:13:40 +0200440 """Slice an iterator and return a list of lists containing
Armin Ronacherd071f952007-04-13 22:32:11 +0200441 those items. Useful if you want to create a div containing
442 three div tags that represent columns:
443
Armin Ronacher89376072007-04-13 22:34:35 +0200444 .. sourcecode:: html+jinja
Armin Ronacherd071f952007-04-13 22:32:11 +0200445
446 <div class="columwrapper">
447 {%- for column in items|slice(3) %}
448 <ul class="column-{{ loop.index }}">
449 {%- for item in column %}
450 <li>{{ item }}</li>
451 {%- endfor %}
452 </ul>
453 {%- endfor %}
454 </div>
455
Armin Ronachereec31382007-04-14 14:50:45 +0200456 If you pass it a second argument it's used to fill missing
457 values on the last iteration.
Armin Ronacherd071f952007-04-13 22:32:11 +0200458 """
Armin Ronacher814f6c22008-04-17 15:52:23 +0200459 seq = list(seq)
Christoph Hacke9e43bb2008-04-13 23:35:48 +0200460 length = len(seq)
461 items_per_slice = length // slices
462 slices_with_extra = length % slices
463 offset = 0
464 for slice_number in xrange(slices):
465 start = offset + slice_number * items_per_slice
466 if slice_number < slices_with_extra:
467 offset += 1
468 end = offset + (slice_number + 1) * items_per_slice
469 tmp = seq[start:end]
470 if fill_with is not None and slice_number >= slices_with_extra:
471 tmp.append(fill_with)
Armin Ronacher814f6c22008-04-17 15:52:23 +0200472 yield tmp
Armin Ronacherd071f952007-04-13 22:32:11 +0200473
474
Christoph Hacke9e43bb2008-04-13 23:35:48 +0200475def do_batch(value, linecount, fill_with=None):
Armin Ronacherd071f952007-04-13 22:32:11 +0200476 """
477 A filter that batches items. It works pretty much like `slice`
478 just the other way round. It returns a list of lists with the
479 given number of items. If you provide a second parameter this
480 is used to fill missing items. See this example:
481
Armin Ronacher89376072007-04-13 22:34:35 +0200482 .. sourcecode:: html+jinja
Armin Ronacherd071f952007-04-13 22:32:11 +0200483
484 <table>
485 {%- for row in items|batch(3, '&nbsp;') %}
486 <tr>
487 {%- for column in row %}
488 <tr>{{ column }}</td>
489 {%- endfor %}
490 </tr>
491 {%- endfor %}
492 </table>
Armin Ronacherd071f952007-04-13 22:32:11 +0200493 """
Christoph Hacke9e43bb2008-04-13 23:35:48 +0200494 result = []
495 tmp = []
496 for item in value:
497 if len(tmp) == linecount:
Armin Ronacher814f6c22008-04-17 15:52:23 +0200498 yield tmp
Christoph Hacke9e43bb2008-04-13 23:35:48 +0200499 tmp = []
500 tmp.append(item)
501 if tmp:
502 if fill_with is not None and len(tmp) < linecount:
503 tmp += [fill_with] * (linecount - len(tmp))
Armin Ronacher814f6c22008-04-17 15:52:23 +0200504 yield tmp
Armin Ronacherd071f952007-04-13 22:32:11 +0200505
506
Armin Ronacher9a027f42008-04-17 11:13:40 +0200507def do_round(value, precision=0, method='common'):
508 """Round the number to a given precision. The first
Armin Ronachereec31382007-04-14 14:50:45 +0200509 parameter specifies the precision (default is ``0``), the
510 second the rounding method:
511
512 - ``'common'`` rounds either up or down
513 - ``'ceil'`` always rounds up
514 - ``'floor'`` always rounds down
515
516 If you don't specify a method ``'common'`` is used.
517
518 .. sourcecode:: jinja
519
520 {{ 42.55|round }}
521 -> 43
522 {{ 42.55|round(1, 'floor') }}
523 -> 42.5
524 """
525 if not method in ('common', 'ceil', 'floor'):
526 raise FilterArgumentError('method must be common, ceil or floor')
527 if precision < 0:
528 raise FilterArgumentError('precision must be a postive integer '
529 'or zero.')
Armin Ronacher9a027f42008-04-17 11:13:40 +0200530 if method == 'common':
531 return round(value, precision)
532 func = getattr(math, method)
533 if precision:
534 return func(value * 10 * precision) / (10 * precision)
535 else:
536 return func(value)
Armin Ronachereec31382007-04-14 14:50:45 +0200537
538
Armin Ronacher9a027f42008-04-17 11:13:40 +0200539def do_sort(value, reverse=False):
540 """Sort a sequence. Per default it sorts ascending, if you pass it
Armin Ronacher9bcd4112007-05-29 14:17:24 +0200541 `True` as first argument it will reverse the sorting.
Armin Ronacher9bcd4112007-05-29 14:17:24 +0200542 """
Armin Ronacher9a027f42008-04-17 11:13:40 +0200543 return sorted(value, reverse=reverse)
Armin Ronacher9bcd4112007-05-29 14:17:24 +0200544
545
Armin Ronacher9a027f42008-04-17 11:13:40 +0200546@environmentfilter
547def do_groupby(environment, value, attribute):
548 """Group a sequence of objects by a common attribute.
Armin Ronachere39a5d22007-06-23 21:11:53 +0200549
550 If you for example have a list of dicts or objects that represent persons
551 with `gender`, `first_name` and `last_name` attributes and you want to
552 group all users by genders you can do something like the following
553 snippet:
554
555 .. sourcecode:: html+jinja
556
557 <ul>
558 {% for group in persons|groupby('gender') %}
559 <li>{{ group.grouper }}<ul>
560 {% for person in group.list %}
561 <li>{{ person.first_name }} {{ person.last_name }}</li>
562 {% endfor %}</ul></li>
563 {% endfor %}
564 </ul>
565
566 As you can see the item we're grouping by is stored in the `grouper`
567 attribute and the `list` contains all the objects that have this grouper
568 in common.
569 """
Armin Ronacher9a027f42008-04-17 11:13:40 +0200570 expr = lambda x: environment.subscribe(x, attribute)
571 return sorted([{
572 'grouper': a,
573 'list': b
574 } for a, b in groupby(sorted(value, key=expr), expr)],
575 key=itemgetter('grouper'))
Armin Ronachere39a5d22007-06-23 21:11:53 +0200576
577
Armin Ronacher92f572f2007-02-26 22:17:32 +0100578FILTERS = {
579 'replace': do_replace,
580 'upper': do_upper,
581 'lower': do_lower,
Armin Ronacher814f6c22008-04-17 15:52:23 +0200582 'escape': escape,
583 'e': escape,
Armin Ronacher450756b2007-04-15 15:13:59 +0200584 'xmlattr': do_xmlattr,
Armin Ronacher92f572f2007-02-26 22:17:32 +0100585 'capitalize': do_capitalize,
586 'title': do_title,
587 'default': do_default,
588 'join': do_join,
Armin Ronacher5f514882008-04-16 15:29:52 +0200589 'count': len,
Armin Ronacher2b765132007-03-13 16:48:10 +0100590 'dictsort': do_dictsort,
Armin Ronacher5f514882008-04-16 15:29:52 +0200591 'length': len,
592 'reverse': reversed,
Armin Ronacherfed86c12007-02-27 10:31:14 +0100593 'center': do_center,
594 'title': do_title,
595 'capitalize': do_capitalize,
596 'first': do_first,
597 'last': do_last,
598 'random': do_random,
Armin Ronacher2b765132007-03-13 16:48:10 +0100599 'filesizeformat': do_filesizeformat,
600 'pprint': do_pprint,
Armin Ronacher2b765132007-03-13 16:48:10 +0100601 'truncate': do_truncate,
602 'wordwrap': do_wordwrap,
603 'wordcount': do_wordcount,
Armin Ronacher2b765132007-03-13 16:48:10 +0100604 'int': do_int,
605 'float': do_float,
Tassilo Schweyer934cb712007-03-14 18:04:07 +0100606 'string': do_string,
Armin Ronacherf0f4deb2007-03-18 23:10:15 +0100607 'urlize': do_urlize,
608 'format': do_format,
Armin Ronacherd071f952007-04-13 22:32:11 +0200609 'trim': do_trim,
Georg Brandlaf31e4d2007-04-15 00:47:37 +0200610 'striptags': do_striptags,
Armin Ronacherd071f952007-04-13 22:32:11 +0200611 'slice': do_slice,
Armin Ronachereec31382007-04-14 14:50:45 +0200612 'batch': do_batch,
Armin Ronacher5f514882008-04-16 15:29:52 +0200613 'sum': sum,
614 'abs': abs,
Armin Ronacher9bcd4112007-05-29 14:17:24 +0200615 'round': do_round,
Armin Ronachere39a5d22007-06-23 21:11:53 +0200616 'sort': do_sort,
Armin Ronacher18c6ca02008-04-17 10:03:29 +0200617 'groupby': do_groupby,
618 'safe': Markup
Armin Ronacher92f572f2007-02-26 22:17:32 +0100619}