earlier versions after an upgrade of the Python interpreter you don't have
to upgrade, it's enough to flush the bytecode cache. This just no longer
makes this necessary, Jinja2 will automatically detect these cases now.
+- the sum filter can now sum up values by attribute. This is a backwards
+ incompatible change. The argument to the filter previously was the
+ optional starting index which defaultes to zero. This now became the
+ second argument to the function because it's rarely used.
Version 2.5.5
-------------
return f
+def make_attrgetter(environment, attribute):
+ """Returns a callable that looks up the given attribute from a
+ passed object with the rules of the environment. Dots are allowed
+ to access attributes of attributes.
+ """
+ if '.' not in attribute:
+ return lambda x: environment.getitem(x, attribute)
+ attribute = attribute.split('.')
+ def attrgetter(item):
+ for part in attribute:
+ item = environment.getitem(item, part)
+ return item
+ return attrgetter
+
+
def do_forceescape(value):
"""Enforce HTML escaping. This will probably double escape variables."""
if hasattr(value, '__html__'):
It's now possible to use dotted notation to group by the child
attribute of another attribute.
"""
- if '.' in attribute:
- def expr(item):
- for part in attribute.split('.'):
- item = environment.getitem(item, part)
- return item
- else:
- expr = lambda x: environment.getitem(x, attribute)
+ expr = make_attrgetter(environment, attribute)
return sorted(map(_GroupTuple, groupby(sorted(value, key=expr), expr)))
return tuple.__new__(cls, (key, list(value)))
+@environmentfilter
+def do_sum(environment, iterable, attribute=None, start=0):
+ """Sums up an iterable.
+
+ .. versionchanged:: 2.6
+ The `attribute` parameter was added to allow suming up over
+ attributes.
+ """
+ if attribute is not None:
+ iterable = imap(make_attrgetter(environment, attribute), iterable)
+ return sum(iterable, start)
+
+
def do_list(value):
"""Convert the value into a list. If it was a string the returned list
will be a list of characters.
'striptags': do_striptags,
'slice': do_slice,
'batch': do_batch,
- 'sum': sum,
+ 'sum': do_sum,
'abs': abs,
'round': do_round,
'groupby': do_groupby,
tmpl = env.from_string('''{{ [1, 2, 3, 4, 5, 6]|sum }}''')
assert tmpl.render() == '21'
+ def test_sum_attributes(self):
+ tmpl = env.from_string('''{{ values|sum('value') }}''')
+ assert tmpl.render(values=[
+ {'value': 23},
+ {'value': 1},
+ {'value': 18},
+ ]) == '42'
+
+ def test_sum_attributes_nested(self):
+ tmpl = env.from_string('''{{ values|sum('real.value') }}''')
+ assert tmpl.render(values=[
+ {'real': {'value': 23}},
+ {'real': {'value': 1}},
+ {'real': {'value': 18}},
+ ]) == '42'
+
def test_abs(self):
tmpl = env.from_string('''{{ -1|abs }}|{{ 1|abs }}''')
assert tmpl.render() == '1|1', tmpl.render()
assert tmpl.render() == '[1, 2, 3]|[3, 2, 1]'
def test_sort2(self):
- tmpl = env.from_string('{{ "".join(["c", "A", "b", "D"]|sort(false, true)) }}')
+ tmpl = env.from_string('{{ "".join(["c", "A", "b", "D"]|sort) }}')
assert tmpl.render() == 'AbcD'
+ def test_sort3(self):
+ tmpl = env.from_string('''{{ ['foo', 'Bar', 'blah']|sort }}''')
+ assert tmpl.render() == "['Bar', 'blah', 'foo']"
+
def test_groupby(self):
tmpl = env.from_string('''
{%- for grouper, list in [{'foo': 1, 'bar': 2},
tmpl = env.from_string('{{ "<div>foo</div>" }}')
assert tmpl.render() == '<div>foo</div>'
- def test_sort2(self):
- tmpl = env.from_string('''{{ ['foo', 'Bar', 'blah']|sort }}''')
- assert tmpl.render() == "['Bar', 'blah', 'foo']"
-
def suite():
suite = unittest.TestSuite()