From 4bdc0eee4d9965af7f113af36fa91fa8325fe558 Mon Sep 17 00:00:00 2001 From: Eric Lipe Date: Wed, 21 Aug 2024 09:52:10 -0400 Subject: [PATCH] - Minimal multiselect field filter --- django508/tests/admin/dummies.py | 7 +- .../tests/admin/multi_select_dropdown.py | 192 +----------------- 2 files changed, 12 insertions(+), 187 deletions(-) diff --git a/django508/tests/admin/dummies.py b/django508/tests/admin/dummies.py index afb23ac..2d88e07 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 MultiSelectDropdownFilter +from .multi_select_dropdown import FieldListMultiSelectFilter class ReadOnlyDummyMixin(admin.ModelAdmin): @@ -36,7 +36,8 @@ class Dummy_Admin(ReadOnlyDummyMixin): ] list_filter = [ - ('name', MultiSelectDropdownFilter), + ('name', FieldListMultiSelectFilter), + ('description', FieldListMultiSelectFilter), "created" ] @@ -52,7 +53,7 @@ class Dummy2_Admin(ReadOnlyDummyMixin): ] list_filter = [ - ('name', MultiSelectDropdownFilter), + ('name', FieldListMultiSelectFilter), "created" ] diff --git a/django508/tests/admin/multi_select_dropdown.py b/django508/tests/admin/multi_select_dropdown.py index f953d61..9f2f8a5 100644 --- a/django508/tests/admin/multi_select_dropdown.py +++ b/django508/tests/admin/multi_select_dropdown.py @@ -7,15 +7,6 @@ from django.core.exceptions import ValidationError from django.contrib.admin.options import IncorrectLookupParameters - -def flatten_used_parameters(used_parameters: dict, keep_list: bool = True): - """Flatten length 1 lists in dictionary.""" - # FieldListFilter.__init__ calls prepare_lookup_value, - # which returns a list if lookup_kwarg ends with "__in" - for k, v in used_parameters.items(): - if len(v) == 1 and (isinstance(v[0], list) or not keep_list): - used_parameters[k] = v[0] - class MultiSelectMixin(object): """Mixin for multi-select filters.""" @@ -31,50 +22,17 @@ def queryset(self, request, queryset): # the parameters to the correct type. raise IncorrectLookupParameters(e) - def querystring_for_choices(self, val, changelist): - """Build query string based on new val.""" - lookup_vals = self.lookup_vals[:] - if val in self.lookup_vals: - lookup_vals.remove(val) - else: - lookup_vals.append(val) - if lookup_vals: - query_string = changelist.get_query_string({ - self.lookup_kwarg: ','.join(lookup_vals), - }, []) - else: - query_string = changelist.get_query_string({}, [self.lookup_kwarg]) - return query_string - - def querystring_for_isnull(self, changelist): - """Build query string based on a null val.""" - if self.lookup_val_isnull: - query_string = changelist.get_query_string({}, [self.lookup_kwarg_isnull]) - else: - query_string = changelist.get_query_string({ - self.lookup_kwarg_isnull: 'True', - }, []) - return query_string - - def has_output(self): - """Return if there is output.""" - return len(self.lookup_choices) > 1 - - def get_facet_counts(self, pk_attname, filtered_qs): - """Return count of __in facets.""" - if not self.lookup_kwarg.endswith("__in"): - raise NotImplementedError("Facets are only supported for default lookup_kwarg values, ending with '__in' " - "(got '%s')" % self.lookup_kwarg) + 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(',', '%~') - orig_lookup_kwarg = self.lookup_kwarg - self.lookup_kwarg = self.lookup_kwarg.removesuffix("in") + "exact" - counts = super().get_facet_counts(pk_attname, filtered_qs) - self.lookup_kwarg = orig_lookup_kwarg - return counts +class FieldListMultiSelectFilter(MultiSelectMixin, admin.AllValuesFieldListFilter): + """Multi select dropdown filter for all kind of fields.""" -class MultiSelectFilter(MultiSelectMixin, admin.AllValuesFieldListFilter): - """Multi select 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 @@ -94,62 +52,6 @@ def __init__(self, field, request, params, model, model_admin, field_path): .order_by(field.name) .values_list(field.name, flat=True)) super(admin.AllValuesFieldListFilter, self).__init__(field, request, params, model, model_admin, field_path) - flatten_used_parameters(self.used_parameters) - self.used_parameters = self.prepare_used_parameters(self.used_parameters) - - 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(',', '%~') - - def prepare_used_parameters(self, used_parameters): - """Prepare parameters.""" - # remove comma-mask from list-values for __in-lookups - for key, value in used_parameters.items(): - if not key.endswith('__in'): - continue - used_parameters[key] = [v.replace('%~', ',') for v in value] - return used_parameters - - 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 - yield { - 'selected': not self.lookup_vals and self.lookup_val_isnull is None, - 'query_string': changelist.get_query_string({}, [self.lookup_kwarg, self.lookup_kwarg_isnull]), - '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': self.querystring_for_choices(qval, changelist), - "display": f"{val} ({count})" if add_facets else val, - } - if include_none: - yield { - 'selected': bool(self.lookup_val_isnull), - 'query_string': self.querystring_for_isnull(changelist), - 'display': empty_title, - } - - -class MultiSelectDropdownFilter(MultiSelectFilter): - """Multi select dropdown filter for all kind of fields.""" - - template = 'multiselectdropdownfilter.html' def choices(self, changelist): """Generate choices.""" @@ -189,81 +91,3 @@ def choices(self, changelist): 'value': 'True', 'key': self.lookup_kwarg_isnull, } - - - - - - - -class NewMultiSelectFilter(admin.AllValuesFieldListFilter): - """Multi select filter for all kind of fields.""" - - 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) - flatten_used_parameters(self.used_parameters) - self.used_parameters = self.prepare_used_parameters(self.used_parameters) - - 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(',', '%~') - - def prepare_used_parameters(self, used_parameters): - """Prepare parameters.""" - # remove comma-mask from list-values for __in-lookups - for key, value in used_parameters.items(): - if not key.endswith('__in'): - continue - used_parameters[key] = [v.replace('%~', ',') for v in value] - return used_parameters - - 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 - yield { - 'selected': not self.lookup_vals and self.lookup_val_isnull is None, - 'query_string': changelist.get_query_string({}, [self.lookup_kwarg, self.lookup_kwarg_isnull]), - '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': self.querystring_for_choices(qval, changelist), - "display": f"{val} ({count})" if add_facets else val, - } - if include_none: - yield { - 'selected': bool(self.lookup_val_isnull), - 'query_string': self.querystring_for_isnull(changelist), - 'display': empty_title, - }