def get_filters(self, request): lookup_params = self.get_filters_params() use_distinct = False filter_specs = [] if self.list_filter: for list_filter in self.list_filter: if callable(list_filter): # This is simply a custom list filter class. spec = list_filter(request, lookup_params, self.model, self.model_admin) else: field_path = None if isinstance(list_filter, (tuple, list)): # This is a custom FieldListFilter class for a given # field. field, field_list_filter_class = list_filter else: # This is simply a field name, so use the default # FieldListFilter class that has been registered for # the type of the given field. field = list_filter field_list_filter_class = FieldListFilter.create if not isinstance(field, models.Field): field_path = field field = get_fields_from_path(self.model, field_path)[-1] spec = field_list_filter_class( field, request, lookup_params, self.model, self.model_admin, field_path=field_path, ) # Check if we need to use distinct() use_distinct = use_distinct or lookup_spawns_duplicates( self.opts, field_path) if spec and spec.has_output(): filter_specs.append(spec) # At this point, all the parameters used by the various ListFilters # have been removed from lookup_params, which now only contains other # parameters passed via the query string. We now loop through the # remaining parameters both to ensure that all the parameters are valid # fields and to determine if at least one of them needs distinct(). If # the lookup parameters aren't real fields, then bail out. try: for key, value in lookup_params.items(): lookup_params[key] = prepare_lookup_value(key, value) use_distinct = use_distinct or lookup_spawns_duplicates( self.opts, key) return (filter_specs, bool(filter_specs), lookup_params, use_distinct) except FieldDoesNotExist as e: raise IncorrectLookupParameters from e
def filter_queryset(self, request, term, queryset=None, **dependent_fields): """ Return QuerySet filtered by search_fields matching the passed term. Args: request (django.http.request.HttpRequest): The request is being passed from the JSON view and can be used to dynamically alter the response queryset. term (str): Search term queryset (django.db.models.query.QuerySet): QuerySet to select choices from. **dependent_fields: Dependent fields and their values. If you want to inherit from ModelSelect2Mixin and later call to this method, be sure to pop everything from keyword arguments that is not a dependent field. Returns: QuerySet: Filtered QuerySet """ if queryset is None: queryset = self.get_queryset() search_fields = self.get_search_fields() select = Q() use_distinct = False if search_fields and term: for bit in term.split(): or_queries = [ Q(**{orm_lookup: bit}) for orm_lookup in search_fields ] select &= reduce(operator.or_, or_queries) or_queries = [ Q(**{orm_lookup: term}) for orm_lookup in search_fields ] select |= reduce(operator.or_, or_queries) use_distinct |= any( lookup_spawns_duplicates(queryset.model._meta, search_spec) for search_spec in search_fields) if dependent_fields: select &= Q(**dependent_fields) use_distinct |= any( lookup_spawns_duplicates(queryset.model._meta, search_spec) for search_spec in dependent_fields.keys()) if use_distinct: return queryset.filter(select).distinct() return queryset.filter(select)
def search_queryset(self, queryset, search_term, **kwargs): if not search_term or not self.search_fields: return queryset orm_lookups = [ "%s__icontains" % str(search_field) for search_field in self.search_fields ] for bit in search_term.split(): or_queries = [Q(**{orm_lookup: bit}) for orm_lookup in orm_lookups] queryset = queryset.filter(reduce(operator.or_, or_queries)) opts = queryset.model._meta for search_spec in orm_lookups: if lookup_spawns_duplicates(opts, search_spec): return queryset.distinct() return queryset
def get_search_results(queryset, search_term, search_fields, model): """ Return a tuple containing a queryset to implement the search and a boolean indicating if the results may contain duplicates. """ try: from django.contrib.admin.utils import ( lookup_needs_distinct as lookup_spawns_duplicates, ) except ImportError: from django.contrib.admin.utils import lookup_spawns_duplicates use_distinct = False if search_fields and search_term: orm_lookups = [ construct_search(queryset, str(search_field)) for search_field in search_fields ] for bit in search_term.split(): or_queries = [Q(**{orm_lookup: bit}) for orm_lookup in orm_lookups] queryset = queryset.filter(reduce(operator.or_, or_queries)) use_distinct |= any( lookup_spawns_duplicates(model._meta, search_spec) for search_spec in orm_lookups) return queryset, use_distinct
def get_filters(self, request): lookup_params = self.get_filters_params() may_have_duplicates = False has_active_filters = False for key, value in lookup_params.items(): if not self.model_admin.lookup_allowed(key, value): raise DisallowedModelAdminLookup("Filtering by %s not allowed" % key) filter_specs = [] for list_filter in self.list_filter: lookup_params_count = len(lookup_params) if callable(list_filter): # This is simply a custom list filter class. spec = list_filter(request, lookup_params, self.model, self.model_admin) else: field_path = None if isinstance(list_filter, (tuple, list)): # This is a custom FieldListFilter class for a given field. field, field_list_filter_class = list_filter else: # This is simply a field name, so use the default # FieldListFilter class that has been registered for the # type of the given field. field, field_list_filter_class = list_filter, FieldListFilter.create if not isinstance(field, Field): field_path = field field = get_fields_from_path(self.model, field_path)[-1] spec = field_list_filter_class( field, request, lookup_params, self.model, self.model_admin, field_path=field_path, ) # field_list_filter_class removes any lookup_params it # processes. If that happened, check if duplicates should be # removed. if lookup_params_count > len(lookup_params): may_have_duplicates |= lookup_spawns_duplicates( self.lookup_opts, field_path, ) if spec and spec.has_output(): filter_specs.append(spec) if lookup_params_count > len(lookup_params): has_active_filters = True if self.date_hierarchy: # Create bounded lookup parameters so that the query is more # efficient. year = lookup_params.pop("%s__year" % self.date_hierarchy, None) if year is not None: month = lookup_params.pop("%s__month" % self.date_hierarchy, None) day = lookup_params.pop("%s__day" % self.date_hierarchy, None) try: from_date = datetime( int(year), int(month if month is not None else 1), int(day if day is not None else 1), ) except ValueError as e: raise IncorrectLookupParameters(e) from e if day: to_date = from_date + timedelta(days=1) elif month: # In this branch, from_date will always be the first of a # month, so advancing 32 days gives the next month. to_date = (from_date + timedelta(days=32)).replace(day=1) else: to_date = from_date.replace(year=from_date.year + 1) if settings.USE_TZ: from_date = make_aware(from_date) to_date = make_aware(to_date) lookup_params.update( { "%s__gte" % self.date_hierarchy: from_date, "%s__lt" % self.date_hierarchy: to_date, } ) # At this point, all the parameters used by the various ListFilters # have been removed from lookup_params, which now only contains other # parameters passed via the query string. We now loop through the # remaining parameters both to ensure that all the parameters are valid # fields and to determine if at least one of them spawns duplicates. If # the lookup parameters aren't real fields, then bail out. try: for key, value in lookup_params.items(): lookup_params[key] = prepare_lookup_value(key, value) may_have_duplicates |= lookup_spawns_duplicates(self.lookup_opts, key) return ( filter_specs, bool(filter_specs), lookup_params, may_have_duplicates, has_active_filters, ) except FieldDoesNotExist as e: raise IncorrectLookupParameters(e) from e