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