Skip to content
This repository
Browse code

Refactored ``SearchBackend.search`` so that kwarg-generation operatio…

…ns are in a discrete method.

This makes it much simpler to subclass ``SearchBackend`` (& the engine-specific variants) to add support for new parameters.
  • Loading branch information...
commit ee04fe4807e29ed57f93bbe5bfd17e218106aed7 1 parent d2b5114
Matt DeBoard mattdeboard authored committed
1  AUTHORS
@@ -63,3 +63,4 @@ Thanks to
63 63 * Alex Vidal (avidal) for a patch allowing developers to override the queryset used for update operations.
64 64 * Igor Támara (ikks) for a patch related to Unicode ``verbose_name_plural``.
65 65 * Dan Helfman (witten) for a patch related to highlighting.
  66 + * Matt DeBoard for refactor of ``SolrSearchBackend.search`` method to allow simpler extension of the class.
20 haystack/backends/__init__.py
@@ -6,9 +6,8 @@
6 6 from django.db.models.base import ModelBase
7 7 from django.utils import tree
8 8 from django.utils.encoding import force_unicode
9   -from haystack.constants import DJANGO_CT, VALID_FILTERS, FILTER_SEPARATOR, DEFAULT_ALIAS, DEFAULT_OPERATOR
  9 +from haystack.constants import VALID_FILTERS, FILTER_SEPARATOR, DEFAULT_ALIAS
10 10 from haystack.exceptions import MoreLikeThisError, FacetingError
11   -from haystack.inputs import Clean
12 11 from haystack.models import SearchResult
13 12 from haystack.utils.loading import UnifiedIndex
14 13
@@ -103,11 +102,7 @@ def clear(self, models=[], commit=True):
103 102 raise NotImplementedError
104 103
105 104 @log_query
106   - def search(self, query_string, sort_by=None, start_offset=0, end_offset=None,
107   - fields='', highlight=False, facets=None, date_facets=None, query_facets=None,
108   - narrow_queries=None, spelling_query=None, within=None,
109   - dwithin=None, distance_point=None, models=None,
110   - limit_to_registered_models=None, result_class=None, **kwargs):
  105 + def search(self, query_string, **kwargs):
111 106 """
112 107 Takes a query to search on and returns dictionary.
113 108
@@ -123,6 +118,17 @@ def search(self, query_string, sort_by=None, start_offset=0, end_offset=None,
123 118 """
124 119 raise NotImplementedError
125 120
  121 + def build_search_kwargs(self, query_string, sort_by=None, start_offset=0, end_offset=None,
  122 + fields='', highlight=False, facets=None,
  123 + date_facets=None, query_facets=None,
  124 + narrow_queries=None, spelling_query=None,
  125 + within=None, dwithin=None, distance_point=None,
  126 + models=None, limit_to_registered_models=None,
  127 + result_class=None):
  128 + # A convenience method most backends should include in order to make
  129 + # extension easier.
  130 + raise NotImplementedError
  131 +
126 132 def prep_value(self, value):
127 133 """
128 134 Hook to give the backend a chance to prep an attribute value before
49 haystack/backends/elasticsearch_backend.py
@@ -231,21 +231,13 @@ def clear(self, models=[], commit=True):
231 231 else:
232 232 self.log.error("Failed to clear Elasticsearch index: %s", e)
233 233
234   - @log_query
235   - def search(self, query_string, sort_by=None, start_offset=0, end_offset=None,
236   - fields='', highlight=False, facets=None, date_facets=None, query_facets=None,
237   - narrow_queries=None, spelling_query=None, within=None,
238   - dwithin=None, distance_point=None, models=None,
239   - limit_to_registered_models=None, result_class=None, **kwargs):
240   - if len(query_string) == 0:
241   - return {
242   - 'results': [],
243   - 'hits': 0,
244   - }
245   -
246   - if not self.setup_complete:
247   - self.setup()
248   -
  234 + def build_search_kwargs(self, query_string, sort_by=None, start_offset=0, end_offset=None,
  235 + fields='', highlight=False, facets=None,
  236 + date_facets=None, query_facets=None,
  237 + narrow_queries=None, spelling_query=None,
  238 + within=None, dwithin=None, distance_point=None,
  239 + models=None, limit_to_registered_models=None,
  240 + result_class=None):
249 241 index = haystack.connections[self.connection_alias].get_unified_index()
250 242 content_field = index.document_field
251 243
@@ -278,8 +270,6 @@ def search(self, query_string, sort_by=None, start_offset=0, end_offset=None,
278 270 },
279 271 }
280 272
281   - geo_sort = False
282   -
283 273 if fields:
284 274 if isinstance(fields, (list, set)):
285 275 fields = " ".join(fields)
@@ -451,16 +441,31 @@ def search(self, query_string, sort_by=None, start_offset=0, end_offset=None,
451 441 if not kwargs['query']['filtered'].get('filter'):
452 442 kwargs['query'] = kwargs['query']['filtered']['query']
453 443
  444 + return kwargs
  445 +
  446 + @log_query
  447 + def search(self, query_string, **kwargs):
  448 + if len(query_string) == 0:
  449 + return {
  450 + 'results': [],
  451 + 'hits': 0,
  452 + }
  453 +
  454 + if not self.setup_complete:
  455 + self.setup()
  456 +
  457 + search_kwargs = self.build_search_kwargs(query_string, **kwargs)
  458 +
454 459 # Because Elasticsearch.
455 460 query_params = {
456   - 'from': start_offset,
  461 + 'from': kwargs.get('start_offset', 0),
457 462 }
458 463
459   - if end_offset is not None and end_offset > start_offset:
460   - query_params['size'] = end_offset - start_offset
  464 + if kwargs.get('end_offset') is not None and kwargs.get('end_offset') > kwargs.get('start_offset', 0):
  465 + query_params['size'] = kwargs.get('end_offset') - kwargs.get('start_offset', 0)
461 466
462 467 try:
463   - raw_results = self.conn.search(None, kwargs, indexes=[self.index_name], doc_types=['modelresult'], **query_params)
  468 + raw_results = self.conn.search(None, search_kwargs, indexes=[self.index_name], doc_types=['modelresult'], **query_params)
464 469 except (requests.RequestException, pyelasticsearch.ElasticSearchError), e:
465 470 if not self.silently_fail:
466 471 raise
@@ -468,7 +473,7 @@ def search(self, query_string, sort_by=None, start_offset=0, end_offset=None,
468 473 self.log.error("Failed to query Elasticsearch using '%s': %s", query_string, e)
469 474 raw_results = {}
470 475
471   - return self._process_results(raw_results, highlight=highlight, result_class=result_class)
  476 + return self._process_results(raw_results, highlight=kwargs.get('highlight'), result_class=kwargs.get('result_class', SearchResult))
472 477
473 478 def more_like_this(self, model_instance, additional_query_string=None,
474 479 start_offset=0, end_offset=None, models=None,
11 haystack/backends/simple_backend.py
@@ -41,16 +41,13 @@ def clear(self, models=[], commit=True):
41 41 logger.warning('clear is not implemented in this backend')
42 42
43 43 @log_query
44   - def search(self, query_string, sort_by=None, start_offset=0, end_offset=None,
45   - fields='', highlight=False, facets=None, date_facets=None, query_facets=None,
46   - narrow_queries=None, spelling_query=None, within=None,
47   - dwithin=None, distance_point=None, models=None,
48   - limit_to_registered_models=None, result_class=None, **kwargs):
  44 + def search(self, query_string, **kwargs):
49 45 hits = 0
50 46 results = []
  47 + result_class = SearchResult
51 48
52   - if result_class is None:
53   - result_class = SearchResult
  49 + if kwargs.get('result_class'):
  50 + result_class = kwargs['result_class']
54 51
55 52 if query_string:
56 53 for model in connections[self.connection_alias].get_unified_index().get_indexed_models():
43 haystack/backends/solr_backend.py
@@ -114,21 +114,34 @@ def clear(self, models=[], commit=True):
114 114 self.log.error("Failed to clear Solr index: %s", e)
115 115
116 116 @log_query
117   - def search(self, query_string, sort_by=None, start_offset=0, end_offset=None,
118   - fields='', highlight=False, facets=None, date_facets=None, query_facets=None,
119   - narrow_queries=None, spelling_query=None, within=None,
120   - dwithin=None, distance_point=None, models=None,
121   - limit_to_registered_models=None, result_class=None, **kwargs):
  117 + def search(self, query_string, **kwargs):
122 118 if len(query_string) == 0:
123 119 return {
124 120 'results': [],
125 121 'hits': 0,
126 122 }
127 123
128   - kwargs = {
129   - 'fl': '* score',
130   - }
131   - geo_sort = False
  124 + search_kwargs = self.build_search_kwargs(query_string, **kwargs)
  125 +
  126 + try:
  127 + raw_results = self.conn.search(query_string, **search_kwargs)
  128 + except (IOError, SolrError), e:
  129 + if not self.silently_fail:
  130 + raise
  131 +
  132 + self.log.error("Failed to query Solr using '%s': %s", query_string, e)
  133 + raw_results = EmptyResults()
  134 +
  135 + return self._process_results(raw_results, highlight=kwargs.get('highlight'), result_class=kwargs.get('result_class', SearchResult), distance_point=kwargs.get('distance_point'))
  136 +
  137 + def build_search_kwargs(self, query_string, sort_by=None, start_offset=0, end_offset=None,
  138 + fields='', highlight=False, facets=None,
  139 + date_facets=None, query_facets=None,
  140 + narrow_queries=None, spelling_query=None,
  141 + within=None, dwithin=None, distance_point=None,
  142 + models=None, limit_to_registered_models=None,
  143 + result_class=None):
  144 + kwargs = {'fl': '* score'}
132 145
133 146 if fields:
134 147 if isinstance(fields, (list, set)):
@@ -142,7 +155,6 @@ def search(self, query_string, sort_by=None, start_offset=0, end_offset=None,
142 155 lng, lat = distance_point['point'].get_coords()
143 156 kwargs['sfield'] = distance_point['field']
144 157 kwargs['pt'] = '%s,%s' % (lat, lng)
145   - geo_sort = True
146 158
147 159 if sort_by == 'distance asc':
148 160 kwargs['sort'] = 'geodist() asc'
@@ -245,16 +257,7 @@ def search(self, query_string, sort_by=None, start_offset=0, end_offset=None,
245 257 # kwargs['fl'] += ' _dist_:geodist()'
246 258 pass
247 259
248   - try:
249   - raw_results = self.conn.search(query_string, **kwargs)
250   - except (IOError, SolrError), e:
251   - if not self.silently_fail:
252   - raise
253   -
254   - self.log.error("Failed to query Solr using '%s': %s", query_string, e)
255   - raw_results = EmptyResults()
256   -
257   - return self._process_results(raw_results, highlight=highlight, result_class=result_class, distance_point=distance_point)
  260 + return kwargs
258 261
259 262 def more_like_this(self, model_instance, additional_query_string=None,
260 263 start_offset=0, end_offset=None, models=None,
49 tests/core/tests/mocks.py
... ... @@ -1,16 +1,14 @@
1 1 from django.db.models.loading import get_model
2   -from django.utils.encoding import force_unicode
3 2 from haystack.backends import BaseEngine, BaseSearchBackend, BaseSearchQuery, log_query
4 3 from haystack.models import SearchResult
5 4 from haystack.routers import BaseRouter
6 5 from haystack.utils import get_identifier
7   -from core.models import MockModel
8 6
9 7
10 8 class MockMasterSlaveRouter(BaseRouter):
11 9 def for_read(self, **hints):
12 10 return 'slave'
13   -
  11 +
14 12 def for_write(self, **hints):
15 13 return 'master'
16 14
@@ -19,13 +17,13 @@ class MockPassthroughRouter(BaseRouter):
19 17 def for_read(self, **hints):
20 18 if hints.get('pass_through') is False:
21 19 return 'pass'
22   -
  20 +
23 21 return None
24   -
  22 +
25 23 def for_write(self, **hints):
26 24 if hints.get('pass_through') is False:
27 25 return 'pass'
28   -
  26 +
29 27 return None
30 28
31 29
@@ -39,7 +37,7 @@ def __init__(self, app_label, model_name, pk, score, **kwargs):
39 37
40 38 class MockSearchBackend(BaseSearchBackend):
41 39 model_name = 'mockmodel'
42   -
  40 +
43 41 def update(self, index, iterable, commit=True):
44 42 global MOCK_INDEX_DATA
45 43 for obj in iterable:
@@ -53,32 +51,29 @@ def remove(self, obj, commit=True):
53 51 def clear(self, models=[], commit=True):
54 52 global MOCK_INDEX_DATA
55 53 MOCK_INDEX_DATA = {}
56   -
  54 +
57 55 @log_query
58   - def search(self, query_string, sort_by=None, start_offset=0, end_offset=None,
59   - fields='', highlight=False, facets=None, date_facets=None, query_facets=None,
60   - narrow_queries=None, spelling_query=None,
61   - limit_to_registered_models=None, result_class=None, **kwargs):
  56 + def search(self, query_string, **kwargs):
62 57 from haystack import connections
63 58 global MOCK_INDEX_DATA
64 59 results = []
65 60 hits = len(MOCK_INDEX_DATA)
66 61 indexed_models = connections['default'].get_unified_index().get_indexed_models()
67   -
  62 +
68 63 def junk_sort(key):
69 64 app, model, pk = key.split('.')
70   -
  65 +
71 66 if pk.isdigit():
72 67 return int(pk)
73 68 else:
74 69 return ord(pk[0])
75   -
  70 +
76 71 sliced = sorted(MOCK_INDEX_DATA, key=junk_sort)
77   -
  72 +
78 73 for result in sliced:
79 74 app_label, model_name, pk = result.split('.')
80 75 model = get_model(app_label, model_name)
81   -
  76 +
82 77 if model:
83 78 if model in indexed_models:
84 79 results.append(MockSearchResult(app_label, model_name, pk, 1 - (i / 100.0)))
@@ -86,12 +81,12 @@ def junk_sort(key):
86 81 hits -= 1
87 82 else:
88 83 hits -= 1
89   -
  84 +
90 85 return {
91   - 'results': results[start_offset:end_offset],
  86 + 'results': results[kwargs.get('start_offset'):kwargs.get('end_offset')],
92 87 'hits': hits,
93 88 }
94   -
  89 +
95 90 def more_like_this(self, model_instance, additional_query_string=None, result_class=None):
96 91 return self.search(query_string='*')
97 92
@@ -111,32 +106,32 @@ class MixedMockSearchBackend(MockSearchBackend):
111 106 def search(self, query_string, **kwargs):
112 107 if kwargs.get('end_offset') and kwargs['end_offset'] > 30:
113 108 kwargs['end_offset'] = 30
114   -
  109 +
115 110 result_info = super(MixedMockSearchBackend, self).search(query_string, **kwargs)
116 111 result_info['hits'] = 30
117   -
  112 +
118 113 # Remove search results from other models.
119 114 temp_results = []
120   -
  115 +
121 116 for result in result_info['results']:
122 117 if not int(result.pk) in (9, 13, 14):
123 118 # MockSearchResult('core', 'AnotherMockModel', 9, .1)
124 119 # MockSearchResult('core', 'AnotherMockModel', 13, .1)
125 120 # MockSearchResult('core', 'NonexistentMockModel', 14, .1)
126 121 temp_results.append(result)
127   -
  122 +
128 123 result_info['results'] = temp_results
129   -
  124 +
130 125 return result_info
131 126
132 127
133 128 class MockSearchQuery(BaseSearchQuery):
134 129 def build_query(self):
135 130 return ''
136   -
  131 +
137 132 def clean(self, query_fragment):
138 133 return query_fragment
139   -
  134 +
140 135 # def run_mlt(self):
141 136 # # To simulate the chunking behavior of a regular search, return a slice
142 137 # # of our results using start/end offset.

0 comments on commit ee04fe4

Please sign in to comment.
Something went wrong with that request. Please try again.