return f
-def make_attrgetter(environment, attribute):
+def make_attrgetter(environment, attribute, lowercase=False):
"""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. Integer parts in paths are
looked up as integers.
"""
- if not isinstance(attribute, string_types) \
- or ('.' not in attribute and not attribute.isdigit()):
- return lambda x: environment.getitem(x, attribute)
- attribute = attribute.split('.')
+ if attribute is None:
+ attribute = []
+ elif isinstance(attribute, string_types):
+ attribute = [int(x) if x.isdigit() else x for x in attribute.split('.')]
+ else:
+ attribute = [attribute]
+
def attrgetter(item):
for part in attribute:
- if part.isdigit():
- part = int(part)
item = environment.getitem(item, part)
+ if lowercase and isinstance(item, string_types):
+ item = item.lower()
return item
return attrgetter
.. versionchanged:: 2.6
The `attribute` parameter was added.
"""
- if not case_sensitive:
- def sort_func(item):
- if isinstance(item, string_types):
- item = item.lower()
- return item
- else:
- sort_func = None
- if attribute is not None:
- getter = make_attrgetter(environment, attribute)
- def sort_func(item, processor=sort_func or (lambda x: x)):
- return processor(getter(item))
- return sorted(value, key=sort_func, reverse=reverse)
+ key_func = make_attrgetter(environment, attribute, not case_sensitive)
+ return sorted(value, key=key_func, reverse=reverse)
+
+
+@environmentfilter
+def do_unique(environment, value, case_sensitive=False, attribute=None):
+ """Returns a list of unique items from the the given iterable.
+
+ .. sourcecode:: jinja
+
+ {{ ['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 themself instead and always returns a flat list of
+ unique items. That can be useuful for example when you need to concatenate
+ that items:
+
+ .. 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 occurence 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']
+ """
+ getter = make_attrgetter(environment, attribute, not case_sensitive)
+
+ seen = set()
+ rv = []
+
+ for item in value:
+ key = getter(item)
+ if key not in seen:
+ seen.add(key)
+ rv.append(item)
+
+ return rv
def do_default(value, default_value=u'', boolean=False):
'title': do_title,
'trim': do_trim,
'truncate': do_truncate,
+ 'unique': do_unique,
'upper': do_upper,
'urlencode': do_urlencode,
'urlize': do_urlize,
from jinja2._compat import text_type, implements_to_string
+@implements_to_string
+class Magic(object):
+ def __init__(self, value):
+ self.value = value
+
+ def __str__(self):
+ return text_type(self.value)
+
+
@pytest.mark.filter
class TestFilter():
assert tmpl.render() == "['Bar', 'blah', 'foo']"
def test_sort4(self, env):
- @implements_to_string
- class Magic(object):
- def __init__(self, value):
- self.value = value
-
- def __str__(self):
- return text_type(self.value)
tmpl = env.from_string('''{{ items|sort(attribute='value')|join }}''')
assert tmpl.render(items=map(Magic, [3, 2, 4, 1])) == '1234'
+ def test_unique1(self, env):
+ tmpl = env.from_string('{{ "".join(["b", "A", "a", "b"]|unique) }}')
+ assert tmpl.render() == "bA"
+
+ def test_unique2(self, env):
+ tmpl = env.from_string('{{ "".join(["b", "A", "a", "b"]|unique(true)) }}')
+ assert tmpl.render() == "bAa"
+
+ def test_unique3(self, env):
+ tmpl = env.from_string("{{ items|unique(attribute='value')|join }}")
+ assert tmpl.render(items=map(Magic, [3, 2, 4, 1, 2])) == '3241'
+
def test_groupby(self, env):
tmpl = env.from_string('''
{%- for grouper, list in [{'foo': 1, 'bar': 2},