Fixed #19721 -- Allowed admin filters to customize the list separator.

This commit is contained in:
Shreya Bamne 2021-12-08 15:24:55 +01:00 committed by Carlton Gibson
parent 2b76f45749
commit 8a4e506760
5 changed files with 72 additions and 4 deletions

View File

@ -118,6 +118,7 @@ class SimpleListFilter(ListFilter):
class FieldListFilter(ListFilter): class FieldListFilter(ListFilter):
_field_list_filters = [] _field_list_filters = []
_take_priority_index = 0 _take_priority_index = 0
list_separator = ','
def __init__(self, field, request, params, model, model_admin, field_path): def __init__(self, field, request, params, model, model_admin, field_path):
self.field = field self.field = field
@ -127,7 +128,7 @@ class FieldListFilter(ListFilter):
for p in self.expected_parameters(): for p in self.expected_parameters():
if p in params: if p in params:
value = params.pop(p) value = params.pop(p)
self.used_parameters[p] = prepare_lookup_value(p, value) self.used_parameters[p] = prepare_lookup_value(p, value, self.list_separator)
def has_output(self): def has_output(self):
return True return True

View File

@ -51,13 +51,13 @@ def lookup_spawns_duplicates(opts, lookup_path):
return False return False
def prepare_lookup_value(key, value): def prepare_lookup_value(key, value, separator=','):
""" """
Return a lookup value prepared to be used in queryset filtering. Return a lookup value prepared to be used in queryset filtering.
""" """
# if key ends with __in, split parameter into separate values # if key ends with __in, split parameter into separate values
if key.endswith('__in'): if key.endswith('__in'):
value = value.split(',') value = value.split(separator)
# if key ends with __isnull, special case '' and the string literals 'false' and '0' # if key ends with __isnull, special case '' and the string literals 'false' and '0'
elif key.endswith('__isnull'): elif key.endswith('__isnull'):
value = value.lower() not in ('', 'false', '0') value = value.lower() not in ('', 'false', '0')

View File

@ -176,6 +176,25 @@ allows to store::
('title', admin.EmptyFieldListFilter), ('title', admin.EmptyFieldListFilter),
) )
By defining a filter using the ``__in`` lookup, it is possible to filter for
any of a group of values. You need to override the ``expected_parameters``
method, and the specify the ``lookup_kwargs`` attribute with the appropriate
field name. By default, multiple values in the query string will be separated
with commas, but this can be customized via the ``list_separator`` attribute.
The following example shows such a filter using the vertical-pipe character as
the separator::
class FilterWithCustomSeparator(admin.FieldListFilter):
# custom list separator that should be used to separate values.
list_separator = '|'
def __init__(self, field, request, params, model, model_admin, field_path):
self.lookup_kwarg = '%s__in' % field_path
super().__init__(field, request, params, model, model_admin, field_path)
def expected_parameters(self):
return [self.lookup_kwarg]
.. note:: .. note::
The :class:`~django.contrib.contenttypes.fields.GenericForeignKey` field is The :class:`~django.contrib.contenttypes.fields.GenericForeignKey` field is

View File

@ -54,6 +54,10 @@ Minor features
* The admin :ref:`dark mode CSS variables <admin-theming>` are now applied in a * The admin :ref:`dark mode CSS variables <admin-theming>` are now applied in a
separate stylesheet and template block. separate stylesheet and template block.
* :ref:`modeladmin-list-filters` providing custom ``FieldListFilter``
subclasses can now control the query string value separator when filtering
for multiple values using the ``__in`` lookup.
:mod:`django.contrib.admindocs` :mod:`django.contrib.admindocs`
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -4,7 +4,8 @@ import unittest
from django.contrib.admin import ( from django.contrib.admin import (
AllValuesFieldListFilter, BooleanFieldListFilter, EmptyFieldListFilter, AllValuesFieldListFilter, BooleanFieldListFilter, EmptyFieldListFilter,
ModelAdmin, RelatedOnlyFieldListFilter, SimpleListFilter, site, FieldListFilter, ModelAdmin, RelatedOnlyFieldListFilter, SimpleListFilter,
site,
) )
from django.contrib.admin.options import IncorrectLookupParameters from django.contrib.admin.options import IncorrectLookupParameters
from django.contrib.auth.admin import UserAdmin from django.contrib.auth.admin import UserAdmin
@ -135,6 +136,17 @@ class DepartmentListFilterLookupWithDynamicValue(DecadeListFilterWithTitleAndPar
return (('the 80s', "the 1980's"), ('the 90s', "the 1990's"),) return (('the 80s', "the 1980's"), ('the 90s', "the 1990's"),)
class EmployeeNameCustomDividerFilter(FieldListFilter):
list_separator = '|'
def __init__(self, field, request, params, model, model_admin, field_path):
self.lookup_kwarg = '%s__in' % field_path
super().__init__(field, request, params, model, model_admin, field_path)
def expected_parameters(self):
return [self.lookup_kwarg]
class CustomUserAdmin(UserAdmin): class CustomUserAdmin(UserAdmin):
list_filter = ('books_authored', 'books_contributed') list_filter = ('books_authored', 'books_contributed')
@ -231,6 +243,12 @@ class EmployeeAdmin(ModelAdmin):
list_filter = ['department'] list_filter = ['department']
class EmployeeCustomDividerFilterAdmin(EmployeeAdmin):
list_filter = [
('name', EmployeeNameCustomDividerFilter),
]
class DepartmentFilterEmployeeAdmin(EmployeeAdmin): class DepartmentFilterEmployeeAdmin(EmployeeAdmin):
list_filter = [DepartmentListFilterLookupWithNonStringValue] list_filter = [DepartmentListFilterLookupWithNonStringValue]
@ -1547,3 +1565,29 @@ class ListFiltersTests(TestCase):
request.user = self.alfred request.user = self.alfred
with self.assertRaises(IncorrectLookupParameters): with self.assertRaises(IncorrectLookupParameters):
modeladmin.get_changelist_instance(request) modeladmin.get_changelist_instance(request)
def test_lookup_using_custom_divider(self):
"""
Filter __in lookups with a custom divider.
"""
jane = Employee.objects.create(name='Jane,Green', department=self.design)
modeladmin = EmployeeCustomDividerFilterAdmin(Employee, site)
employees = [jane, self.jack]
request = self.request_factory.get(
'/', {'name__in': "|".join(e.name for e in employees)}
)
# test for lookup with custom divider
request.user = self.alfred
changelist = modeladmin.get_changelist_instance(request)
# Make sure the correct queryset is returned
queryset = changelist.get_queryset(request)
self.assertEqual(list(queryset), employees)
# test for lookup with comma in the lookup string
request = self.request_factory.get('/', {'name': jane.name})
request.user = self.alfred
changelist = modeladmin.get_changelist_instance(request)
# Make sure the correct queryset is returned
queryset = changelist.get_queryset(request)
self.assertEqual(list(queryset), [jane])