summaryrefslogtreecommitdiff
path: root/django
diff options
context:
space:
mode:
authorsarahboyce <sarahvboyce95@gmail.com>2023-03-06 15:24:39 +0100
committerMariusz Felisiak <felisiak.mariusz@gmail.com>2023-03-16 08:38:44 +0100
commitd2b688b966f5d30414899549412d370e1317ddb8 (patch)
tree04be2a73653c70c27d9a0bf1baeaa99b86ef8107 /django
parentd03dc63177ad3ba6e685e314eed45d6a8ec5cb0c (diff)
downloaddjango-d2b688b966f5d30414899549412d370e1317ddb8.tar.gz
Fixed #1873 -- Handled multi-valued query parameters in admin changelist filters.
Diffstat (limited to 'django')
-rw-r--r--django/contrib/admin/filters.py18
-rw-r--r--django/contrib/admin/utils.py11
-rw-r--r--django/contrib/admin/views/main.py15
3 files changed, 30 insertions, 14 deletions
diff --git a/django/contrib/admin/filters.py b/django/contrib/admin/filters.py
index 3ae7805dc2..f6f6d0dd6f 100644
--- a/django/contrib/admin/filters.py
+++ b/django/contrib/admin/filters.py
@@ -9,6 +9,7 @@ import datetime
from django.contrib.admin.options import IncorrectLookupParameters
from django.contrib.admin.utils import (
+ build_q_object_from_lookup_parameters,
get_last_value_from_parameters,
get_model_from_relation,
prepare_lookup_value,
@@ -187,7 +188,8 @@ class FieldListFilter(FacetsMixin, ListFilter):
def queryset(self, request, queryset):
try:
- return queryset.filter(**self.used_parameters)
+ q_object = build_q_object_from_lookup_parameters(self.used_parameters)
+ return queryset.filter(q_object)
except (ValueError, ValidationError) as e:
# Fields may raise a ValueError or ValidationError when converting
# the parameters to the correct type.
@@ -220,7 +222,7 @@ class RelatedFieldListFilter(FieldListFilter):
other_model = get_model_from_relation(field)
self.lookup_kwarg = "%s__%s__exact" % (field_path, field.target_field.name)
self.lookup_kwarg_isnull = "%s__isnull" % field_path
- self.lookup_val = get_last_value_from_parameters(params, self.lookup_kwarg)
+ self.lookup_val = params.get(self.lookup_kwarg)
self.lookup_val_isnull = get_last_value_from_parameters(
params, self.lookup_kwarg_isnull
)
@@ -293,7 +295,8 @@ class RelatedFieldListFilter(FieldListFilter):
count = facet_counts[f"{pk_val}__c"]
val = f"{val} ({count})"
yield {
- "selected": self.lookup_val == str(pk_val),
+ "selected": self.lookup_val is not None
+ and str(pk_val) in self.lookup_val,
"query_string": changelist.get_query_string(
{self.lookup_kwarg: pk_val}, [self.lookup_kwarg_isnull]
),
@@ -391,7 +394,7 @@ class ChoicesFieldListFilter(FieldListFilter):
def __init__(self, field, request, params, model, model_admin, field_path):
self.lookup_kwarg = "%s__exact" % field_path
self.lookup_kwarg_isnull = "%s__isnull" % field_path
- self.lookup_val = get_last_value_from_parameters(params, self.lookup_kwarg)
+ self.lookup_val = params.get(self.lookup_kwarg)
self.lookup_val_isnull = get_last_value_from_parameters(
params, self.lookup_kwarg_isnull
)
@@ -432,7 +435,8 @@ class ChoicesFieldListFilter(FieldListFilter):
none_title = title
continue
yield {
- "selected": str(lookup) == self.lookup_val,
+ "selected": self.lookup_val is not None
+ and str(lookup) in self.lookup_val,
"query_string": changelist.get_query_string(
{self.lookup_kwarg: lookup}, [self.lookup_kwarg_isnull]
),
@@ -555,7 +559,7 @@ class AllValuesFieldListFilter(FieldListFilter):
def __init__(self, field, request, params, model, model_admin, field_path):
self.lookup_kwarg = field_path
self.lookup_kwarg_isnull = "%s__isnull" % field_path
- self.lookup_val = get_last_value_from_parameters(params, self.lookup_kwarg)
+ self.lookup_val = params.get(self.lookup_kwarg)
self.lookup_val_isnull = get_last_value_from_parameters(
params, self.lookup_kwarg_isnull
)
@@ -609,7 +613,7 @@ class AllValuesFieldListFilter(FieldListFilter):
continue
val = str(val)
yield {
- "selected": self.lookup_val == val,
+ "selected": self.lookup_val is not None and val in self.lookup_val,
"query_string": changelist.get_query_string(
{self.lookup_kwarg: val}, [self.lookup_kwarg_isnull]
),
diff --git a/django/contrib/admin/utils.py b/django/contrib/admin/utils.py
index 90442788c9..5e6a400b6c 100644
--- a/django/contrib/admin/utils.py
+++ b/django/contrib/admin/utils.py
@@ -2,6 +2,8 @@ import datetime
import decimal
import json
from collections import defaultdict
+from functools import reduce
+from operator import or_
from django.core.exceptions import FieldDoesNotExist
from django.db import models, router
@@ -64,7 +66,7 @@ def prepare_lookup_value(key, value, separator=","):
Return a lookup value prepared to be used in queryset filtering.
"""
if isinstance(value, list):
- value = value[-1]
+ return [prepare_lookup_value(key, v, separator=separator) for v in value]
# if key ends with __in, split parameter into separate values
if key.endswith("__in"):
value = value.split(separator)
@@ -74,6 +76,13 @@ def prepare_lookup_value(key, value, separator=","):
return value
+def build_q_object_from_lookup_parameters(parameters):
+ q_object = models.Q()
+ for param, param_item_list in parameters.items():
+ q_object &= reduce(or_, (models.Q((param, item)) for item in param_item_list))
+ return q_object
+
+
def quote(s):
"""
Ensure that primary key values do not confuse the admin URLs by escaping
diff --git a/django/contrib/admin/views/main.py b/django/contrib/admin/views/main.py
index c0c03819f3..9a130ae8a7 100644
--- a/django/contrib/admin/views/main.py
+++ b/django/contrib/admin/views/main.py
@@ -16,6 +16,7 @@ from django.contrib.admin.options import (
ShowFacets,
)
from django.contrib.admin.utils import (
+ build_q_object_from_lookup_parameters,
get_fields_from_path,
lookup_spawns_duplicates,
prepare_lookup_value,
@@ -173,9 +174,10 @@ class ChangeList:
may_have_duplicates = False
has_active_filters = False
- for key, value in lookup_params.items():
- if not self.model_admin.lookup_allowed(key, value[-1]):
- raise DisallowedModelAdminLookup("Filtering by %s not allowed" % key)
+ for key, value_list in lookup_params.items():
+ for value in value_list:
+ if not self.model_admin.lookup_allowed(key, value):
+ raise DisallowedModelAdminLookup(f"Filtering by {key} not allowed")
filter_specs = []
for list_filter in self.list_filter:
@@ -246,8 +248,8 @@ class ChangeList:
to_date = make_aware(to_date)
lookup_params.update(
{
- "%s__gte" % self.date_hierarchy: from_date,
- "%s__lt" % self.date_hierarchy: to_date,
+ "%s__gte" % self.date_hierarchy: [from_date],
+ "%s__lt" % self.date_hierarchy: [to_date],
}
)
@@ -534,7 +536,8 @@ class ChangeList:
# Finally, we apply the remaining lookup parameters from the query
# string (i.e. those that haven't already been processed by the
# filters).
- qs = qs.filter(**remaining_lookup_params)
+ q_object = build_q_object_from_lookup_parameters(remaining_lookup_params)
+ qs = qs.filter(q_object)
except (SuspiciousOperation, ImproperlyConfigured):
# Allow certain types of errors to be re-raised as-is so that the
# caller can treat them in a special way.