summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBaptiste Mispelon <bmispelon@gmail.com>2016-08-26 11:39:06 +0200
committerTim Graham <timograham@gmail.com>2016-08-26 16:59:21 -0400
commit61b45dff6be3a2c85adac73acf5fe0de71f9ecda (patch)
tree861f107b2ff26cee60e1970c320d6d929bc25767
parent7968bb7fada503b79d8b0e8c92aa076d60c17241 (diff)
Fixed #27126 -- Made {% regroup %} return a namedtuple to ease unpacking.
-rw-r--r--django/template/defaulttags.py6
-rw-r--r--docs/ref/templates/builtins.txt25
-rw-r--r--docs/releases/1.11.txt4
-rw-r--r--tests/template_tests/syntax_tests/test_regroup.py19
4 files changed, 52 insertions, 2 deletions
diff --git a/django/template/defaulttags.py b/django/template/defaulttags.py
index 7372c9414d..bb6e8bf4c2 100644
--- a/django/template/defaulttags.py
+++ b/django/template/defaulttags.py
@@ -4,6 +4,7 @@ from __future__ import unicode_literals
import re
import sys
import warnings
+from collections import namedtuple
from datetime import datetime
from itertools import cycle as itertools_cycle, groupby
@@ -335,6 +336,9 @@ class LoremNode(Node):
return '\n\n'.join(paras)
+GroupedResult = namedtuple('GroupedResult', ['grouper', 'list'])
+
+
class RegroupNode(Node):
def __init__(self, target, expression, var_name):
self.target, self.expression = target, expression
@@ -355,7 +359,7 @@ class RegroupNode(Node):
# List of dictionaries in the format:
# {'grouper': 'key', 'list': [list of contents]}.
context[self.var_name] = [
- {'grouper': key, 'list': list(val)}
+ GroupedResult(grouper=key, list=list(val))
for key, val in
groupby(obj_list, lambda obj: self.resolve_expression(obj, context))
]
diff --git a/docs/ref/templates/builtins.txt b/docs/ref/templates/builtins.txt
index 1a8fe600ac..9a5bd4abba 100644
--- a/docs/ref/templates/builtins.txt
+++ b/docs/ref/templates/builtins.txt
@@ -896,13 +896,36 @@ resulting list. Here, we're regrouping the ``cities`` list by the ``country``
attribute and calling the result ``country_list``.
``{% regroup %}`` produces a list (in this case, ``country_list``) of
-**group objects**. Each group object has two attributes:
+**group objects**. Group objects are instances of
+:py:func:`~collections.namedtuple` with two fields:
* ``grouper`` -- the item that was grouped by (e.g., the string "India" or
"Japan").
* ``list`` -- a list of all items in this group (e.g., a list of all cities
with country='India').
+.. versionchanged:: 1.11
+
+ The group object was changed from a dictionary to a
+ :py:func:`~collections.namedtuple`.
+
+Because ``{% regroup %}`` produces :py:func:`~collections.namedtuple` objects,
+you can also write the previous example as::
+
+ {% regroup cities by country as country_list %}
+
+ <ul>
+ {% for country, local_cities in country_list %}
+ <li>{{ country }}
+ <ul>
+ {% for city in local_cities %}
+ <li>{{ city.name }}: {{ city.population }}</li>
+ {% endfor %}
+ </ul>
+ </li>
+ {% endfor %}
+ </ul>
+
Note that ``{% regroup %}`` does not order its input! Our example relies on
the fact that the ``cities`` list was ordered by ``country`` in the first place.
If the ``cities`` list did *not* order its members by ``country``, the
diff --git a/docs/releases/1.11.txt b/docs/releases/1.11.txt
index c8282f70d6..67c247c86e 100644
--- a/docs/releases/1.11.txt
+++ b/docs/releases/1.11.txt
@@ -300,6 +300,10 @@ Templates
supports context processors by setting the ``'context_processors'`` option in
:setting:`OPTIONS <TEMPLATES-OPTIONS>`.
+* The :ttag:`regroup` tag now returns ``namedtuple``\s instead of dictionaries
+ so you can unpack the group object directly in a loop, e.g.
+ ``{% for grouper, list in regrouped %}``.
+
Tests
~~~~~
diff --git a/tests/template_tests/syntax_tests/test_regroup.py b/tests/template_tests/syntax_tests/test_regroup.py
index d747c016f8..2c0135587e 100644
--- a/tests/template_tests/syntax_tests/test_regroup.py
+++ b/tests/template_tests/syntax_tests/test_regroup.py
@@ -100,3 +100,22 @@ class RegroupTagTests(SimpleTestCase):
def test_regroup08(self):
with self.assertRaises(TemplateSyntaxError):
self.engine.get_template('regroup08')
+
+ @setup({'regroup_unpack': '{% regroup data by bar as grouped %}'
+ '{% for grouper, group in grouped %}'
+ '{{ grouper }}:'
+ '{% for item in group %}'
+ '{{ item.foo }}'
+ '{% endfor %},'
+ '{% endfor %}'})
+ def test_regroup_unpack(self):
+ output = self.engine.render_to_string('regroup_unpack', {
+ 'data': [
+ {'foo': 'c', 'bar': 1},
+ {'foo': 'd', 'bar': 1},
+ {'foo': 'a', 'bar': 2},
+ {'foo': 'b', 'bar': 2},
+ {'foo': 'x', 'bar': 3},
+ ],
+ })
+ self.assertEqual(output, '1:cd,2:ab,3:x,')