Last active
January 13, 2021 08:20
-
-
Save thomst/3f9028ec99e70e8382e06f0f24999870 to your computer and use it in GitHub Desktop.
Multi-Select-Filter for django 1.11
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# -*- coding: utf-8 -*- | |
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.contrib.admin.utils import get_model_from_relation | |
from django.core.exceptions import ValidationError | |
from django.contrib.admin.options import IncorrectLookupParameters | |
class MultiSelectMixin(object): | |
def queryset(self, request, queryset): | |
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 querystring_for_choices(self, val, changelist): | |
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): | |
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 | |
class MultiSelectFieldListFilter(MultiSelectMixin, admin.AllValuesFieldListFilter): | |
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) | |
self.used_parameters = self.prepare_used_parameters(self.used_parameters) | |
def prepare_querystring_value(self, 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): | |
# remove comma-mask from list-values for __in-lookups | |
for key, value in used_parameters.iteritems(): | |
if not key.endswith('__in'): continue | |
used_parameters[key] = [v.replace('%~', ',') for v in value] | |
return used_parameters | |
def choices(self, changelist): | |
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 | |
for val in self.lookup_choices: | |
if val is None: | |
include_none = True | |
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': val, | |
} | |
if include_none: | |
yield { | |
'selected': bool(self.lookup_val_isnull), | |
'query_string': self.querystring_for_isnull(changelist), | |
'display': self.empty_value_display, | |
} | |
class MultiSelectRelatedFieldListFilter(MultiSelectMixin, admin.RelatedFieldListFilter): | |
def __init__(self, field, request, params, model, model_admin, field_path): | |
other_model = get_model_from_relation(field) | |
self.lookup_kwarg = '%s__%s__in' % (field_path, field.target_field.name) | |
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) | |
super(admin.RelatedFieldListFilter, self).__init__(field, request, params, model, model_admin, field_path) | |
self.lookup_choices = self.field_choices(field, request, model_admin) | |
if hasattr(field, 'verbose_name'): | |
self.lookup_title = field.verbose_name | |
else: | |
self.lookup_title = other_model._meta.verbose_name | |
self.title = self.lookup_title | |
self.empty_value_display = model_admin.get_empty_value_display() | |
def choices(self, changelist): | |
yield { | |
'selected': not self.lookup_vals and not self.lookup_val_isnull, | |
'query_string': changelist.get_query_string( | |
{}, | |
[self.lookup_kwarg, self.lookup_kwarg_isnull] | |
), | |
'display': _('All'), | |
} | |
for pk_val, val in self.lookup_choices: | |
pk_val = str(pk_val) | |
yield { | |
'selected': pk_val in self.lookup_vals, | |
'query_string': self.querystring_for_choices(pk_val, changelist), | |
'display': val, | |
} | |
if self.include_empty_choice: | |
yield { | |
'selected': bool(self.lookup_val_isnull), | |
'query_string': self.querystring_for_isnull(changelist), | |
'display': self.empty_value_display, | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment