Fixed #19721 -- Allowed admin filters to customize the list separator.
This commit is contained in:
parent
2b76f45749
commit
8a4e506760
|
@ -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
|
||||||
|
|
|
@ -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')
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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`
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
|
|
@ -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])
|
||||||
|
|
Loading…
Reference in New Issue