in src/olympia/amo/admin.py [0:0]
def get_search_results(self, request, queryset, search_term):
"""
Return a tuple containing a queryset to implement the search,
and a boolean indicating if the results may contain duplicates.
Originally copied from Django's, but with the following differences:
- The operator joining the query parts is dynamic: if the search term
contain a comma and no space, then the comma is used as the separator
instead, and the query parts are joined by OR, not AND, allowing
admins to search by a list of ids, emails or usernames and find all
objects in that list.
- If the search terms are all numeric and there is more than one, then
we also restrict the fields we search to the one returned by
get_search_id_field(request) using a __in ORM lookup directly.
- If the search terms are all IP addresses, a special search for
objects matching those IPs is triggered
- If the queryset has a `for_count` property, then we use that to do
some optimizations, removing annotations that are only needed for
display purposes.
"""
# Apply keyword searches.
def construct_search(field_name):
if field_name.startswith('^'):
return '%s__istartswith' % field_name[1:]
elif field_name.startswith('='):
return '%s__iexact' % field_name[1:]
elif field_name.startswith('@'):
return '%s__icontains' % field_name[1:]
# Use field_name if it includes a lookup.
opts = queryset.model._meta
lookup_fields = field_name.split(models.constants.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):
return field_name
else:
prev_field = field
if hasattr(field, 'get_path_info'):
# Update opts to follow the relation.
opts = field.get_path_info()[-1].to_opts
# Otherwise, use the field with icontains.
return '%s__icontains' % field_name
if self.search_by_ip_actions:
ips_and_networks = self.ip_addresses_and_networks_from_query(search_term)
# If self.search_by_ip_actions is truthy, then we can call
# get_queryset_with_related_ips(), which will add IP
# annotations if needed (either because we're doing an IP search
# or because the known_ip_addresses field is in list_display)
queryset, may_have_duplicates = self.get_queryset_with_related_ips(
request, queryset, ips_and_networks
)
# ... We can return here early if we were indeed searching by IP.
if ips_and_networks:
return queryset, may_have_duplicates
else:
may_have_duplicates = False
search_fields = self.get_search_fields(request)
filters = []
joining_operator = operator.and_
if not (search_fields and search_term):
# return early if we have nothing special to do
return queryset, may_have_duplicates
# Do our custom logic if a `,` is present. Note that our custom search
# form (AMOModelAdminChangeListSearchForm) does some preliminary
# cleaning when it sees a comma, trimming whitespace around each term.
if ',' in search_term:
separator = ','
joining_operator = operator.or_
else:
separator = None
# We support `*` as a wildcard character for our `__like` lookups.
search_term = search_term.replace('*', '%')
search_terms = search_term.split(separator)
if (
(search_id_field := self.get_search_id_field(request))
and len(search_terms) >= self.minimum_search_terms_to_search_by_id
and all(term.isnumeric() for term in search_terms)
):
# if we have at least minimum_search_terms_to_search_by_id terms
# they are all numeric, we're doing a bulk id search
queryset = queryset.filter(**{f'{search_id_field}__in': search_terms})
else:
orm_lookups = [
construct_search(str(search_field)) for search_field in search_fields
]
for bit in search_terms:
or_queries = [
models.Q(**{orm_lookup: bit}) for orm_lookup in orm_lookups
]
q_for_this_term = models.Q(functools.reduce(operator.or_, or_queries))
filters.append(q_for_this_term)
may_have_duplicates |= any(
# Use our own lookup_spawns_duplicates(), not django's.
self.lookup_spawns_duplicates(self.opts, search_spec)
for search_spec in orm_lookups
)
if filters:
queryset = queryset.filter(functools.reduce(joining_operator, filters))
return queryset, may_have_duplicates