SearchFilter to support JSONField and HStoreField (#7121)

* SearchFilter to support Custom query Transforms

Since Some fields support `__` as a custom Transform for query lookups we needed to update the m2m checking code to handle search_fields that contain __ that are not relationships.

* Update documentation on SearchFilter to include references to JSON and HStore Fields.
This commit is contained in:
Matthaus Woolard 2020-04-23 20:38:14 +12:00 committed by GitHub
parent 13c08370e7
commit 812f254bbd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 47 additions and 0 deletions

View File

@ -205,6 +205,10 @@ This will allow the client to filter the items in the list by making queries suc
You can also perform a related lookup on a ForeignKey or ManyToManyField with the lookup API double-underscore notation:
search_fields = ['username', 'email', 'profile__profession']
For [JSONField][JSONField] and [HStoreField][HStoreField] fields you can filter based on nested values within the data structure using the same double-underscore notation:
search_fields = ['data__breed', 'data__owner__other_pets__0__name']
By default, searches will use case-insensitive partial matches. The search parameter may contain multiple search terms, which should be whitespace and/or comma separated. If multiple search terms are used then objects will be returned in the list only if all the provided terms are matched.
@ -360,3 +364,5 @@ The [djangorestframework-word-filter][django-rest-framework-word-search-filter]
[django-rest-framework-word-search-filter]: https://github.com/trollknurr/django-rest-framework-word-search-filter
[django-url-filter]: https://github.com/miki725/django-url-filter
[drf-url-filter]: https://github.com/manjitkumar/drf-url-filters
[HStoreField]: https://docs.djangoproject.com/en/3.0/ref/contrib/postgres/fields/#hstorefield
[JSONField]: https://docs.djangoproject.com/en/3.0/ref/contrib/postgres/fields/#jsonfield

View File

@ -96,6 +96,9 @@ class SearchFilter(BaseFilterBackend):
if any(path.m2m for path in path_info):
# This field is a m2m relation so we know we need to call distinct
return True
else:
# This field has a custom __ query transform but is not a relational field.
break
return False
def filter_queryset(self, request, queryset, view):

View File

@ -1,9 +1,11 @@
import datetime
from importlib import reload as reload_module
import django
import pytest
from django.core.exceptions import ImproperlyConfigured
from django.db import models
from django.db.models import CharField, Transform
from django.db.models.functions import Concat, Upper
from django.test import TestCase
from django.test.utils import override_settings
@ -189,6 +191,42 @@ class SearchFilterTests(TestCase):
assert terms == ['asdf']
@pytest.mark.skipif(django.VERSION[:2] < (2, 2), reason="requires django 2.2 or higher")
def test_search_field_with_additional_transforms(self):
from django.test.utils import register_lookup
class SearchListView(generics.ListAPIView):
queryset = SearchFilterModel.objects.all()
serializer_class = SearchFilterSerializer
filter_backends = (filters.SearchFilter,)
search_fields = ('text__trim', )
view = SearchListView.as_view()
# an example custom transform, that trims `a` from the string.
class TrimA(Transform):
function = 'TRIM'
lookup_name = 'trim'
def as_sql(self, compiler, connection):
sql, params = compiler.compile(self.lhs)
return "trim(%s, 'a')" % sql, params
with register_lookup(CharField, TrimA):
# Search including `a`
request = factory.get('/', {'search': 'abc'})
response = view(request)
assert response.data == []
# Search excluding `a`
request = factory.get('/', {'search': 'bc'})
response = view(request)
assert response.data == [
{'id': 1, 'title': 'z', 'text': 'abc'},
{'id': 2, 'title': 'zz', 'text': 'bcd'},
]
class AttributeModel(models.Model):
label = models.CharField(max_length=32)