diff options
| author | Sarah Boyce <42296566+sarahboyce@users.noreply.github.com> | 2024-11-04 16:09:55 +0100 |
|---|---|---|
| committer | Sarah Boyce <42296566+sarahboyce@users.noreply.github.com> | 2024-11-05 09:06:14 +0100 |
| commit | 5fa4ccab7e42e86fa4a0681d21bd1326c9c5eac3 (patch) | |
| tree | ba4a1d65c8b3e47e5c7c02971851a64b63a44c13 /django/contrib/admin/options.py | |
| parent | 5bd58058116d2e6f07b72881218548fd9904167e (diff) | |
Refs #26001 -- Handled relationship exact lookups in ModelAdmin.search_fields.
Diffstat (limited to 'django/contrib/admin/options.py')
| -rw-r--r-- | django/contrib/admin/options.py | 53 |
1 files changed, 24 insertions, 29 deletions
diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py index 78063a134d..69b0cc0373 100644 --- a/django/contrib/admin/options.py +++ b/django/contrib/admin/options.py @@ -1178,17 +1178,17 @@ class ModelAdmin(BaseModelAdmin): # Apply keyword searches. def construct_search(field_name): if field_name.startswith("^"): - return "%s__istartswith" % field_name.removeprefix("^") + return "%s__istartswith" % field_name.removeprefix("^"), None elif field_name.startswith("="): - return "%s__iexact" % field_name.removeprefix("=") + return "%s__iexact" % field_name.removeprefix("="), None elif field_name.startswith("@"): - return "%s__search" % field_name.removeprefix("@") + return "%s__search" % field_name.removeprefix("@"), None # Use field_name if it includes a lookup. opts = queryset.model._meta lookup_fields = field_name.split(LOOKUP_SEP) # Go through the fields, following all relations. prev_field = None - for path_part in lookup_fields: + for i, path_part in enumerate(lookup_fields): if path_part == "pk": path_part = opts.pk.name try: @@ -1196,44 +1196,39 @@ class ModelAdmin(BaseModelAdmin): except FieldDoesNotExist: # Use valid query lookups. if prev_field and prev_field.get_lookup(path_part): - return field_name + if path_part == "exact" and not isinstance( + prev_field, (models.CharField, models.TextField) + ): + field_name_without_exact = "__".join(lookup_fields[:i]) + alias = Cast( + field_name_without_exact, + output_field=models.CharField(), + ) + alias_name = "_".join(lookup_fields[:i]) + return f"{alias_name}_str", alias + else: + return field_name, None else: prev_field = field if hasattr(field, "path_infos"): # Update opts to follow the relation. opts = field.path_infos[-1].to_opts # Otherwise, use the field with icontains. - return "%s__icontains" % field_name + return "%s__icontains" % field_name, None may_have_duplicates = False search_fields = self.get_search_fields(request) if search_fields and search_term: - str_annotations = {} + str_aliases = {} 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) + lookup, str_alias = construct_search(str(field)) + orm_lookups.append(lookup) + if str_alias: + str_aliases[lookup] = str_alias - if str_annotations: - queryset = queryset.annotate(**str_annotations) + if str_aliases: + queryset = queryset.alias(**str_aliases) term_queries = [] for bit in smart_split(search_term): |
