From 8b11327a2f4763f75917e7425fa55a79c73abd87 Mon Sep 17 00:00:00 2001 From: Taavi Teska Date: Tue, 24 Apr 2018 11:59:00 +0300 Subject: [PATCH] Add mixin for easier implementation of ordering in Django's generic ListView --- AUTHORS.rst | 2 +- README.rst | 1 + tests/test_importing.py | 1 + tg_utils/mixins.py | 58 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 61 insertions(+), 1 deletion(-) create mode 100644 tg_utils/mixins.py diff --git a/AUTHORS.rst b/AUTHORS.rst index d4d0ab3..66e699f 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -10,4 +10,4 @@ Development Lead Contributors ------------ -None yet. Why not be the first? +* Taavi Teska diff --git a/README.rst b/README.rst index f7ffe74..f57e1e5 100644 --- a/README.rst +++ b/README.rst @@ -30,6 +30,7 @@ Features * Unique filename generation for uploads. * Using hashids for models (instead of exposing primary keys). * System checks for email and Sentry configuration. +* Mixin for easier implementation of ordering in Django's generic ListView. * JS/CSS compressors for `Django Compressor `_. diff --git a/tests/test_importing.py b/tests/test_importing.py index 9e2d3e2..7ebaed6 100644 --- a/tests/test_importing.py +++ b/tests/test_importing.py @@ -13,6 +13,7 @@ def test_imports(): from tg_utils import files from tg_utils import hashmodels from tg_utils import managers + from tg_utils import mixins from tg_utils import models from tg_utils import profiling from tg_utils import signals diff --git a/tg_utils/mixins.py b/tg_utils/mixins.py new file mode 100644 index 0000000..5ab3221 --- /dev/null +++ b/tg_utils/mixins.py @@ -0,0 +1,58 @@ +import re + +from django.utils.html import escape +from django.utils.safestring import mark_safe + + +class OrderingOptionsMixin: + """ + Mixin for easier implementation of ordering in Django's generic ListView. + + Add `ordering_options` to your subclass and you should be ready to go. + Note that the user-passed query parameters are validated and you should have ascending as well as descending (with a + starting minus-sign (-)) listed in the ordering_options. + + Use `{{ view.ordering_query_ }}` in the template to get the corresponding parameters for a header link. + """ + ordering_query_param = 'order' + ordering_options = () + discard_params = ('page',) + + @staticmethod + def ordering_to_str(ordering: tuple): + return mark_safe(','.join(map(escape, ordering))) + + def get_ordering(self): + requested_ordering = self.request.GET.get(self.ordering_query_param) + if requested_ordering is not None: + requested_ordering = tuple(o for o in requested_ordering.split(',')) + if all(map(lambda o: o in self.ordering_options, requested_ordering)): + return requested_ordering + return super().get_ordering() + + def __getitem__(self, item): + current_ordering = tuple(self.get_ordering()) + if item == 'ordering_query_current': + if current_ordering is not None: + return self.ordering_to_str(current_ordering) + return '' + + match = re.search('^ordering_query_([_a-zA-Z]+)$', item) + if match is not None: + ordering = match.group(1) + if ordering in self.ordering_options: + if current_ordering is None or not current_ordering: + resulting_ordering = ordering + else: + reverse = ordering == current_ordering[0] + ordering_possibilities = (ordering, '-{}'.format(ordering)) + filtered_ordering = tuple(co for co in current_ordering if co not in ordering_possibilities) + resulting_ordering = (ordering if not reverse else '-{}'.format(ordering),) + filtered_ordering + query_params = self.request.GET.copy() + for param in self.discard_params: + if param in query_params: + del query_params[param] + query_params[self.ordering_query_param] = self.ordering_to_str(resulting_ordering) + return query_params.urlencode() + + return super()[item]