-
Notifications
You must be signed in to change notification settings - Fork 114
/
simple_query_string.py
165 lines (130 loc) · 5.51 KB
/
simple_query_string.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
import copy
from elasticsearch_dsl.query import Q
from .base import BaseSearchQueryBackend
__title__ = 'django_elasticsearch_dsl_drf.filter_backends.search.' \
'query_backends.simple_query_string'
__author__ = 'Artur Barseghyan <artur.barseghyan@gmail.com>'
__copyright__ = '2017-2020 Artur Barseghyan'
__license__ = 'GPL 2.0/LGPL 2.1'
__all__ = ('SimpleQueryStringQueryBackend',)
class SimpleQueryStringQueryBackend(BaseSearchQueryBackend):
"""Simple query string query backend."""
query_type = 'simple_query_string'
@classmethod
def get_field(cls, field, options):
"""Get field.
:param field:
:param options:
:return:
"""
if not options:
options = {}
field_name = options['field'] \
if 'field' in options \
else field
if 'boost' in options:
return '{}^{}'.format(field_name, options['boost'])
return field_name
@classmethod
def get_query_options(cls, request, view, search_backend):
query_options = getattr(view, 'simple_query_string_options', {})
return query_options
@classmethod
def construct_search(cls, request, view, search_backend):
"""Construct search.
In case of multi match, we always look in a group of fields.
Thus, matching per field is no longer valid use case here. However,
we might want to have multiple fields enabled for multi match per
view set, and only search in some of them in specific request.
Example:
/search/books/?search_simple_query_string=
"fried eggs" %2B(eggplant | potato) -frittata
/search/books/?search_simple_query_string=
title,summary:"fried eggs" +(eggplant | potato) -frittata
Note, that multiple searches are not supported (would not raise
an exception, but would simply take only the first):
/search/books/?search_simple_query_string=
title,summary:"fried eggs" +(eggplant | potato) -frittata
&search_simple_query_string=
author,publisher="fried eggs" +(eggplant | potato) -frittata
In the view-set fields shall be defined in a very simple way. The
only accepted argument would be boost (per field).
Example 1 (complex):
simple_query_string_search_fields = {
'title': {'field': 'title.english', 'boost': 4},
'summary': {'boost': 2},
'description': None,
}
Example 2 (simple list):
simple_query_string_search_fields = (
'title',
'summary',
'description',
)
Query examples:
http://localhost:8000/search
/books-simple-query-string-search-backend
/?search_simple_query_string=%22Pool%20of%20Tears%22
http://localhost:8000/search
/books-simple-query-string-search-backend
/?search_simple_query_string=%22Pool%20of%20Tears%22
-considering
:param request:
:param view:
:param search_backend:
:return:
"""
if hasattr(view, 'simple_query_string_search_fields'):
view_search_fields = copy.copy(
getattr(view, 'simple_query_string_search_fields')
)
else:
view_search_fields = copy.copy(view.search_fields)
__is_complex = isinstance(view_search_fields, dict)
# Getting the list of search query params.
query_params = search_backend.get_search_query_params(request)
__queries = []
for search_term in query_params[:1]:
__values = search_backend.split_lookup_name(search_term, 1)
__len_values = len(__values)
__search_term = search_term
query_fields = []
# If we're dealing with case like
# /search/books/?search_multi_match=title,summary:lorem ipsum
if __len_values > 1:
_field, value = __values
__search_term = value
fields = search_backend.split_lookup_complex_multiple_value(
_field
)
for field in fields:
if field in view_search_fields:
if __is_complex:
query_fields.append(
cls.get_field(field, view_search_fields[field])
)
else:
query_fields.append(field)
# If it's just a simple search like
# /search/books/?search_multi_match=lorem ipsum
# Fields shall be defined in a very simple way.
else:
# It's a dict, see example 1 (complex)
if __is_complex:
for field, options in view_search_fields.items():
query_fields.append(
cls.get_field(field, options)
)
# It's a list, see example 2 (simple)
else:
query_fields = copy.copy(view_search_fields)
# The multi match query
__queries.append(
Q(
cls.query_type,
query=__search_term,
fields=query_fields,
**cls.get_query_options(request, view, search_backend)
)
)
return __queries