= Helper functions for use in Markup templates = [[PageOutline(2)]] Often you need non-trivial presentation logic in templates, but Markup does not (yet) let you drop into straight Python. In Markup, such presentation logic must be either performed in the controller (i.e. the Python code feeding the template with date), or in ''helper functions'' that are called from within template expressions. This page serves as a place where generalized functions that solve common tasks in presentation logic can be collected. At some point, Markup might include a library of such functions. == Python Standard Library == Many of the Python [http://docs.python.org/lib/built-in-funcs.html builtin functions] (such as `reversed` or `sorted`), as well as those in the [http://docs.python.org/lib/module-itertools.html itertools] package (such as `groupby`), can be quite useful in templates. The builtin functions are available by default, whereas other functions need to be put in the template context data explicitly. == group() == The following was written by [http://www.cmlenz.net/ Christopher Lenz] for use in the [http://trac.edgewall.org/ Trac project]: {{{ #!python def group(iterable, num, predicate=None): """Combines the elements produced by the given iterable so that every `n` items are returned as a tuple. >>> items = [1, 2, 3, 4] >>> for item in group(items, 2): ... print item (1, 2) (3, 4) The last tuple is padded with `None` values if its' length is smaller than `num`. >>> items = [1, 2, 3, 4, 5] >>> for item in group(items, 2): ... print item (1, 2) (3, 4) (5, None) The optional `predicate` parameter can be used to flag elements that should not be packed together with other items. Only those elements where the predicate function returns True are grouped with other elements, otherwise they are returned as a tuple of length 1: >>> items = [1, 2, 3, 4] >>> for item in group(items, 2, lambda x: x != 3): ... print item (1, 2) (3,) (4, None) """ buf = [] for item in iterable: flush = predicate and not predicate(item) if buf and flush: buf += [None] * (num - len(buf)) yield tuple(buf) del buf[:] buf.append(item) if flush or len(buf) == num: yield tuple(buf) del buf[:] if buf: buf += [None] * (num - len(buf)) yield tuple(buf) }}} If the `predicate` functionality is not needed, a vastly simpler implementation of that function would be: {{{ #!python def group(iterable, num): """Group an iterable into an n-tuples iterable. Incomplete tuples are discarded e.g. >>> list(group(range(10), 3)) [(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, None, None)] """ return map(None, *[iter(iterable)] * num) }}} See also the Python Cookbook recipe “[http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/303060 Group a list into sequential n-tuples]”. === Example usage === {{{ #!xml
${cell}
}}} That should result in the following output: {{{ #!xml
ab
cd
e
}}} == paginate() == A basic pagination routine written by [http://www.cmlenz.net/ Christopher Lenz]. {{{ #!python from math import ceil def paginate(items, page=0, max_per_page=10): """Simple generic pagination. Given an iterable, this function returns: * the slice of objects on the requested page, * the total number of items, and * the total number of pages. The `items` parameter can be a list, tuple, or iterator: >>> items = range(12) >>> items [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11] >>> paginate(items) ([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], 12, 2) >>> paginate(items, page=1) ([10, 11], 12, 2) >>> paginate(iter(items)) ([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], 12, 2) >>> paginate(iter(items), page=1) ([10, 11], 12, 2) This function also works with generators: >>> def generate(): ... for idx in range(12): ... yield idx >>> paginate(generate()) ([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], 12, 2) >>> paginate(generate(), page=1) ([10, 11], 12, 2) The `max_per_page` parameter can be used to set the number of items that should be displayed per page: >>> items = range(12) >>> paginate(items, page=0, max_per_page=6) ([0, 1, 2, 3, 4, 5], 12, 2) >>> paginate(items, page=1, max_per_page=6) ([6, 7, 8, 9, 10, 11], 12, 2) """ if not page: page = 0 start = page * max_per_page stop = start + max_per_page count = None if hasattr(items, '__len__'): count = len(items) try: # Try slicing first for better performance retval = items[start:stop] except TypeError: # Slicing not supported, so iterate through the whole list retval = [] for idx, item in enumerate(items): if start <= idx < stop: retval.append(item) # If we already obtained the total number of items via `len()`, # we can break out of the loop as soon as we've got the last item # for the requested page if count is not None and idx >= stop: break if count is None: count = idx + 1 return retval, count, int(ceil(float(count) / max_per_page)) }}} === Example usage === {{{ #!xml

Page ${cur_page} of ${num_pages}


$num
}}} That should result in the following output: {{{ #!xml

Page 1 of 3


1 2 3
}}} == countoccurrences() / countdistinct() == Written by Arnar Birgisson and shared on the IrcChannel. {{{ #!python def countoccurrences(numbers, minlength=0): """Takes a list of integers, in the range of 0..n and returns a list of integers where i-th item is the number of times i appears in the input list. If minlength is specified and n+1 < minlength, the returned list is right-padded with zeroes to make it contain minlength items. Examples: >>> countoccurrences([0,3,1,2,1,1,3,5]) [1,3,1,2,0,1] >>> countoccurrences([0,3,1,2,1,1,3,5], 10) [1,3,1,2,0,1,0,0,0,0] """ # TODO come up with a better name counts = [0] * max(max(numbers)+1, minlength) for x in numbers: counts[x] += 1 return counts }}} === Example usage === {{{ #!xml ${counts[0]} ${counts[1]} ${counts[2]} }}} === Similar version with a dict, counts any hashable types === The previous function can only count occurrences of numbers. This counts occurrences of any hashable types and returns a dict instead of a list. {{{ #!python def countitemoccurrences(items, requireditems=[]): """Takes a list of hashable items and returns a dict whose keys are those items and the values are the counts of how many times each item appers in the list. If the list requireditems is specified it's values are guaranteed to be keys in the resulting dict, even if they don't appear in items in which case the count will be 0 Examples: >>> counttypes('blue green green'.split(), 'blue red green'.split()) {'blue': 1, 'green': 2, 'red': 0} """ counts = dict() for i in requireditems: counts[i] = 0 for i in items: counts[i] = counts.get(i, 0) + 1 return counts }}} === Generic version based on `itertools.groupby` === {{{ #!python from itertools import groupby def countdistinct(iterable, groups=None, key=None): """Count things. >>> items = ['red', 'green', 'blue', 'blue'] >>> countdistinct(items) {'blue': 2, 'green': 1, 'red': 1} You can ensure that specific groups are always included in the result, even if they don't occur in the input: >>> items = ['red', 'blue', 'blue'] >>> countdistinct(items, groups=['red', 'green', 'blue']) {'blue': 2, 'green': 0, 'red': 1} The optional `key` argument can be used to provide a function that returns the comparison key for every item: >>> from operator import itemgetter >>> items = [dict(name='foo', category='buzz'), ... dict(name='bar', category='buzz')] >>> print countdistinct(items, key=itemgetter('category')) {'buzz': 2} """ return dict([(g, 0) for g in groups] + [(g, len(list(l))) for g, l in groupby(iterable, key=key)]) }}} ---- See also: MarkupGuide, MarkupRecipes