]> git.ipfire.org Git - thirdparty/jinja.git/commitdiff
add default to map filter 1018/head
authorKevin <krbiggers@gmail.com>
Fri, 31 May 2019 17:59:01 +0000 (13:59 -0400)
committerDavid Lord <davidism@gmail.com>
Mon, 22 Jul 2019 14:38:50 +0000 (07:38 -0700)
CHANGES.rst
jinja2/filters.py
tests/test_filters.py

index 63ad274f32588bf2a4239bc771fbfebe2bf1a9e8..2b6b7632e260e6139f4edde14121bddf95034b1f 100644 (file)
@@ -16,9 +16,11 @@ unreleased
 - The ``map`` filter in async mode now automatically awaits
 - Added a new ``ChainableUndefined`` class to support getitem
   and getattr on an undefined object. (`#977`_)
-- Allow `'{%+'` syntax (with NOP behavior) when
-  `lstrip_blocks == False` (`#748`_)
+- Allow ``{%+`` syntax (with NOP behavior) when
+  ``lstrip_blocks == False`` (`#748`_)
+- Added ``default`` param for ``map`` filter. (`#557`_)
 
+.. _#557: https://github.com/pallets/jinja/issues/557
 .. _#765: https://github.com/pallets/jinja/issues/765
 .. _#748: https://github.com/pallets/jinja/issues/748
 .. _#977: https://github.com/pallets/jinja/issues/977
index 834623cd510a070e00d83c28bc5f6eda5af602ee..da3e535cbdc31e22240f3c777d52b89e61351fc5 100644 (file)
@@ -59,7 +59,7 @@ def ignore_case(value):
     return value.lower() if isinstance(value, string_types) else value
 
 
-def make_attrgetter(environment, attribute, postprocess=None):
+def make_attrgetter(environment, attribute, default=None, 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
@@ -71,6 +71,9 @@ def make_attrgetter(environment, attribute, postprocess=None):
         for part in attribute:
             item = environment.getitem(item, part)
 
+            if isinstance(item, Undefined) and default:
+                item = default
+
         if postprocess is not None:
             item = postprocess(item)
 
@@ -352,8 +355,9 @@ def _min_or_max(environment, value, func, case_sensitive, attribute):
         return environment.undefined('No aggregated item, sequence was empty.')
 
     key_func = make_attrgetter(
-        environment, attribute,
-        ignore_case if not case_sensitive else None
+        environment,
+        attribute,
+        postprocess=ignore_case if not case_sensitive else None
     )
     return func(chain([first], it), key=key_func)
 
@@ -1003,6 +1007,13 @@ def do_map(*args, **kwargs):
 
         Users on this page: {{ users|map(attribute='username')|join(', ') }}
 
+    You can specify a ``default`` value to use if an object in the list
+    does not have the given attribute.
+
+    .. sourcecode:: jinja
+
+        Users on this page: {{ users|map(attribute="username", default="Anonymous")|join(", ") }}
+
     Alternatively you can let it invoke a filter by passing the name of the
     filter and the arguments afterwards.  A good example would be applying a
     text conversion filter on a sequence:
@@ -1011,6 +1022,9 @@ def do_map(*args, **kwargs):
 
         Users on this page: {{ titles|map('lower')|join(', ') }}
 
+    .. versionchanged:: 2.11.0
+        Added the ``default`` parameter.
+
     .. versionadded:: 2.7
     """
     seq, func = prepare_map(args, kwargs)
@@ -1137,13 +1151,16 @@ def do_tojson(eval_ctx, value, indent=None):
 def prepare_map(args, kwargs):
     context = args[0]
     seq = args[1]
+    default = None
 
     if len(args) == 2 and 'attribute' in kwargs:
         attribute = kwargs.pop('attribute')
+        if 'default' in kwargs:
+            default = kwargs.pop('default')
         if kwargs:
             raise FilterArgumentError('Unexpected keyword argument %r' %
                 next(iter(kwargs)))
-        func = make_attrgetter(context.environment, attribute)
+        func = make_attrgetter(context.environment, attribute, default=default)
     else:
         try:
             name = args[2]
index 0a337b6327a4c9b4debafec2514df734381e8ecb..fd3a0cf9afb554a701c8878f0f68b569f2c13d76 100644 (file)
@@ -599,6 +599,28 @@ class TestFilter(object):
         tmpl = env.from_string('{{ none|map("upper")|list }}')
         assert tmpl.render() == '[]'
 
+    def test_map_default(self, env):
+        class Fullname(object):
+            def __init__(self, firstname, lastname):
+                self.firstname = firstname
+                self.lastname = lastname
+
+        class Firstname(object):
+            def __init__(self, firstname):
+                self.firstname = firstname
+
+        env = Environment()
+        tmpl = env.from_string(
+            '{{ users|map(attribute="lastname", default="smith")|join(", ") }}'
+        )
+        users = [
+            Fullname("john", "lennon"),
+            Fullname("jane", "edwards"),
+            Fullname("jon", None),
+            Firstname("mike")
+        ]
+        assert tmpl.render(users=users) == "lennon, edwards, None, smith"
+
     def test_simple_select(self, env):
         env = Environment()
         tmpl = env.from_string('{{ [1, 2, 3, 4, 5]|select("odd")|join("|") }}')