diff options
author | sarahboyce <sarahvboyce95@gmail.com> | 2023-03-06 15:24:39 +0100 |
---|---|---|
committer | Mariusz Felisiak <felisiak.mariusz@gmail.com> | 2023-03-16 08:38:44 +0100 |
commit | d2b688b966f5d30414899549412d370e1317ddb8 (patch) | |
tree | 04be2a73653c70c27d9a0bf1baeaa99b86ef8107 /django | |
parent | d03dc63177ad3ba6e685e314eed45d6a8ec5cb0c (diff) | |
download | django-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.py | 18 | ||||
-rw-r--r-- | django/contrib/admin/utils.py | 11 | ||||
-rw-r--r-- | django/contrib/admin/views/main.py | 15 |
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. |