Skip to content

Commit

Permalink
Merge pull request #737 from davidism/minmax-filters
Browse files Browse the repository at this point in the history
min and max filters
  • Loading branch information
davidism committed Jul 6, 2017
2 parents 532171d + 52dcb47 commit 3ff8ec7
Show file tree
Hide file tree
Showing 3 changed files with 74 additions and 19 deletions.
2 changes: 2 additions & 0 deletions CHANGES
Expand Up @@ -25,8 +25,10 @@ Version 2.10
- The ``random`` filter is no longer incorrectly constant folded and will
produce a new random choice each time the template is rendered. (`#478`_)
- Add a ``unique`` filter. (`#469`_)
- Add ``min`` and ``max`` filters. (`#475`_)

.. _#469: https://github.com/pallets/jinja/pull/469
.. _#475: https://github.com/pallets/jinja/pull/475
.. _#478: https://github.com/pallets/jinja/pull/478

Version 2.9.6
Expand Down
71 changes: 52 additions & 19 deletions jinja2/filters.py
Expand Up @@ -12,7 +12,7 @@
import math
import random

from itertools import groupby
from itertools import groupby, chain
from collections import namedtuple
from jinja2.utils import Markup, escape, pformat, urlize, soft_unicode, \
unicode_urlencode, htmlsafe_json_dumps
Expand Down Expand Up @@ -279,25 +279,11 @@ def do_unique(environment, value, case_sensitive=False, attribute=None):
{{ ['foo', 'bar', 'foobar', 'FooBar']|unique }}
-> ['foo', 'bar', 'foobar']
This filter complements the `groupby` filter, which sorts and groups an
iterable by a certain attribute. The `unique` filter groups the items
from the iterable by themselves instead and always returns a flat list of
unique items. That can be useful for example when you need to concatenate
that items:
The unique items are yielded in the same order as their first occurrence in
the iterable passed to the filter.
.. sourcecode:: jinja
{{ ['foo', 'bar', 'foobar', 'FooBar']|unique|join(',') }}
-> foo,bar,foobar
Also note that the resulting list contains the items in the same order
as their first occurrence in the iterable passed to the filter. If sorting
is needed you can still chain the `unique` and `sort` filter:
.. sourcecode:: jinja
{{ ['foo', 'bar', 'foobar', 'FooBar']|unique|sort }}
-> ['bar', 'foo', 'foobar']
:param case_sensitive: Treat upper and lower case strings as distinct.
:param attribute: Filter objects with unique values for this attribute.
"""
getter = make_attrgetter(
environment, attribute,
Expand All @@ -313,6 +299,51 @@ def do_unique(environment, value, case_sensitive=False, attribute=None):
yield item


def _min_or_max(environment, value, func, case_sensitive, attribute):
it = iter(value)

try:
first = next(it)
except StopIteration:
return environment.undefined('No aggregated item, sequence was empty.')

key_func = make_attrgetter(
environment, attribute,
ignore_case if not case_sensitive else None
)
return func(chain([first], it), key=key_func)


@environmentfilter
def do_min(environment, value, case_sensitive=False, attribute=None):
"""Return the smallest item from the sequence.
.. sourcecode:: jinja
{{ [1, 2, 3]|min }}
-> 1
:param case_sensitive: Treat upper and lower case strings as distinct.
:param attribute: Get the object with the max value of this attribute.
"""
return _min_or_max(environment, value, min, case_sensitive, attribute)


@environmentfilter
def do_max(environment, value, case_sensitive=False, attribute=None):
"""Return the smallest item from the sequence.
.. sourcecode:: jinja
{{ [1, 2, 3]|max }}
-> 3
:param case_sensitive: Treat upper and lower case strings as distinct.
:param attribute: Get the object with the max value of this attribute.
"""
return _min_or_max(environment, value, max, case_sensitive, attribute)


def do_default(value, default_value=u'', boolean=False):
"""If the value is undefined it will return the passed default value,
otherwise the value of the variable:
Expand Down Expand Up @@ -1097,6 +1128,8 @@ def select_or_reject(args, kwargs, modfunc, lookup_attr):
'list': do_list,
'lower': do_lower,
'map': do_map,
'min': do_min,
'max': do_max,
'pprint': do_pprint,
'random': do_random,
'reject': do_reject,
Expand Down
20 changes: 20 additions & 0 deletions tests/test_filters.py
Expand Up @@ -403,6 +403,26 @@ def test_unique_attribute(self, env):
t = env.from_string("{{ items|unique(attribute='value')|join }}")
assert t.render(items=map(Magic, [3, 2, 4, 1, 2])) == '3241'

@pytest.mark.parametrize('source,expect', (
('{{ ["a", "B"]|min }}', 'a'),
('{{ ["a", "B"]|min(case_sensitive=true) }}', 'B'),
('{{ []|min }}', ''),
('{{ ["a", "B"]|max }}', 'B'),
('{{ ["a", "B"]|max(case_sensitive=true) }}', 'a'),
('{{ []|max }}', ''),
))
def test_min_max(self, env, source, expect):
t = env.from_string(source)
assert t.render() == expect

@pytest.mark.parametrize('name,expect', (
('min', '1'),
('max', '9'),
))
def test_min_max_attribute(self, env, name, expect):
t = env.from_string('{{ items|' + name + '(attribute="value") }}')
assert t.render(items=map(Magic, [5, 1, 9])) == expect

def test_groupby(self, env):
tmpl = env.from_string('''
{%- for grouper, list in [{'foo': 1, 'bar': 2},
Expand Down

0 comments on commit 3ff8ec7

Please sign in to comment.