]> git.ipfire.org Git - thirdparty/jinja.git/commitdiff
make attrgetter take generic postprocess function 735/head
authorDavid Lord <davidism@gmail.com>
Wed, 5 Jul 2017 21:22:27 +0000 (14:22 -0700)
committerDavid Lord <davidism@gmail.com>
Wed, 5 Jul 2017 21:22:27 +0000 (14:22 -0700)
add changelog

CHANGES
jinja2/filters.py
tests/test_filters.py

diff --git a/CHANGES b/CHANGES
index 2441849b08a8c6dbce0057c208155f67196f62d7..cc43aa55eba916b98b97e496790f064d736bda11 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -24,7 +24,9 @@ Version 2.10
   `trans` blocks.
 - 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`_)
 
+.. _#469: https://github.com/pallets/jinja/pull/469
 .. _#478: https://github.com/pallets/jinja/pull/478
 
 Version 2.9.6
index c419d66aa0654049cb895d9be60fd94f3957db75..c0d105bd0d7b04cb6aa667883f15bfd375b7e7bd 100644 (file)
@@ -52,7 +52,13 @@ def environmentfilter(f):
     return f
 
 
-def make_attrgetter(environment, attribute, lowercase=False):
+def ignore_case(value):
+    """For use as a postprocessor for :func:`make_attrgetter`. Converts strings
+    to lowercase and returns other types as-is."""
+    return value.lower() if isinstance(value, string_types) else value
+
+
+def make_attrgetter(environment, attribute, postprocess=None):
     """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
@@ -68,9 +74,12 @@ def make_attrgetter(environment, attribute, lowercase=False):
     def attrgetter(item):
         for part in attribute:
             item = environment.getitem(item, part)
-        if lowercase and isinstance(item, string_types):
-            item = item.lower()
+
+        if postprocess is not None:
+            item = postprocess(item)
+
         return item
+
     return attrgetter
 
 
@@ -226,8 +235,9 @@ def do_dictsort(value, case_sensitive=False, by='key'):
 
 
 @environmentfilter
-def do_sort(environment, value, reverse=False, case_sensitive=False,
-            attribute=None):
+def do_sort(
+    environment, value, reverse=False, case_sensitive=False, attribute=None
+):
     """Sort an iterable.  Per default it sorts ascending, if you pass it
     true as first argument it will reverse the sorting.
 
@@ -253,7 +263,10 @@ def do_sort(environment, value, reverse=False, case_sensitive=False,
     .. versionchanged:: 2.6
        The `attribute` parameter was added.
     """
-    key_func = make_attrgetter(environment, attribute, not case_sensitive)
+    key_func = make_attrgetter(
+        environment, attribute,
+        postprocess=ignore_case if not case_sensitive else None
+    )
     return sorted(value, key=key_func, reverse=reverse)
 
 
@@ -268,8 +281,8 @@ def do_unique(environment, value, case_sensitive=False, attribute=None):
 
     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
+    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:
 
     .. sourcecode:: jinja
@@ -278,7 +291,7 @@ def do_unique(environment, value, case_sensitive=False, attribute=None):
             -> 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
+    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
@@ -286,18 +299,18 @@ def do_unique(environment, value, case_sensitive=False, attribute=None):
         {{ ['foo', 'bar', 'foobar', 'FooBar']|unique|sort }}
             -> ['bar', 'foo', 'foobar']
     """
-    getter = make_attrgetter(environment, attribute, not case_sensitive)
-
+    getter = make_attrgetter(
+        environment, attribute,
+        postprocess=ignore_case if not case_sensitive else None
+    )
     seen = set()
-    rv = []
 
     for item in value:
         key = getter(item)
+
         if key not in seen:
             seen.add(key)
-            rv.append(item)
-
-    return rv
+            yield item
 
 
 def do_default(value, default_value=u'', boolean=False):
index 3cf94ac3c4c9a7a9973f5e7a6ee418f704b38144..01e4c3c15ea9bf486c44fba5a174b3badbf7f398 100644 (file)
@@ -391,17 +391,17 @@ class TestFilter(object):
         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_unique(self, env):
+        t = env.from_string('{{ "".join(["b", "A", "a", "b"]|unique) }}')
+        assert t.render() == "bA"
 
-    def test_unique2(self, env):
-        tmpl = env.from_string('{{ "".join(["b", "A", "a", "b"]|unique(true)) }}')
-        assert tmpl.render() == "bAa"
+    def test_unique_case_sensitive(self, env):
+        t = env.from_string('{{ "".join(["b", "A", "a", "b"]|unique(true)) }}')
+        assert t.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_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'
 
     def test_groupby(self, env):
         tmpl = env.from_string('''