From b1525a542c5580685961cbae1fc3e137f36545ae Mon Sep 17 00:00:00 2001 From: Eric Lipe Date: Wed, 21 Aug 2024 09:55:17 -0400 Subject: [PATCH] - Move multiselect to filters file - REmove old multi select --- django508/tests/admin/dummies.py | 2 +- django508/tests/admin/filters.py | 162 +++++++++--------- .../tests/admin/multi_select_dropdown.py | 93 ---------- .../templates/multiselectlistfilter.html | 24 --- 4 files changed, 86 insertions(+), 195 deletions(-) delete mode 100644 django508/tests/admin/multi_select_dropdown.py delete mode 100644 django508/tests/templates/multiselectlistfilter.html diff --git a/django508/tests/admin/dummies.py b/django508/tests/admin/dummies.py index 2d88e07..194fa92 100644 --- a/django508/tests/admin/dummies.py +++ b/django508/tests/admin/dummies.py @@ -1,5 +1,5 @@ from django.contrib import admin -from .multi_select_dropdown import FieldListMultiSelectFilter +from .filters import FieldListMultiSelectFilter class ReadOnlyDummyMixin(admin.ModelAdmin): diff --git a/django508/tests/admin/filters.py b/django508/tests/admin/filters.py index a86c3f6..fc5c7bc 100644 --- a/django508/tests/admin/filters.py +++ b/django508/tests/admin/filters.py @@ -1,85 +1,93 @@ -"""Filter classes.""" -from django.utils.translation import ugettext_lazy as _ -from django.contrib.admin import SimpleListFilter -from ..models import DummyModel - - -class MultipleChoiceListFilter(SimpleListFilter): - """Filter class allowing multiple filter options.""" - - template = "multiselectlistfilter.html" - - def lookups(self, request, model_admin): - """Must be overridden to return a list of tuples (value, verbose value).""" - raise NotImplementedError( - "The MultipleChoiceListFilter.lookups() method must be overridden to " - "return a list of tuples (value, verbose value)." - ) +"""File containing filter classes and mixins.""" +import urllib.parse +from django.contrib import admin +from django.db.models import Q +from django.utils.translation import gettext_lazy as _ +from django.contrib.admin.utils import reverse_field_path +from django.core.exceptions import ValidationError +from django.contrib.admin.options import IncorrectLookupParameters + +class MultiSelectMixin: + """Mixin for multi-select filters.""" def queryset(self, request, queryset): - """Return queryset based on selected parameters.""" - if request.GET.get(self.parameter_name): - kwargs = {self.parameter_name: request.GET[self.parameter_name].split(",")} - queryset = queryset.filter(**kwargs) - return queryset - - def value_as_list(self): - """Convert multiple filter fields to list.""" - return self.value().split(",") if self.value() else [] + """Build queryset based on choices.""" + params = Q() + for lookup_arg, value in self.used_parameters.items(): + params |= Q(**{lookup_arg: value}) + try: + return queryset.filter(params) + except (ValueError, ValidationError) as e: + # Fields may raise a ValueError or ValidationError when converting + # the parameters to the correct type. + raise IncorrectLookupParameters(e) + + def prepare_querystring_value(self, value): + """Preparse the query string value.""" + # mask all commas or these values will be used + # in a comma-seperated-list as get-parameter + return str(value).replace(',', '%~') + + +class FieldListMultiSelectFilter(MultiSelectMixin, admin.AllValuesFieldListFilter): + """Multi select dropdown filter for all kind of fields.""" + + template = 'multiselectdropdownfilter.html' + + def __init__(self, field, request, params, model, model_admin, field_path): + self.lookup_kwarg = '%s__in' % field_path + self.lookup_kwarg_isnull = '%s__isnull' % field_path + lookup_vals = request.GET.get(self.lookup_kwarg) + self.lookup_vals = lookup_vals.split(',') if lookup_vals else list() + self.lookup_val_isnull = request.GET.get(self.lookup_kwarg_isnull) + self.empty_value_display = model_admin.get_empty_value_display() + parent_model, reverse_path = reverse_field_path(model, field_path) + # Obey parent ModelAdmin queryset when deciding which options to show + if model == parent_model: + queryset = model_admin.get_queryset(request) + else: + queryset = parent_model._default_manager.all() + self.lookup_choices = (queryset + .distinct() + .order_by(field.name) + .values_list(field.name, flat=True)) + super(admin.AllValuesFieldListFilter, self).__init__(field, request, params, model, model_admin, field_path) def choices(self, changelist): - """Overriden choices method.""" - - def amend_query_string(include=None, exclude=None): - selections = self.value_as_list() - if include and include not in selections: - selections.append(include) - if exclude and exclude in selections: - selections.remove(exclude) - if selections: - csv = ",".join(selections) - return changelist.get_query_string({self.parameter_name: csv}) - else: - return changelist.get_query_string(remove=[self.parameter_name]) - + """Generate choices.""" + add_facets = getattr(changelist, "add_facets", False) + facet_counts = self.get_facet_queryset(changelist) if add_facets else None + query_string = changelist.get_query_string({}, [self.lookup_kwarg, self.lookup_kwarg_isnull]) yield { - "selected": self.value() is None, - "query_string": changelist.get_query_string(remove=[self.parameter_name]), - "display": "All", - "reset": True, + 'selected': not self.lookup_vals and self.lookup_val_isnull is None, + 'query_string': query_string, + 'display': _('All'), } - for lookup, title in self.lookup_choices: + include_none = False + count = None + empty_title = self.empty_value_display + for i, val in enumerate(self.lookup_choices): + if add_facets: + count = facet_counts[f"{i}__c"] + if val is None: + include_none = True + empty_title = f"{empty_title} ({count})" if add_facets else empty_title + continue + + val = str(val) + qval = self.prepare_querystring_value(val) yield { - "selected": str(lookup) in self.value_as_list(), - "query_string": changelist.get_query_string( - {self.parameter_name: lookup} - ), - "include_query_string": amend_query_string(include=str(lookup)), - "exclude_query_string": amend_query_string(exclude=str(lookup)), - "display": title, + 'selected': qval in self.lookup_vals, + 'query_string': query_string, + "display": f"{val} ({count})" if add_facets else val, + 'value': urllib.parse.quote_plus(val), + 'key': self.lookup_kwarg, + } + if include_none: + yield { + 'selected': bool(self.lookup_val_isnull), + 'query_string': query_string, + "display": empty_title, + 'value': 'True', + 'key': self.lookup_kwarg_isnull, } - - -class DummyModelNameFilter(MultipleChoiceListFilter): - """Simple filter class to show records based on stt.""" - - title = _("Name") - - parameter_name = "name" - - def lookups(self, request, model_admin): - """Available options in dropdown.""" - options = list() - objs = DummyModel.objects.all() - for obj in objs: - name = obj.name - options.append((name, name)) - - return options - - def queryset(self, request, queryset): - """Return queryset of records based on stt code(s).""" - if self.value() is not None and queryset.exists(): - names = self.value().split(",") - queryset = queryset.filter(name__in=names) - return queryset diff --git a/django508/tests/admin/multi_select_dropdown.py b/django508/tests/admin/multi_select_dropdown.py deleted file mode 100644 index 9f2f8a5..0000000 --- a/django508/tests/admin/multi_select_dropdown.py +++ /dev/null @@ -1,93 +0,0 @@ -"""File containing multiselect filter classes and mixins.""" -import urllib.parse -from django.contrib import admin -from django.db.models import Q -from django.utils.translation import gettext_lazy as _ -from django.contrib.admin.utils import reverse_field_path -from django.core.exceptions import ValidationError -from django.contrib.admin.options import IncorrectLookupParameters - -class MultiSelectMixin(object): - """Mixin for multi-select filters.""" - - def queryset(self, request, queryset): - """Build queryset based on choices.""" - params = Q() - for lookup_arg, value in self.used_parameters.items(): - params |= Q(**{lookup_arg: value}) - try: - return queryset.filter(params) - except (ValueError, ValidationError) as e: - # Fields may raise a ValueError or ValidationError when converting - # the parameters to the correct type. - raise IncorrectLookupParameters(e) - - def prepare_querystring_value(self, value): - """Preparse the query string value.""" - # mask all commas or these values will be used - # in a comma-seperated-list as get-parameter - return str(value).replace(',', '%~') - - -class FieldListMultiSelectFilter(MultiSelectMixin, admin.AllValuesFieldListFilter): - """Multi select dropdown filter for all kind of fields.""" - - template = 'multiselectdropdownfilter.html' - - def __init__(self, field, request, params, model, model_admin, field_path): - self.lookup_kwarg = '%s__in' % field_path - self.lookup_kwarg_isnull = '%s__isnull' % field_path - lookup_vals = request.GET.get(self.lookup_kwarg) - self.lookup_vals = lookup_vals.split(',') if lookup_vals else list() - self.lookup_val_isnull = request.GET.get(self.lookup_kwarg_isnull) - self.empty_value_display = model_admin.get_empty_value_display() - parent_model, reverse_path = reverse_field_path(model, field_path) - # Obey parent ModelAdmin queryset when deciding which options to show - if model == parent_model: - queryset = model_admin.get_queryset(request) - else: - queryset = parent_model._default_manager.all() - self.lookup_choices = (queryset - .distinct() - .order_by(field.name) - .values_list(field.name, flat=True)) - super(admin.AllValuesFieldListFilter, self).__init__(field, request, params, model, model_admin, field_path) - - def choices(self, changelist): - """Generate choices.""" - add_facets = getattr(changelist, "add_facets", False) - facet_counts = self.get_facet_queryset(changelist) if add_facets else None - query_string = changelist.get_query_string({}, [self.lookup_kwarg, self.lookup_kwarg_isnull]) - yield { - 'selected': not self.lookup_vals and self.lookup_val_isnull is None, - 'query_string': query_string, - 'display': _('All'), - } - include_none = False - count = None - empty_title = self.empty_value_display - for i, val in enumerate(self.lookup_choices): - if add_facets: - count = facet_counts[f"{i}__c"] - if val is None: - include_none = True - empty_title = f"{empty_title} ({count})" if add_facets else empty_title - continue - - val = str(val) - qval = self.prepare_querystring_value(val) - yield { - 'selected': qval in self.lookup_vals, - 'query_string': query_string, - "display": f"{val} ({count})" if add_facets else val, - 'value': urllib.parse.quote_plus(val), - 'key': self.lookup_kwarg, - } - if include_none: - yield { - 'selected': bool(self.lookup_val_isnull), - 'query_string': query_string, - "display": empty_title, - 'value': 'True', - 'key': self.lookup_kwarg_isnull, - } diff --git a/django508/tests/templates/multiselectlistfilter.html b/django508/tests/templates/multiselectlistfilter.html deleted file mode 100644 index d6b4a45..0000000 --- a/django508/tests/templates/multiselectlistfilter.html +++ /dev/null @@ -1,24 +0,0 @@ -{% load i18n %} -

{% blocktrans with filter_title=title %} By {{ filter_title }} {% endblocktrans %}

- \ No newline at end of file