浏览代码

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

Baptiste Mispelon 8 年之前
父节点
当前提交
61b45dff6b

+ 5 - 1
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))
         ]

+ 24 - 1
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

+ 4 - 0
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
 ~~~~~
 

+ 19 - 0
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,')