diff options
| author | 0saurabh0 <saurabhrai1717@gmail.com> | 2024-11-01 08:26:10 +0100 |
|---|---|---|
| committer | Sarah Boyce <42296566+sarahboyce@users.noreply.github.com> | 2024-11-04 09:39:47 +0100 |
| commit | f223729f8f8380901473b244c0fc7c7dc397e3f1 (patch) | |
| tree | ed434b9e50481d160a9804aaeb1fe8a7784d49cb | |
| parent | cd4497254e8b52e7d28dda764b6a2567253059f4 (diff) | |
Fixed #26001 -- Fixed non-string field exact lookups in ModelAdmin.search_fields.
| -rw-r--r-- | django/contrib/admin/options.py | 31 | ||||
| -rw-r--r-- | tests/admin_changelist/admin.py | 1 | ||||
| -rw-r--r-- | tests/admin_changelist/tests.py | 19 |
3 files changed, 48 insertions, 3 deletions
diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py index 6d5c0708a3..78063a134d 100644 --- a/django/contrib/admin/options.py +++ b/django/contrib/admin/options.py @@ -41,6 +41,7 @@ from django.core.exceptions import ( from django.core.paginator import Paginator from django.db import models, router, transaction from django.db.models.constants import LOOKUP_SEP +from django.db.models.functions import Cast from django.forms.formsets import DELETION_FIELD_NAME, all_valid from django.forms.models import ( BaseInlineFormSet, @@ -1207,9 +1208,33 @@ class ModelAdmin(BaseModelAdmin): may_have_duplicates = False search_fields = self.get_search_fields(request) if search_fields and search_term: - orm_lookups = [ - construct_search(str(search_field)) for search_field in search_fields - ] + str_annotations = {} + orm_lookups = [] + for field in search_fields: + if field.endswith("__exact"): + field_name = field.rsplit("__exact", 1)[0] + try: + field_obj = queryset.model._meta.get_field(field_name) + except FieldDoesNotExist: + lookup = construct_search(field) + orm_lookups.append(lookup) + continue + # Add string cast annotations for non-string exact lookups. + if not isinstance(field_obj, (models.CharField, models.TextField)): + str_annotations[f"{field_name}_str"] = Cast( + field_name, output_field=models.CharField() + ) + orm_lookups.append(f"{field_name}_str__exact") + else: + lookup = construct_search(field) + orm_lookups.append(lookup) + else: + lookup = construct_search(str(field)) + orm_lookups.append(lookup) + + if str_annotations: + queryset = queryset.annotate(**str_annotations) + term_queries = [] for bit in smart_split(search_term): if bit.startswith(('"', "'")) and bit[0] == bit[-1]: diff --git a/tests/admin_changelist/admin.py b/tests/admin_changelist/admin.py index 349ef7d465..d9dc498e84 100644 --- a/tests/admin_changelist/admin.py +++ b/tests/admin_changelist/admin.py @@ -48,6 +48,7 @@ class ChildAdmin(admin.ModelAdmin): list_display = ["name", "parent"] list_per_page = 10 list_filter = ["parent", "age"] + search_fields = ["age__exact", "name__exact"] def get_queryset(self, request): return super().get_queryset(request).select_related("parent") diff --git a/tests/admin_changelist/tests.py b/tests/admin_changelist/tests.py index d8055a809b..a823a72f7d 100644 --- a/tests/admin_changelist/tests.py +++ b/tests/admin_changelist/tests.py @@ -860,6 +860,25 @@ class ChangeListTests(TestCase): cl = m.get_changelist_instance(request) self.assertCountEqual(cl.queryset, [abcd]) + def test_search_with_exact_lookup_for_non_string_field(self): + child = Child.objects.create(name="Asher", age=11) + model_admin = ChildAdmin(Child, custom_site) + + for search_term, expected_result in [ + ("11", [child]), + ("Asher", [child]), + ("1", []), + ("A", []), + ("random", []), + ]: + request = self.factory.get("/", data={SEARCH_VAR: search_term}) + request.user = self.superuser + with self.subTest(search_term=search_term): + # 1 query for filtered result, 1 for filtered count, 1 for total count. + with self.assertNumQueries(3): + cl = model_admin.get_changelist_instance(request) + self.assertCountEqual(cl.queryset, expected_result) + def test_no_distinct_for_m2m_in_list_filter_without_params(self): """ If a ManyToManyField is in list_filter but isn't in any lookup params, |
