Fixed #27126 -- Made {% regroup %} return a namedtuple to ease unpacking.
This commit is contained in:
parent
7968bb7fad
commit
61b45dff6b
|
@ -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))
|
||||||
]
|
]
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
~~~~~
|
~~~~~
|
||||||
|
|
||||||
|
|
|
@ -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,')
|
||||||
|
|
Loading…
Reference in New Issue