From e81d1c995c0cc5573d3627de0fe6b803b2f43fb2 Mon Sep 17 00:00:00 2001 From: Andrew Kuchev <0coming.soon@gmail.com> Date: Thu, 5 Nov 2015 15:59:56 +0500 Subject: [PATCH] Fixed #25670 -- Allowed dictsort to sort a list of lists. Thanks Tim Graham for the review. --- django/template/defaultfilters.py | 31 +++++++++++++++-- django/views/debug.py | 10 +++--- docs/ref/templates/builtins.txt | 34 +++++++++++++++++++ docs/releases/1.10.txt | 3 ++ .../filter_tests/test_dictsort.py | 18 ++++++++++ .../filter_tests/test_dictsortreversed.py | 18 ++++++++++ 6 files changed, 107 insertions(+), 7 deletions(-) diff --git a/django/template/defaultfilters.py b/django/template/defaultfilters.py index 1d0c6c6d22..a7c9b718ff 100644 --- a/django/template/defaultfilters.py +++ b/django/template/defaultfilters.py @@ -5,6 +5,7 @@ import random as random_module import re from decimal import ROUND_HALF_UP, Context, Decimal, InvalidOperation from functools import wraps +from operator import itemgetter from pprint import pformat from django.utils import formats, six @@ -510,6 +511,32 @@ def striptags(value): # 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) def dictsort(value, arg): """ @@ -517,7 +544,7 @@ def dictsort(value, arg): the argument. """ try: - return sorted(value, key=Variable(arg).resolve) + return sorted(value, key=_property_resolver(arg)) except (TypeError, VariableDoesNotExist): return '' @@ -529,7 +556,7 @@ def dictsortreversed(value, arg): property given in the argument. """ try: - return sorted(value, key=Variable(arg).resolve, reverse=True) + return sorted(value, key=_property_resolver(arg), reverse=True) except (TypeError, VariableDoesNotExist): return '' diff --git a/django/views/debug.py b/django/views/debug.py index 4445990c74..ffd16622ac 100644 --- a/django/views/debug.py +++ b/django/views/debug.py @@ -813,7 +813,7 @@ TECHNICAL_500_TEMPLATE = (""" - {% for var in frame.vars|dictsort:"0" %} + {% for var in frame.vars|dictsort:0 %} {{ var.0|force_escape }}
{{ var.1 }}
@@ -995,7 +995,7 @@ Exception Value: {{ exception_value|force_escape }} - {% for var in request.META.items|dictsort:"0" %} + {% for var in request.META.items|dictsort:0 %} {{ var.0 }}
{{ var.1|pprint }}
@@ -1017,7 +1017,7 @@ Exception Value: {{ exception_value|force_escape }} - {% for var in settings.items|dictsort:"0" %} + {% for var in settings.items|dictsort:0 %} {{ var.0 }}
{{ var.1|pprint }}
@@ -1107,12 +1107,12 @@ FILES:{% for k, v in request.FILES.items %} COOKIES:{% for k, v in request.COOKIES.items %} {{ 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 %} {% else %}Request data not supplied {% endif %} 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 %} {% if not is_email %} diff --git a/docs/ref/templates/builtins.txt b/docs/ref/templates/builtins.txt index 21e5321f80..20fcdb02b5 100644 --- a/docs/ref/templates/builtins.txt +++ b/docs/ref/templates/builtins.txt @@ -1443,6 +1443,40 @@ then the output would be:: * 1984 (George) * 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 ``dictsortreversed`` diff --git a/docs/releases/1.10.txt b/docs/releases/1.10.txt index fc7b03d2a7..8bff4e99c6 100644 --- a/docs/releases/1.10.txt +++ b/docs/releases/1.10.txt @@ -333,6 +333,9 @@ Templates * 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 ~~~~~ diff --git a/tests/template_tests/filter_tests/test_dictsort.py b/tests/template_tests/filter_tests/test_dictsort.py index 1d937a2dae..00c2bd42cb 100644 --- a/tests/template_tests/filter_tests/test_dictsort.py +++ b/tests/template_tests/filter_tests/test_dictsort.py @@ -33,6 +33,24 @@ class FunctionTests(SimpleTestCase): 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): """ If dictsort is passed something other than a list of dictionaries, diff --git a/tests/template_tests/filter_tests/test_dictsortreversed.py b/tests/template_tests/filter_tests/test_dictsortreversed.py index 92229495fe..ada199e127 100644 --- a/tests/template_tests/filter_tests/test_dictsortreversed.py +++ b/tests/template_tests/filter_tests/test_dictsortreversed.py @@ -19,6 +19,24 @@ class FunctionTests(SimpleTestCase): [('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): """ If dictsortreversed is passed something other than a list of