Skip to content

Instantly share code, notes, and snippets.

@thomst
Last active January 13, 2021 08:20
Show Gist options
  • Save thomst/3f9028ec99e70e8382e06f0f24999870 to your computer and use it in GitHub Desktop.
Save thomst/3f9028ec99e70e8382e06f0f24999870 to your computer and use it in GitHub Desktop.
Multi-Select-Filter for django 1.11
# -*- 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