From dd8afa2204149cf134e4ea30ebe51086e775218f Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Mon, 24 Jan 2011 00:57:58 +0100 Subject: [PATCH] The sum filter can now sum up attributes --- CHANGES | 4 ++++ jinja2/filters.py | 38 +++++++++++++++++++++++++++++-------- jinja2/testsuite/filters.py | 26 ++++++++++++++++++++----- 3 files changed, 55 insertions(+), 13 deletions(-) diff --git a/CHANGES b/CHANGES index 7815a6d..a410c9e 100644 --- a/CHANGES +++ b/CHANGES @@ -24,6 +24,10 @@ Version 2.6 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 ------------- diff --git a/jinja2/filters.py b/jinja2/filters.py index 3a81f70..82f0153 100644 --- a/jinja2/filters.py +++ b/jinja2/filters.py @@ -48,6 +48,21 @@ def environmentfilter(f): 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__'): @@ -600,13 +615,7 @@ def do_groupby(environment, value, attribute): 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))) @@ -619,6 +628,19 @@ class _GroupTuple(tuple): 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. @@ -720,7 +742,7 @@ FILTERS = { 'striptags': do_striptags, 'slice': do_slice, 'batch': do_batch, - 'sum': sum, + 'sum': do_sum, 'abs': abs, 'round': do_round, 'groupby': do_groupby, diff --git a/jinja2/testsuite/filters.py b/jinja2/testsuite/filters.py index 80a6f80..6331d76 100644 --- a/jinja2/testsuite/filters.py +++ b/jinja2/testsuite/filters.py @@ -204,6 +204,22 @@ class FilterTestCase(JinjaTestCase): 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() @@ -234,9 +250,13 @@ class FilterTestCase(JinjaTestCase): 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}, @@ -306,10 +326,6 @@ class FilterTestCase(JinjaTestCase): tmpl = env.from_string('{{ "
foo
" }}') 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() -- 2.26.2