]> git.ipfire.org Git - thirdparty/jinja.git/commitdiff
Adding a default parameter to builtin map filter
authorBrendan <brendancq@gmail.com>
Mon, 6 May 2019 15:49:48 +0000 (11:49 -0400)
committerDavid Lord <davidism@gmail.com>
Mon, 22 Jul 2019 16:43:13 +0000 (09:43 -0700)
CHANGES.rst
jinja2/filters.py
tests/test_filters.py

index a41b887dd0ebe1a15ace0ecf1b5e93a17f921b30..5c55276ad87eaf21351ae495cf56c973cae97525 100644 (file)
@@ -14,8 +14,10 @@ unreleased
   slow initial import. (`#765`_)
 - Python 2.6 and 3.3 are not supported anymore.
 - The `map` filter in async mode now automatically awaits
+- Added `default` parameter for the `map` filter. (`#985`_)
 
 .. _#765: https://github.com/pallets/jinja/issues/765
+.. _#985: https://github.com/pallets/jinja/pull/985
 
 
 Version 2.10.1
index bf5173c1a32db9fe82802f4a884b75f73c12dff4..94751b8788d810b594e1b58bf18ef0ce79c6186d 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, postprocess=None, default=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
@@ -76,6 +76,10 @@ def make_attrgetter(environment, attribute, postprocess=None):
         for part in attribute:
             item = environment.getitem(item, part)
 
+            if default and isinstance(item, Undefined):
+                item = default
+                break
+
         if postprocess is not None:
             item = postprocess(item)
 
@@ -961,6 +965,13 @@ def do_map(*args, **kwargs):
 
         Users on this page: {{ users|map(attribute='username')|join(', ') }}
 
+    If the list of objects may not contain the given attribute, a default
+    value may be provided.
+
+    .. sourcecode:: jinja
+
+        {{ 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:
@@ -1098,10 +1109,11 @@ def prepare_map(args, kwargs):
 
     if len(args) == 2 and 'attribute' in kwargs:
         attribute = kwargs.pop('attribute')
+        default = kwargs.pop('default', None)
         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 60808f2edbd0243961f0fd4d406dfceca5497d97..f963438995db5d272e0066a43e628319912fd4d6 100644 (file)
@@ -561,6 +561,22 @@ class TestFilter(object):
         tmpl = env.from_string('{{ users|map(attribute="name")|join("|") }}')
         assert tmpl.render(users=users) == 'john|jane|mike'
 
+    def test_attribute_map_default(self, env):
+        class User(object):
+            def __init__(self, name):
+                self.name = name
+        class NotUser(object):
+            def __init__(self, not_name):
+                self.not_name = not_name
+        env = Environment()
+        users = [
+            User('john'),
+            User('jane'),
+            NotUser('plant'),
+        ]
+        tmpl = env.from_string('{{ users|map(attribute="name", default="anonymous")|join("|") }}')
+        assert tmpl.render(users=users) == 'john|jane|anonymous'
+
     def test_empty_map(self, env):
         env = Environment()
         tmpl = env.from_string('{{ none|map("upper")|list }}')