From 226deb5707b5cf7171409e4534703300872ffcc9 Mon Sep 17 00:00:00 2001 From: Gassan Gousseinov Date: Sun, 14 Mar 2021 17:52:14 +0100 Subject: [PATCH] Allow select additional sort criteria on modal filter form --- rest_framework/filters.py | 32 +++++++++++++-- .../static/rest_framework/css/default.css | 8 ++++ .../static/rest_framework/js/default.js | 6 +++ .../rest_framework/filters/ordering.html | 19 +++++++-- rest_framework/templatetags/rest_framework.py | 11 ++++++ tests/test_filters.py | 39 +++++++++++++++++++ 6 files changed, 108 insertions(+), 7 deletions(-) diff --git a/rest_framework/filters.py b/rest_framework/filters.py index 366577519..5ee37563c 100644 --- a/rest_framework/filters.py +++ b/rest_framework/filters.py @@ -276,7 +276,8 @@ class OrderingFilter(BaseFilterBackend): def get_template_context(self, request, queryset, view): current = self.get_ordering(request, queryset, view) - current = None if not current else current[0] + if not current: + current = None options = [] context = { 'request': request, @@ -284,11 +285,36 @@ class OrderingFilter(BaseFilterBackend): 'param': self.ordering_param, } for key, label in self.get_valid_fields(queryset, view, context): - options.append((key, '%s - %s' % (label, _('ascending')))) - options.append(('-' + key, '%s - %s' % (label, _('descending')))) + options.append((key, '%s - %s' % (label, _('ascending')), *self.plus_key(current, key))) + options.append(('-' + key, '%s - %s' % (label, _('descending')), *self.plus_key(current, '-' + key))) context['options'] = options return context + @staticmethod + def plus_key(current, key): + priority = None + plus_key = None + if current: + if len(current) > 1: + try: + priority = current.index(key) + 1 + except ValueError: + pass + + for_plus = current.copy() + if key in for_plus: + # for click on minus + for_plus.remove(key) + else: + # for click on plus - rearrange sort priorities + # remove key or -key + for_plus = [k for k in for_plus if k.lstrip('-') != key.lstrip('-')] + for_plus.append(key) + if for_plus: + plus_key = ','.join(for_plus) + + return priority, plus_key + def to_html(self, request, queryset, view): template = loader.get_template(self.template) context = self.get_template_context(request, queryset, view) diff --git a/rest_framework/static/rest_framework/css/default.css b/rest_framework/static/rest_framework/css/default.css index 51ca3ba19..99fca1f15 100644 --- a/rest_framework/static/rest_framework/css/default.css +++ b/rest_framework/static/rest_framework/css/default.css @@ -80,3 +80,11 @@ pre { #filtersModal .modal-body h2 { margin-top: 0 } + +#filtersModal .list-group-item .glyphicon-plus[data-plus-ordering] { + visibility: hidden; +} + +#filtersModal .list-group-item:hover .glyphicon-plus[data-plus-ordering] { + visibility: visible; +} \ No newline at end of file diff --git a/rest_framework/static/rest_framework/js/default.js b/rest_framework/static/rest_framework/js/default.js index bec2e4f9e..b7c5aa32e 100644 --- a/rest_framework/static/rest_framework/js/default.js +++ b/rest_framework/static/rest_framework/js/default.js @@ -41,6 +41,12 @@ $(document).ready(function() { $('.form-switcher a:first').tab('show'); } + // add onclick to ordering plus and minus glyph + $('#filtersModal span[data-plus-ordering]').click(function(e) { + var glyph = $(e.target); + glyph.closest('a.list-group-item').attr('href', glyph.attr('data-plus-ordering')); + }); + $(window).on('load', function() { $('#errorModal').modal('show'); }); diff --git a/rest_framework/templates/rest_framework/filters/ordering.html b/rest_framework/templates/rest_framework/filters/ordering.html index b71b2a5bf..e094c6ce9 100644 --- a/rest_framework/templates/rest_framework/filters/ordering.html +++ b/rest_framework/templates/rest_framework/filters/ordering.html @@ -2,13 +2,24 @@ {% load i18n %}

{% trans "Ordering" %}

- {% for key, label in options %} - {% if key == current %} + {% for key, label, priority, plus_key in options %} + {% if key in current %} - {{ label }} + + {% if priority %} + {{ priority }} + {% endif %} + {{ label }} {% else %} - {{ label }} + + {% if current %} + + {% endif %} + {{ label }} + {% endif %} {% endfor %}
diff --git a/rest_framework/templatetags/rest_framework.py b/rest_framework/templatetags/rest_framework.py index 7bfa8f599..31e366c11 100644 --- a/rest_framework/templatetags/rest_framework.py +++ b/rest_framework/templatetags/rest_framework.py @@ -10,6 +10,7 @@ from django.utils.safestring import mark_safe from rest_framework.compat import apply_markdown, pygments_highlight from rest_framework.renderers import HTMLFormRenderer +from rest_framework.utils.urls import remove_query_param as _remove_query_param from rest_framework.utils.urls import replace_query_param register = template.Library() @@ -154,6 +155,16 @@ def add_query_param(request, key, val): return escape(replace_query_param(uri, key, val)) +@register.simple_tag +def remove_query_param(request, key): + """ + Remove a query parameter from the current request url, and return the new url. + """ + iri = request.get_full_path() + uri = iri_to_uri(iri) + return escape(_remove_query_param(uri, key)) + + @register.filter def as_string(value): if value is None: diff --git a/tests/test_filters.py b/tests/test_filters.py index 567e5f83f..684add679 100644 --- a/tests/test_filters.py +++ b/tests/test_filters.py @@ -712,6 +712,45 @@ class OrderingFilterTests(TestCase): view(request) +class OrderingFilterPlusGlyphTests(TestCase): + def setUp(self): + self.filter = filters.OrderingFilter() + + def test_no_ordering_defined(self): + self.assertEqual((None, None), self.filter.plus_key(None, 'id')) + self.assertEqual((None, None), self.filter.plus_key(None, '-id')) + + def test_one_ordering_param_same_key(self): + # there is no priority output at all + self.assertEqual((None, None), self.filter.plus_key(['id'], 'id')) + self.assertEqual((None, None), self.filter.plus_key(['-id'], '-id')) + + self.assertEqual((None, '-id'), self.filter.plus_key(['id'], '-id')) + self.assertEqual((None, 'id'), self.filter.plus_key(['-id'], 'id')) + + def test_one_ordering_param_different_key(self): + # there is no priority output at all + self.assertEqual((None, 'id,slug'), self.filter.plus_key(['id'], 'slug')) + self.assertEqual((None, 'id,-slug'), self.filter.plus_key(['id'], '-slug')) + + self.assertEqual((None, '-id,slug'), self.filter.plus_key(['-id'], 'slug')) + self.assertEqual((None, '-id,-slug'), self.filter.plus_key(['-id'], '-slug')) + + def test_two_ordering_param_exist_key(self): + self.assertEqual((1, 'slug'), self.filter.plus_key(['id', 'slug'], 'id')) + self.assertEqual((None, 'slug,-id'), self.filter.plus_key(['id', 'slug'], '-id')) + + self.assertEqual((2, 'id'), self.filter.plus_key(['id', 'slug'], 'slug')) + self.assertEqual((None, 'id,-slug'), self.filter.plus_key(['id', 'slug'], '-slug')) + + self.assertEqual((None, 'id,-slug'), self.filter.plus_key(['id', 'slug'], '-slug')) + self.assertEqual((2, 'id'), self.filter.plus_key(['id', 'slug'], 'slug')) + + def test_two_ordering_param_other_key(self): + self.assertEqual((None, 'id,slug,author'), self.filter.plus_key(['id', 'slug'], 'author')) + self.assertEqual((None, 'id,slug,-author'), self.filter.plus_key(['id', 'slug'], '-author')) + + class SensitiveOrderingFilterModel(models.Model): username = models.CharField(max_length=20) password = models.CharField(max_length=100)