Repository: django/django
File: django/contrib/admin/options.py (Python)

Code before:
                return "%s__iexact" % field_name.removeprefix("="), None
            elif field_name.startswith("@"):
                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 i, path_part in enumerate(lookup_fields):
                if path_part == "pk":
                    path_part = opts.pk.name
                try:
                    field = opts.get_field(path_part)
                except FieldDoesNotExist:
                    # Use valid query lookups.
                    if prev_field and prev_field.get_lookup(path_part):
                        if path_part == "exact" and not isinstance(
                            prev_field, (models.CharField, models.TextField)
                        ):
                            # Skip Cast if search_term is valid for this field
                            # to preserve index usage.
                            try:
                                prev_field.to_python(search_term)
                                return field_name, None
                            except ValidationError:
                                pass
                            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, None

        may_have_duplicates = False
        search_fields = self.get_search_fields(request)
        if search_fields and search_term:
            str_aliases = {}
            orm_lookups = []
            for field in search_fields:
                lookup, str_alias = construct_search(str(field))
                orm_lookups.append(lookup)

Review comment (suggestion):
This doesn't account for multiple search terms (see `smart_split(search_term)` below) where some might need cast and others don't.

For example, if your search for `foo 123` the resulting query should be

```python
alias(
    int_field_str=Cast("int_field", TextField())
).filter(
    Q(int_field_str="foo") | Q(int_field="123")
)
```

with your current path it'll be doing

```python
alias(
    int_field_str=Cast("int_field", TextField())
).filter(
    Q(int_field_str="foo") | Q(int_field_str=123)
)
```

See how #18765 changed the logic to no longer be search term `bit` specific and how we might need to restore it.

Code after:

            field_to_validate is set for non-text exact lookups so that
            invalid search terms can be skipped (preserving index usage).
            """
            if field_name.startswith("^"):
                return "%s__istartswith" % field_name.removeprefix("^"), None
            elif field_name.startswith("="):
                return "%s__iexact" % field_name.removeprefix("="), None
            elif field_name.startswith("@"):
                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:
                if path_part == "pk":
                    path_part = opts.pk.name
                try:
                    field = opts.get_field(path_part)
                except FieldDoesNotExist:
                    # Use valid query lookups.
                    if prev_field and prev_field.get_lookup(path_part):
                        if path_part == "exact" and not isinstance(
                            prev_field, (models.CharField, models.TextField)
                        ):
                            # Use prev_field to validate the search term.
                            return field_name, prev_field
                        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, None

        may_have_duplicates = False
        search_fields = self.get_search_fields(request)
        if search_fields and search_term:
            orm_lookups = []
            for field in search_fields:
                orm_lookups.append(construct_search(str(field)))

            term_queries = []
            for bit in smart_split(search_term):
                if bit.startswith(('"', "'")) and bit[0] == bit[-1]:
                    bit = unescape_string_literal(bit)
                # Build term lookups, skipping values invalid for their field.
                bit_lookups = []
                for orm_lookup, validate_field in orm_lookups: