summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Lord <davidism@gmail.com>2021-04-05 14:44:37 -0700
committerGitHub <noreply@github.com>2021-04-05 14:44:37 -0700
commitc752dc81834ecfdc5635e4b5b24b9a74ed474def (patch)
tree0c6bb6b23598c54f1332d58c2feae705a20d5f03
parent4a941cab0ddff1ce58f4c6634e22a7659fd0648b (diff)
parent34fbec9a726113381a27c30d2363aae676b9086a (diff)
downloadjinja2-c752dc81834ecfdc5635e4b5b24b9a74ed474def.tar.gz
Merge pull request #1360 from lisongmin/add-default-value-to-groupby
Ability to set default value in groupby filter
-rw-r--r--CHANGES.rst2
-rw-r--r--src/jinja2/asyncfilters.py3
-rw-r--r--src/jinja2/filters.py19
-rw-r--r--tests/test_filters.py15
4 files changed, 36 insertions, 3 deletions
diff --git a/CHANGES.rst b/CHANGES.rst
index dd3739f..c94894a 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -58,6 +58,8 @@ Unreleased
- ``NativeEnvironment`` supports async mode. :issue:`1362`
- Template rendering only treats ``\n``, ``\r\n`` and ``\r`` as line
breaks. Other characters are left unchanged. :issue:`769, 952, 1313`
+- ``|groupby`` filter takes an optional ``default`` argument.
+ :issue:`1359`
Version 2.11.3
diff --git a/src/jinja2/asyncfilters.py b/src/jinja2/asyncfilters.py
index 11b031a..dfd8cba 100644
--- a/src/jinja2/asyncfilters.py
+++ b/src/jinja2/asyncfilters.py
@@ -107,8 +107,9 @@ async def do_groupby(
environment: "Environment",
value: "t.Union[t.AsyncIterable[V], t.Iterable[V]]",
attribute: t.Union[str, int],
+ default: t.Optional[t.Any] = None,
) -> "t.List[t.Tuple[t.Any, t.List[V]]]":
- expr = filters.make_attrgetter(environment, attribute)
+ expr = filters.make_attrgetter(environment, attribute, default=default)
return [
filters._GroupTuple(key, await auto_to_seq(values))
for key, values in groupby(sorted(await auto_to_seq(value), key=expr), expr)
diff --git a/src/jinja2/filters.py b/src/jinja2/filters.py
index 33353e5..c925f83 100644
--- a/src/jinja2/filters.py
+++ b/src/jinja2/filters.py
@@ -1116,7 +1116,10 @@ class _GroupTuple(t.NamedTuple):
@environmentfilter
def do_groupby(
- environment: "Environment", value: "t.Iterable[V]", attribute: t.Union[str, int]
+ environment: "Environment",
+ value: "t.Iterable[V]",
+ attribute: t.Union[str, int],
+ default: t.Optional[t.Any] = None,
) -> "t.List[t.Tuple[t.Any, t.List[V]]]":
"""Group a sequence of objects by an attribute using Python's
:func:`itertools.groupby`. The attribute can use dot notation for
@@ -1148,10 +1151,22 @@ def do_groupby(
<li>{{ group.grouper }}: {{ group.list|join(", ") }}
{% endfor %}</ul>
+ You can specify a ``default`` value to use if an object in the list
+ does not have the given attribute.
+
+ .. sourcecode:: jinja
+
+ <ul>{% for city, items in users|groupby("city", default="NY") %}
+ <li>{{ city }}: {{ items|map(attribute="name")|join(", ") }}</li>
+ {% endfor %}</ul>
+
+ .. versionchanged:: 3.0
+ Added the ``default`` parameter.
+
.. versionchanged:: 2.6
The attribute supports dot notation for nested access.
"""
- expr = make_attrgetter(environment, attribute)
+ expr = make_attrgetter(environment, attribute, default=default)
return [
_GroupTuple(key, list(values))
for key, values in groupby(sorted(value, key=expr), expr)
diff --git a/tests/test_filters.py b/tests/test_filters.py
index 2c119c3..0843246 100644
--- a/tests/test_filters.py
+++ b/tests/test_filters.py
@@ -593,6 +593,21 @@ class TestFilter:
"",
]
+ def test_groupby_default(self, env):
+ tmpl = env.from_string(
+ "{% for city, items in users|groupby('city', default='NY') %}"
+ "{{ city }}: {{ items|map(attribute='name')|join(', ') }}\n"
+ "{% endfor %}"
+ )
+ out = tmpl.render(
+ users=[
+ {"name": "emma", "city": "NY"},
+ {"name": "smith", "city": "WA"},
+ {"name": "john"},
+ ]
+ )
+ assert out == "NY: emma, john\nWA: smith\n"
+
def test_filtertag(self, env):
tmpl = env.from_string(
"{% filter upper|replace('FOO', 'foo') %}foobar{% endfilter %}"