]> git.ipfire.org Git - thirdparty/jinja.git/commitdiff
Added unique filter 469/head
authorSebastian Noack <sebastian.noack@gmail.com>
Tue, 4 Aug 2015 20:11:55 +0000 (22:11 +0200)
committerSebastian Noack <sebastian.noack@gmail.com>
Mon, 10 Aug 2015 10:25:21 +0000 (12:25 +0200)
jinja2/filters.py
tests/test_filters.py

index e5c7a1ab439f803d76563a4cd77695bc92cfb539..0f7762d858601397bcb704cb7a4e71b66b7f0ac3 100644 (file)
@@ -51,21 +51,24 @@ def environmentfilter(f):
     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
 
@@ -251,18 +254,51 @@ def do_sort(environment, value, reverse=False, case_sensitive=False,
     .. 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):
@@ -987,6 +1023,7 @@ FILTERS = {
     'title':                do_title,
     'trim':                 do_trim,
     'truncate':             do_truncate,
+    'unique':               do_unique,
     'upper':                do_upper,
     'urlencode':            do_urlencode,
     'urlize':               do_urlize,
index 741ef341b1b0f7691e3b01dccac91e3193e3f95f..d6c5a759c5a7a21d484a7ec1a450f6e4a79d2145 100644 (file)
@@ -13,6 +13,15 @@ from jinja2 import Markup, Environment
 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():
 
@@ -348,16 +357,21 @@ 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},