Merge pull request #74 from toastdriven/master.

Changes to help bring the Xapian backend up to current.
This commit is contained in:
David Sauve 2011-05-04 11:46:18 -07:00
commit ef8ca2dfa7
3 changed files with 99 additions and 4 deletions

View file

@ -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()

View file

@ -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)))')

View file

@ -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: