Fixed #27126 -- Made {% regroup %} return a namedtuple to ease unpacking.

This commit is contained in:
Baptiste Mispelon 2016-08-26 11:39:06 +02:00 committed by Tim Graham
parent 7968bb7fad
commit 61b45dff6b
4 changed files with 52 additions and 2 deletions

View File

@ -4,6 +4,7 @@ from __future__ import unicode_literals
import re import re
import sys import sys
import warnings import warnings
from collections import namedtuple
from datetime import datetime from datetime import datetime
from itertools import cycle as itertools_cycle, groupby from itertools import cycle as itertools_cycle, groupby
@ -335,6 +336,9 @@ class LoremNode(Node):
return '\n\n'.join(paras) return '\n\n'.join(paras)
GroupedResult = namedtuple('GroupedResult', ['grouper', 'list'])
class RegroupNode(Node): class RegroupNode(Node):
def __init__(self, target, expression, var_name): def __init__(self, target, expression, var_name):
self.target, self.expression = target, expression self.target, self.expression = target, expression
@ -355,7 +359,7 @@ class RegroupNode(Node):
# List of dictionaries in the format: # List of dictionaries in the format:
# {'grouper': 'key', 'list': [list of contents]}. # {'grouper': 'key', 'list': [list of contents]}.
context[self.var_name] = [ context[self.var_name] = [
{'grouper': key, 'list': list(val)} GroupedResult(grouper=key, list=list(val))
for key, val in for key, val in
groupby(obj_list, lambda obj: self.resolve_expression(obj, context)) groupby(obj_list, lambda obj: self.resolve_expression(obj, context))
] ]

View File

@ -896,13 +896,36 @@ resulting list. Here, we're regrouping the ``cities`` list by the ``country``
attribute and calling the result ``country_list``. attribute and calling the result ``country_list``.
``{% regroup %}`` produces a list (in this case, ``country_list``) of ``{% 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 * ``grouper`` -- the item that was grouped by (e.g., the string "India" or
"Japan"). "Japan").
* ``list`` -- a list of all items in this group (e.g., a list of all cities * ``list`` -- a list of all items in this group (e.g., a list of all cities
with country='India'). 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 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. 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 If the ``cities`` list did *not* order its members by ``country``, the

View File

@ -300,6 +300,10 @@ Templates
supports context processors by setting the ``'context_processors'`` option in supports context processors by setting the ``'context_processors'`` option in
:setting:`OPTIONS <TEMPLATES-OPTIONS>`. :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 Tests
~~~~~ ~~~~~

View File

@ -100,3 +100,22 @@ class RegroupTagTests(SimpleTestCase):
def test_regroup08(self): def test_regroup08(self):
with self.assertRaises(TemplateSyntaxError): with self.assertRaises(TemplateSyntaxError):
self.engine.get_template('regroup08') 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,')