diff --git a/tests/xapian_tests/tests/xapian_backend.py b/tests/xapian_tests/tests/xapian_backend.py index 024ffd8..0d7e49f 100644 --- a/tests/xapian_tests/tests/xapian_backend.py +++ b/tests/xapian_tests/tests/xapian_backend.py @@ -15,10 +15,12 @@ from django.test import TestCase from haystack import indexes, sites, backends from haystack.backends.xapian_backend import SearchBackend, SearchQuery, _marshal_value from haystack.exceptions import HaystackError +from haystack.models import SearchResult from haystack.query import SearchQuerySet, SQ from haystack.sites import SearchSite from core.models import MockTag, MockModel, AnotherMockModel, AFourthMockModel +from core.tests.mocks import MockSearchResult class XapianMockModel(models.Model): @@ -188,6 +190,9 @@ class XapianSearchBackendTestCase(TestCase): self.assertEqual([result.pk for result in self.backend.search(xapian.Query(''))['results']], [1, 2, 3]) self.assertEqual(self.backend.search(xapian.Query('indexed'))['hits'], 3) self.assertEqual([result.pk for result in self.backend.search(xapian.Query(''))['results']], [1, 2, 3]) + + # Ensure that swapping the ``result_class`` works. + self.assertTrue(isinstance(self.backend.search(xapian.Query('indexed'), result_class=MockSearchResult)['results'][0], MockSearchResult)) def test_search_field_with_punctuation(self): self.backend.update(self.index, self.sample_objs) @@ -301,6 +306,39 @@ class XapianSearchBackendTestCase(TestCase): results = self.backend.more_like_this(self.sample_objs[0], limit_to_registered_models=True) self.assertEqual(results['hits'], 2) self.assertEqual([result.pk for result in results['results']], [3, 2]) + + # Ensure that swapping the ``result_class`` works. + self.assertTrue(isinstance(self.backend.more_like_this(self.sample_objs[0], result_class=MockSearchResult)['results'][0], MockSearchResult)) + + def test_use_correct_site(self): + test_site = SearchSite() + test_site.register(XapianMockModel, XapianMockSearchIndex) + self.backend.update(self.index, self.sample_objs) + + # Make sure that ``_process_results`` uses the right ``site``. + self.assertEqual(self.backend.search(xapian.Query('indexed'))['hits'], 3) + self.assertEqual([result.pk for result in self.backend.search(xapian.Query('indexed'))['results']], [1, 2, 3]) + + self.site.unregister(XapianMockModel) + self.assertEqual(len(self.site.get_indexed_models()), 0) + self.backend.site = test_site + self.assertTrue(len(self.backend.site.get_indexed_models()) > 0) + + # Should still be there, despite the main ``site`` not having that model + # registered any longer. + self.assertEqual(self.backend.search(xapian.Query('indexed'))['hits'], 3) + self.assertEqual([result.pk for result in self.backend.search(xapian.Query('indexed'))['results']], [1, 2, 3]) + + # Unregister it on the backend & make sure it takes effect. + self.backend.site.unregister(XapianMockModel) + self.assertEqual(len(self.backend.site.get_indexed_models()), 0) + self.assertEqual(self.backend.search(xapian.Query('indexed'))['hits'], 0) + + # Nuke it & fallback on the main ``site``. + self.backend.site = haystack.site + self.assertEqual(self.backend.search(xapian.Query('indexed'))['hits'], 0) + self.site.register(XapianMockModel, XapianMockSearchIndex) + self.assertEqual(self.backend.search(xapian.Query('indexed'))['hits'], 3) def test_order_by(self): self.backend.update(self.index, self.sample_objs) @@ -484,6 +522,38 @@ class LiveXapianSearchQueryTestCase(TestCase): settings.DEBUG = old_debug +class LiveXapianSearchQuerySetTestCase(TestCase): + """ + SearchQuerySet specific tests + """ + fixtures = ['initial_data.json'] + + def setUp(self): + super(LiveXapianSearchQuerySetTestCase, self).setUp() + + site = SearchSite() + backend = SearchBackend(site=site) + index = LiveXapianMockSearchIndex(MockModel, backend=backend) + site.register(MockModel, LiveXapianMockSearchIndex) + backend.update(index, MockModel.objects.all()) + + self.sq = SearchQuery(backend=backend) + self.sqs = SearchQuerySet(query=self.sq) + + def test_result_class(self): + # Assert that we're defaulting to ``SearchResult``. + sqs = self.sqs.all() + self.assertTrue(isinstance(sqs[0], SearchResult)) + + # Custom class. + sqs = self.sqs.result_class(MockSearchResult).all() + self.assertTrue(isinstance(sqs[0], MockSearchResult)) + + # Reset to default. + sqs = self.sqs.result_class(None).all() + self.assertTrue(isinstance(sqs[0], SearchResult)) + + class XapianBoostBackendTestCase(TestCase): def setUp(self): super(XapianBoostBackendTestCase, self).setUp() diff --git a/tests/xapian_tests/tests/xapian_query.py b/tests/xapian_tests/tests/xapian_query.py index b79ce7f..1c9eb51 100644 --- a/tests/xapian_tests/tests/xapian_query.py +++ b/tests/xapian_tests/tests/xapian_query.py @@ -159,3 +159,8 @@ class XapianSearchQueryTestCase(TestCase): def test_build_query_with_punctuation(self): self.sq.add_filter(SQ(content='http://www.example.com')) self.assertEqual(str(self.sq.build_query()), u'Xapian::Query((Zhttp://www.example.com OR http://www.example.com))') + + def test_in_filter_values_list(self): + self.sq.add_filter(SQ(content='why')) + self.sq.add_filter(SQ(title__in=MockModel.objects.values_list('id', flat=True))) + self.assertEqual(str(self.sq.build_query()), u'Xapian::Query(((Zwhi OR why) AND (ZXTITLE1 OR XTITLE1 OR ZXTITLE2 OR XTITLE2 OR ZXTITLE3 OR XTITLE3)))') diff --git a/xapian_backend.py b/xapian_backend.py index 8fb3c88..67b45ba 100755 --- a/xapian_backend.py +++ b/xapian_backend.py @@ -317,7 +317,7 @@ class SearchBackend(BaseSearchBackend): def search(self, query, sort_by=None, start_offset=0, end_offset=None, fields='', highlight=False, facets=None, date_facets=None, query_facets=None, narrow_queries=None, spelling_query=None, - limit_to_registered_models=True, **kwargs): + limit_to_registered_models=True, result_class=None, **kwargs): """ Executes the Xapian::query as defined in `query`. @@ -354,6 +354,11 @@ class SearchBackend(BaseSearchBackend): and any suggestions for spell correction will be returned as well as the results. """ + if not self.site: + from haystack import site + else: + site = self.site + if xapian.Query.empty(query): return { 'results': [], @@ -362,6 +367,9 @@ class SearchBackend(BaseSearchBackend): database = self._database() + if result_class is None: + result_class = SearchResult + if getattr(settings, 'HAYSTACK_INCLUDE_SPELLING', False) is True: spelling_suggestion = self._do_spelling_suggestion(database, query, spelling_query) else: @@ -424,7 +432,7 @@ class SearchBackend(BaseSearchBackend): ) } results.append( - SearchResult(app_label, module_name, pk, match.percent, **model_data) + result_class(app_label, module_name, pk, match.percent, searchsite=site, **model_data) ) if facets: @@ -443,7 +451,7 @@ class SearchBackend(BaseSearchBackend): def more_like_this(self, model_instance, additional_query=None, start_offset=0, end_offset=None, - limit_to_registered_models=True, **kwargs): + limit_to_registered_models=True, result_class=None, **kwargs): """ Given a model instance, returns a result set of similar documents. @@ -473,8 +481,16 @@ class SearchBackend(BaseSearchBackend): Finally, processes the resulting matches and returns. """ + if not self.site: + from haystack import site + else: + site = self.site + database = self._database() + if result_class is None: + result_class = SearchResult + query = xapian.Query(DOCUMENT_ID_TERM_PREFIX + get_identifier(model_instance)) enquire = xapian.Enquire(database) @@ -521,7 +537,7 @@ class SearchBackend(BaseSearchBackend): for match in matches: app_label, module_name, pk, model_data = pickle.loads(self._get_document_data(database, match.document)) results.append( - SearchResult(app_label, module_name, pk, match.percent, **model_data) + result_class(app_label, module_name, pk, match.percent, searchsite=site, **model_data) ) return { @@ -948,6 +964,10 @@ class SearchQuery(BaseSearchQuery): expression, term = child field, filter_type = search_node.split_expression(expression) + # Handle when we've got a ``ValuesListQuerySet``... + if hasattr(term, 'values_list'): + term = list(term) + if isinstance(term, (list, tuple)): term = [_marshal_term(t) for t in term] else: