From b4b2dc18fa4cb6bbe008d4f6e59b58862ed20be6 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 20 Aug 2015 11:35:32 +0100 Subject: [PATCH] Clean-up refactoring of SearchFilter implementation --- rest_framework/compat.py | 7 +++++++ rest_framework/filters.py | 39 +++++++++++++++++++++------------------ 2 files changed, 28 insertions(+), 18 deletions(-) diff --git a/rest_framework/compat.py b/rest_framework/compat.py index 11fc9a28a..2cff61088 100644 --- a/rest_framework/compat.py +++ b/rest_framework/compat.py @@ -50,6 +50,13 @@ def total_seconds(timedelta): return (timedelta.days * 86400.0) + float(timedelta.seconds) + (timedelta.microseconds / 1000000.0) +def distinct(queryset, base): + if settings.DATABASES[queryset.db]["ENGINE"] == "django.db.backends.oracle": + # distinct analogue for Oracle users + return base.filter(pk__in=set(queryset.values_list('pk', flat=True))) + return queryset.distinct() + + # OrderedDict only available in Python 2.7. # This will always be the case in Django 1.7 and above, as these versions # no longer support Python 2.6. diff --git a/rest_framework/filters.py b/rest_framework/filters.py index 3956bb9bc..e05d31ae3 100644 --- a/rest_framework/filters.py +++ b/rest_framework/filters.py @@ -7,12 +7,13 @@ from __future__ import unicode_literals import operator from functools import reduce -from django.conf import settings from django.core.exceptions import ImproperlyConfigured from django.db import models from django.utils import six -from rest_framework.compat import django_filters, get_model_name, guardian +from rest_framework.compat import ( + distinct, django_filters, get_model_name, guardian +) from rest_framework.settings import api_settings FilterSet = django_filters and django_filters.FilterSet or None @@ -99,25 +100,27 @@ class SearchFilter(BaseFilterBackend): def filter_queryset(self, request, queryset, view): search_fields = getattr(view, 'search_fields', None) - if not search_fields: + orm_lookups = [ + self.construct_search(six.text_type(search_field)) + for search_field in search_fields + ] + search_terms = self.get_search_terms(request) + + if not search_fields or not search_terms: return queryset - original_queryset = queryset - orm_lookups = [self.construct_search(six.text_type(search_field)) - for search_field in search_fields] + base = queryset + for search_term in search_terms: + queries = [ + models.Q(**{orm_lookup: search_term}) + for orm_lookup in orm_lookups + ] + queryset = queryset.filter(reduce(operator.or_, queries)) - for search_term in self.get_search_terms(request): - or_queries = [models.Q(**{orm_lookup: search_term}) - for orm_lookup in orm_lookups] - queryset = queryset.filter(reduce(operator.or_, or_queries)) - - if settings.DATABASES[queryset.db]["ENGINE"] == "django.db.backends.oracle": - # distinct analogue for Oracle users - queryset = original_queryset.filter(pk__in=set(queryset.values_list('pk', flat=True))) - else: - queryset = queryset.distinct() - - return queryset + # Filtering against a many-to-many field requires us to + # call queryset.distinct() in order to avoid duplicate items + # in the resulting queryset. + return distinct(queryset, base) class OrderingFilter(BaseFilterBackend):