Skip to content

Commit 1ce85d0

Browse files
committed
Allow search to split also by comma after smart split
1 parent 82c42dc commit 1ce85d0

File tree

2 files changed

+37
-5
lines changed

2 files changed

+37
-5
lines changed

rest_framework/filters.py

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,21 @@
2020
from rest_framework.settings import api_settings
2121

2222

23+
def search_smart_split(search_terms):
24+
"""generator that first splits string by spaces, leaving quoted phrases togheter,
25+
then it splits non-quoted phrases by commas.
26+
"""
27+
for term in smart_split(search_terms):
28+
# trim commas to avoid bad matching for quoted phrases
29+
term = term.strip(',')
30+
if term.startswith(('"', "'")) and term[0] == term[-1]:
31+
# quoted phrases are kept togheter without any other split
32+
yield unescape_string_literal(term)
33+
else:
34+
# non-quoted tokens are split by comma, keeping only non-empty ones
35+
yield from (sub_term.strip() for sub_term in term.split(',') if sub_term)
36+
37+
2338
class BaseFilterBackend:
2439
"""
2540
A base class from which all filter backend classes should inherit.
@@ -144,9 +159,7 @@ def filter_queryset(self, request, queryset, view):
144159

145160
base = queryset
146161
conditions = []
147-
for term in smart_split(search_terms):
148-
if term.startswith(('"', "'")) and term[0] == term[-1]:
149-
term = unescape_string_literal(term)
162+
for term in search_smart_split(search_terms):
150163
queries = [
151164
models.Q(**{orm_lookup: term})
152165
for orm_lookup in orm_lookups

tests/test_filters.py

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from django.db import models
77
from django.db.models import CharField, Transform
88
from django.db.models.functions import Concat, Upper
9-
from django.test import TestCase
9+
from django.test import SimpleTestCase, TestCase
1010
from django.test.utils import override_settings
1111

1212
from rest_framework import filters, generics, serializers
@@ -17,6 +17,25 @@
1717
factory = APIRequestFactory()
1818

1919

20+
class SearchSplitTests(SimpleTestCase):
21+
22+
def test_keep_quoted_togheter_regardless_of_commas(self):
23+
assert ['hello, world'] == list(filters.search_smart_split('"hello, world"'))
24+
25+
def test_strips_commas_around_quoted(self):
26+
assert ['hello, world'] == list(filters.search_smart_split(',,"hello, world"'))
27+
assert ['hello, world'] == list(filters.search_smart_split(',,"hello, world",,'))
28+
assert ['hello, world'] == list(filters.search_smart_split('"hello, world",,'))
29+
30+
def test_splits_by_comma(self):
31+
assert ['hello', 'world'] == list(filters.search_smart_split(',,hello, world'))
32+
assert ['hello', 'world'] == list(filters.search_smart_split(',,hello, world,,'))
33+
assert ['hello', 'world'] == list(filters.search_smart_split('hello, world,,'))
34+
35+
def test_splits_quotes_followed_by_comma_and_sentence(self):
36+
assert ['"hello', 'world"', 'found'] == list(filters.search_smart_split('"hello, world",found'))
37+
38+
2039
class BaseFilterTests(TestCase):
2140
def setUp(self):
2241
self.original_coreapi = filters.coreapi
@@ -435,7 +454,7 @@ class SearchListView(generics.ListAPIView):
435454
search_fields = ('=name', 'entry__headline', '=entry__pub_date__year')
436455

437456
view = SearchListView.as_view()
438-
request = factory.get('/', {'search': 'Lennon 1979'})
457+
request = factory.get('/', {'search': 'Lennon,1979'})
439458
response = view(request)
440459
assert len(response.data) == 1
441460

0 commit comments

Comments
 (0)