Fixed #25670 -- Allowed dictsort to sort a list of lists.

Thanks Tim Graham for the review.
This commit is contained in:
Andrew Kuchev 2015-11-05 15:59:56 +05:00 committed by Tim Graham
parent cdbd8745f6
commit e81d1c995c
6 changed files with 107 additions and 7 deletions

View File

@ -5,6 +5,7 @@ import random as random_module
import re import re
from decimal import ROUND_HALF_UP, Context, Decimal, InvalidOperation from decimal import ROUND_HALF_UP, Context, Decimal, InvalidOperation
from functools import wraps from functools import wraps
from operator import itemgetter
from pprint import pformat from pprint import pformat
from django.utils import formats, six from django.utils import formats, six
@ -510,6 +511,32 @@ def striptags(value):
# LISTS # # LISTS #
################### ###################
def _property_resolver(arg):
"""
When arg is convertible to float, behave like operator.itemgetter(arg)
Otherwise, behave like Variable(arg).resolve
>>> _property_resolver(1)('abc')
'b'
>>> _property_resolver('1')('abc')
Traceback (most recent call last):
...
TypeError: string indices must be integers
>>> class Foo:
... a = 42
... b = 3.14
... c = 'Hey!'
>>> _property_resolver('b')(Foo())
3.14
"""
try:
float(arg)
except ValueError:
return Variable(arg).resolve
else:
return itemgetter(arg)
@register.filter(is_safe=False) @register.filter(is_safe=False)
def dictsort(value, arg): def dictsort(value, arg):
""" """
@ -517,7 +544,7 @@ def dictsort(value, arg):
the argument. the argument.
""" """
try: try:
return sorted(value, key=Variable(arg).resolve) return sorted(value, key=_property_resolver(arg))
except (TypeError, VariableDoesNotExist): except (TypeError, VariableDoesNotExist):
return '' return ''
@ -529,7 +556,7 @@ def dictsortreversed(value, arg):
property given in the argument. property given in the argument.
""" """
try: try:
return sorted(value, key=Variable(arg).resolve, reverse=True) return sorted(value, key=_property_resolver(arg), reverse=True)
except (TypeError, VariableDoesNotExist): except (TypeError, VariableDoesNotExist):
return '' return ''

View File

@ -813,7 +813,7 @@ TECHNICAL_500_TEMPLATE = ("""
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for var in frame.vars|dictsort:"0" %} {% for var in frame.vars|dictsort:0 %}
<tr> <tr>
<td>{{ var.0|force_escape }}</td> <td>{{ var.0|force_escape }}</td>
<td class="code"><pre>{{ var.1 }}</pre></td> <td class="code"><pre>{{ var.1 }}</pre></td>
@ -995,7 +995,7 @@ Exception Value: {{ exception_value|force_escape }}
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for var in request.META.items|dictsort:"0" %} {% for var in request.META.items|dictsort:0 %}
<tr> <tr>
<td>{{ var.0 }}</td> <td>{{ var.0 }}</td>
<td class="code"><pre>{{ var.1|pprint }}</pre></td> <td class="code"><pre>{{ var.1|pprint }}</pre></td>
@ -1017,7 +1017,7 @@ Exception Value: {{ exception_value|force_escape }}
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for var in settings.items|dictsort:"0" %} {% for var in settings.items|dictsort:0 %}
<tr> <tr>
<td>{{ var.0 }}</td> <td>{{ var.0 }}</td>
<td class="code"><pre>{{ var.1|pprint }}</pre></td> <td class="code"><pre>{{ var.1|pprint }}</pre></td>
@ -1107,12 +1107,12 @@ FILES:{% for k, v in request.FILES.items %}
COOKIES:{% for k, v in request.COOKIES.items %} COOKIES:{% for k, v in request.COOKIES.items %}
{{ k }} = {{ v|stringformat:"r" }}{% empty %} No cookie data{% endfor %} {{ k }} = {{ v|stringformat:"r" }}{% empty %} No cookie data{% endfor %}
META:{% for k, v in request.META.items|dictsort:"0" %} META:{% for k, v in request.META.items|dictsort:0 %}
{{ k }} = {{ v|stringformat:"r" }}{% endfor %} {{ k }} = {{ v|stringformat:"r" }}{% endfor %}
{% else %}Request data not supplied {% else %}Request data not supplied
{% endif %} {% endif %}
Settings: Settings:
Using settings module {{ settings.SETTINGS_MODULE }}{% for k, v in settings.items|dictsort:"0" %} Using settings module {{ settings.SETTINGS_MODULE }}{% for k, v in settings.items|dictsort:0 %}
{{ k }} = {{ v|stringformat:"r" }}{% endfor %} {{ k }} = {{ v|stringformat:"r" }}{% endfor %}
{% if not is_email %} {% if not is_email %}

View File

@ -1443,6 +1443,40 @@ then the output would be::
* 1984 (George) * 1984 (George)
* Timequake (Kurt) * Timequake (Kurt)
``dictsort`` can also order a list of lists (or any other object implementing
``__getitem__()``) by elements at specified index. For example::
{{ value|dictsort:0 }}
If ``value`` is:
.. code-block:: python
[
('a', '42'),
('c', 'string'),
('b', 'foo'),
]
then the output would be:
.. code-block:: python
[
('a', '42'),
('b', 'foo'),
('c', 'string'),
]
You must pass the index as an integer rather than a string. The following
produce empty output::
{{ values|dictsort:"0" }}
.. versionchanged:: 1.10
The ability to order a list of lists was added.
.. templatefilter:: dictsortreversed .. templatefilter:: dictsortreversed
``dictsortreversed`` ``dictsortreversed``

View File

@ -333,6 +333,9 @@ Templates
* Added the ``is`` comparison operator to the :ttag:`if` tag. * Added the ``is`` comparison operator to the :ttag:`if` tag.
* Allowed :tfilter:`dictsort` to order a list of lists by an element at a
specified index.
Tests Tests
~~~~~ ~~~~~

View File

@ -33,6 +33,24 @@ class FunctionTests(SimpleTestCase):
self.assertEqual([d['foo']['bar'] for d in sorted_data], [3, 2, 1]) self.assertEqual([d['foo']['bar'] for d in sorted_data], [3, 2, 1])
def test_sort_list_of_tuples(self):
data = [('a', '42'), ('c', 'string'), ('b', 'foo')]
expected = [('a', '42'), ('b', 'foo'), ('c', 'string')]
self.assertEqual(dictsort(data, 0), expected)
def test_sort_list_of_tuple_like_dicts(self):
data = [
{'0': 'a', '1': '42'},
{'0': 'c', '1': 'string'},
{'0': 'b', '1': 'foo'},
]
expected = [
{'0': 'a', '1': '42'},
{'0': 'b', '1': 'foo'},
{'0': 'c', '1': 'string'},
]
self.assertEqual(dictsort(data, '0'), expected)
def test_invalid_values(self): def test_invalid_values(self):
""" """
If dictsort is passed something other than a list of dictionaries, If dictsort is passed something other than a list of dictionaries,

View File

@ -19,6 +19,24 @@ class FunctionTests(SimpleTestCase):
[('age', 18), ('name', 'Jonny B Goode')]], [('age', 18), ('name', 'Jonny B Goode')]],
) )
def test_sort_list_of_tuples(self):
data = [('a', '42'), ('c', 'string'), ('b', 'foo')]
expected = [('c', 'string'), ('b', 'foo'), ('a', '42')]
self.assertEqual(dictsortreversed(data, 0), expected)
def test_sort_list_of_tuple_like_dicts(self):
data = [
{'0': 'a', '1': '42'},
{'0': 'c', '1': 'string'},
{'0': 'b', '1': 'foo'},
]
expected = [
{'0': 'c', '1': 'string'},
{'0': 'b', '1': 'foo'},
{'0': 'a', '1': '42'},
]
self.assertEqual(dictsortreversed(data, '0'), expected)
def test_invalid_values(self): def test_invalid_values(self):
""" """
If dictsortreversed is passed something other than a list of If dictsortreversed is passed something other than a list of