summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--django/contrib/admin/filters.py3
-rw-r--r--django/contrib/admin/utils.py4
-rw-r--r--docs/ref/contrib/admin/filters.txt19
-rw-r--r--docs/releases/4.1.txt4
-rw-r--r--tests/admin_filters/tests.py46
5 files changed, 72 insertions, 4 deletions
diff --git a/django/contrib/admin/filters.py b/django/contrib/admin/filters.py
index 82dabe4d2c..f6833e57cb 100644
--- a/django/contrib/admin/filters.py
+++ b/django/contrib/admin/filters.py
@@ -118,6 +118,7 @@ class SimpleListFilter(ListFilter):
class FieldListFilter(ListFilter):
_field_list_filters = []
_take_priority_index = 0
+ list_separator = ','
def __init__(self, field, request, params, model, model_admin, field_path):
self.field = field
@@ -127,7 +128,7 @@ class FieldListFilter(ListFilter):
for p in self.expected_parameters():
if p in params:
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):
return True
diff --git a/django/contrib/admin/utils.py b/django/contrib/admin/utils.py
index baaaa9e43f..42d0d96ea6 100644
--- a/django/contrib/admin/utils.py
+++ b/django/contrib/admin/utils.py
@@ -51,13 +51,13 @@ def lookup_spawns_duplicates(opts, lookup_path):
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.
"""
# if key ends with __in, split parameter into separate values
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'
elif key.endswith('__isnull'):
value = value.lower() not in ('', 'false', '0')
diff --git a/docs/ref/contrib/admin/filters.txt b/docs/ref/contrib/admin/filters.txt
index f78005936d..0dc119af3f 100644
--- a/docs/ref/contrib/admin/filters.txt
+++ b/docs/ref/contrib/admin/filters.txt
@@ -176,6 +176,25 @@ allows to store::
('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::
The :class:`~django.contrib.contenttypes.fields.GenericForeignKey` field is
diff --git a/docs/releases/4.1.txt b/docs/releases/4.1.txt
index 543c9caf6e..c55e638695 100644
--- a/docs/releases/4.1.txt
+++ b/docs/releases/4.1.txt
@@ -54,6 +54,10 @@ Minor features
* The admin :ref:`dark mode CSS variables <admin-theming>` are now applied in a
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`
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
diff --git a/tests/admin_filters/tests.py b/tests/admin_filters/tests.py
index 17aa54a6f6..4f1ff26048 100644
--- a/tests/admin_filters/tests.py
+++ b/tests/admin_filters/tests.py
@@ -4,7 +4,8 @@ import unittest
from django.contrib.admin import (
AllValuesFieldListFilter, BooleanFieldListFilter, EmptyFieldListFilter,
- ModelAdmin, RelatedOnlyFieldListFilter, SimpleListFilter, site,
+ FieldListFilter, ModelAdmin, RelatedOnlyFieldListFilter, SimpleListFilter,
+ site,
)
from django.contrib.admin.options import IncorrectLookupParameters
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"),)
+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):
list_filter = ('books_authored', 'books_contributed')
@@ -231,6 +243,12 @@ class EmployeeAdmin(ModelAdmin):
list_filter = ['department']
+class EmployeeCustomDividerFilterAdmin(EmployeeAdmin):
+ list_filter = [
+ ('name', EmployeeNameCustomDividerFilter),
+ ]
+
+
class DepartmentFilterEmployeeAdmin(EmployeeAdmin):
list_filter = [DepartmentListFilterLookupWithNonStringValue]
@@ -1547,3 +1565,29 @@ class ListFiltersTests(TestCase):
request.user = self.alfred
with self.assertRaises(IncorrectLookupParameters):
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])