From 80320d015d4afb1f13fe1de0a8dfd410a5944547 Mon Sep 17 00:00:00 2001 From: Oz Bar Shalom Date: Wed, 7 Mar 2018 08:55:22 +0200 Subject: [PATCH 1/2] Added new filter - SearchFieldFilter Added new Filter to allow the client choose the specific fields he wish to search for (usually very good for grid tables where the client search on each column) --- rest_framework/filters.py | 52 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/rest_framework/filters.py b/rest_framework/filters.py index 830d0a616..c58820554 100644 --- a/rest_framework/filters.py +++ b/rest_framework/filters.py @@ -306,3 +306,55 @@ class DjangoObjectPermissionsFilter(BaseFilterBackend): else: extra = {} return get_objects_for_user(user, permission, queryset, **extra) + + class SearchFieldFilter(SearchFilter): + # The URL query parameter used for the search on specific field. + # Use this filter to let the client choose the specific search field + # Example: ?search__name=x&search__last_name=y + search_param = api_settings.SEARCH_PARAM + '__' + lookup_prefixes = { + '^': 'istartswith', + '=': 'iexact', + '@': 'search', + '$': 'iregex', + } + + def get_search_terms(self, request): + """ + Search terms are set by the user as a suffix after the ?search__FIELDNAME=... query parameter, + and may be comma and/or whitespace delimited. + """ + params = [(key.replace(self.search_param, ''), request.query_params[key]) for + key in request.query_params.keys() + if key.startswith(self.search_param)] + + return params + + def filter_queryset(self, request, queryset, view): + search_terms = self.get_search_terms(request) + + if not search_terms: + return queryset + + orm_lookups = [ + self.construct_search(six.text_type(search_term[0])) + for search_term in search_terms + ] + + base = queryset + conditions = [] + for search_term in search_terms: + queries = [ + models.Q(**{orm_lookup: search_term[1]}) + for orm_lookup in orm_lookups + ] + conditions.append(reduce(operator.or_, queries)) + queryset = queryset.filter(reduce(operator.and_, conditions)) + + if self.must_call_distinct(queryset, [search_term[0] for search_term in search_terms]): + # Filtering against a many-to-many field requires us to + # call queryset.distinct() in order to avoid duplicate items + # in the resulting queryset. + # We try to avoid this if possible, for performance reasons. + queryset = distinct(queryset, base) + return queryset From 4af00465b272fec945518518abcd7991c89b82f0 Mon Sep 17 00:00:00 2001 From: Oz Bar Shalom Date: Wed, 7 Mar 2018 09:00:10 +0200 Subject: [PATCH 2/2] allow search only for valid search fields registered in the view --- rest_framework/filters.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/rest_framework/filters.py b/rest_framework/filters.py index c58820554..9d2d727bd 100644 --- a/rest_framework/filters.py +++ b/rest_framework/filters.py @@ -331,10 +331,16 @@ class DjangoObjectPermissionsFilter(BaseFilterBackend): return params def filter_queryset(self, request, queryset, view): + + valid_fields = getattr(view, 'search_fields', []) search_terms = self.get_search_terms(request) - - if not search_terms: + + if not search_terms or not allowed_search_fields: return queryset + + if valid_fields != '__all__': + search_terms = [search_term for search_term in search_terms if + search_term[0] in valid_fields] orm_lookups = [ self.construct_search(six.text_type(search_term[0]))