django-multiselectfield
django-multiselectfield copied to clipboard
list_filter behaviour yields empty lists
If we include a MultiSelectField in a model's Admin list_filter then when we click on any of the "filter" links in the list page we get redirect to a URL that uses ?field_name__exact=choice and hence returns no valid results.
However, tampering with the URL and changing it to ?field_name__icontains=choice works as expected.
I've poked around and I believe that we could create our own filter derived from admin.ChoicesFieldListFilter (defined in filters.py), but I'm not sure even where to begin with.
Hi! @ZippoLag, any luck with the filter?
Workaround:
class FieldNameFilter(admin.ChoicesFieldListFilter):
title = 'field_name'
parameter_name = 'field_name'
def __init__(self, field, request, params, model, model_admin, field_path):
super().__init__(field, request, params, model, model_admin, field_path)
self.lookup_kwarg = '%s__icontains' % field_path
Thanks @FedeG for providing this workaround. For the "selected" option to work, I had to override both lookup_kwarg and lookup_val (see below code).
class FieldNameFilter(admin.ChoicesFieldListFilter):
title = 'field_name'
parameter_name = 'field_name'
def __init__(self, field, request, params, model, model_admin, field_path):
super().__init__(field, request, params, model, model_admin, field_path)
self.lookup_kwarg = '%s__icontains' % field_path
self.lookup_val = params.get(self.lookup_kwarg)
Workaround2
Based on the docs multiselect storage a charfield with the options selected separated by commas you can use the endswith to get when the user selects only one option, and the icontains to get when the user choose more than one option.
class MultiSelectFilter(admin.SimpleListFilter):
# Human-readable title which will be displayed in the
# right admin sidebar just above the filter options.
title = param_title
parameter_name = param_name
def lookups(self, request, model_admin):
return ( ('option 1', 'Option 1'),
('option 2', 'Option 2')) # tuple ('lookup_value', 'tittle_value in list filter')
def queryset(self, request, queryset):
if self.value():
return queryset.filter(Q(**{
f'{self.parameter_name}__iendswith': self.value()
}) | Q(**{
f'{self.parameter_name}__icontains': f'{self.value()},'
}))
return queryset
Thanks for the above! Using that I've hacked something together which can be used with list_filter on a admin.ModelAdmin:
def _multiple_choice_filter(field_name):
class FieldNameFilter(admin.ChoicesFieldListFilter):
title = field_name
parameter_name = field_name
def __init__(self, field, request, params, model, model_admin, field_path):
super().__init__(field, request, params, model, model_admin, field_path)
self.lookup_kwarg = "%s__icontains" % field_path
self.lookup_val = params.get(self.lookup_kwarg)
return (field_name, FieldNameFilter)
class ObjectAdmin(admin.ModelAdmin):
list_filter = [_multiple_choice_filter("field_name")]