From 9afba72cae4de3f9def0f1a2aa36ce4dd6ae1a87 Mon Sep 17 00:00:00 2001 From: Daniel Lindsley Date: Tue, 3 May 2011 15:03:07 -0500 Subject: [PATCH 1/4] Added ``result_class`` compatibility to match the other Haystack backends. --- tests/xapian_tests/tests/xapian_backend.py | 40 ++++++++++++++++++++++ xapian_backend.py | 14 +++++--- 2 files changed, 50 insertions(+), 4 deletions(-) diff --git a/tests/xapian_tests/tests/xapian_backend.py b/tests/xapian_tests/tests/xapian_backend.py index 024ffd8..fa9cf9c 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,9 @@ 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_order_by(self): self.backend.update(self.index, self.sample_objs) @@ -484,6 +492,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/xapian_backend.py b/xapian_backend.py index 8fb3c88..44af56b 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`. @@ -362,6 +362,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 +427,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, **model_data) ) if facets: @@ -443,7 +446,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. @@ -475,6 +478,9 @@ class SearchBackend(BaseSearchBackend): """ 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 +527,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, **model_data) ) return { From 3b3834d8ed1b3f1ae86d6cbfc5f12a5dca780449 Mon Sep 17 00:00:00 2001 From: Daniel Lindsley Date: Tue, 3 May 2011 15:03:29 -0500 Subject: [PATCH 2/4] Fixed a bug so that ``ValuesListQuerySet`` now works with the ``__in`` filter. --- tests/xapian_tests/tests/xapian_query.py | 5 +++++ xapian_backend.py | 4 ++++ 2 files changed, 9 insertions(+) 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 44af56b..c680b3c 100755 --- a/xapian_backend.py +++ b/xapian_backend.py @@ -954,6 +954,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: From 8d3bc1a4ee1ee69cf4d8cbfd5f92540da271b2eb Mon Sep 17 00:00:00 2001 From: Daniel Lindsley Date: Tue, 3 May 2011 15:14:58 -0500 Subject: [PATCH 3/4] Fixed results to now include the ``site``. --- xapian_backend.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xapian_backend.py b/xapian_backend.py index c680b3c..7a8a9e8 100755 --- a/xapian_backend.py +++ b/xapian_backend.py @@ -427,7 +427,7 @@ class SearchBackend(BaseSearchBackend): ) } results.append( - result_class(app_label, module_name, pk, match.percent, **model_data) + result_class(app_label, module_name, pk, match.percent, searchsite=site, **model_data) ) if facets: From 8112cf383d20cab6376d11deca2b1e6919f9bd1b Mon Sep 17 00:00:00 2001 From: Daniel Lindsley Date: Tue, 3 May 2011 15:37:30 -0500 Subject: [PATCH 4/4] Added a failing test to demonstrate that respecting the current site isn't working. --- tests/xapian_tests/tests/xapian_backend.py | 30 ++++++++++++++++++++++ xapian_backend.py | 12 ++++++++- 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/tests/xapian_tests/tests/xapian_backend.py b/tests/xapian_tests/tests/xapian_backend.py index fa9cf9c..0d7e49f 100644 --- a/tests/xapian_tests/tests/xapian_backend.py +++ b/tests/xapian_tests/tests/xapian_backend.py @@ -310,6 +310,36 @@ class XapianSearchBackendTestCase(TestCase): # 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) self.assertEqual(self.backend.document_count(), 3) diff --git a/xapian_backend.py b/xapian_backend.py index 7a8a9e8..67b45ba 100755 --- a/xapian_backend.py +++ b/xapian_backend.py @@ -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': [], @@ -476,6 +481,11 @@ 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: @@ -527,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( - result_class(app_label, module_name, pk, match.percent, **model_data) + result_class(app_label, module_name, pk, match.percent, searchsite=site, **model_data) ) return {