Added support for faceting when using MultiValueField

This commit is contained in:
David Sauve 2009-09-16 14:07:24 -04:00
parent d681827d0c
commit e0c6b3d7b1
2 changed files with 45 additions and 14 deletions

View file

@ -38,7 +38,11 @@ class XapianMockSearchIndex(indexes.SearchIndex):
value = indexes.IntegerField(model_attr='value')
flag = indexes.BooleanField(model_attr='flag')
slug = indexes.CharField(indexed=False, model_attr='slug')
popularity = indexes.FloatField(indexed=True, model_attr='popularity')
popularity = indexes.FloatField(model_attr='popularity')
mvf = indexes.MultiValueField()
def prepare_mvf(self, obj):
return ['%d' % (i * obj.id) for i in xrange(1, 4)]
class XapianSearchSite(sites.SearchSite):
@ -111,11 +115,11 @@ class XapianSearchBackendTestCase(TestCase):
self.assertEqual(len(self.xapian_search('')), 3)
self.assertEqual([dict(doc) for doc in self.xapian_search('')], [
{'flag': u't', 'name': u'david1', 'text': u'Indexed!\n1', 'pub_date': u'20090224000000', 'value': '000000000005', 'id': u'tests.mockmodel.1', 'slug': 'http://example.com/1', 'popularity': '\xca\x84'},
{'flag': u'f', 'name': u'david2', 'text': u'Indexed!\n2', 'pub_date': u'20090223000000', 'value': '000000000010', 'id': u'tests.mockmodel.2', 'slug': 'http://example.com/2', 'popularity': '\xb4`'},
{'flag': u't', 'name': u'david3', 'text': u'Indexed!\n3', 'pub_date': u'20090222000000', 'value': '000000000015', 'id': u'tests.mockmodel.3', 'slug': 'http://example.com/3', 'popularity': '\xcb\x98'}
{'flag': u't', 'name': u'david1', 'text': u'Indexed!\n1', 'mvf': u"['1', '2', '3']", 'pub_date': u'20090224000000', 'value': '000000000005', 'id': u'tests.mockmodel.1', 'slug': 'http://example.com/1', 'popularity': '\xca\x84'},
{'flag': u'f', 'name': u'david2', 'text': u'Indexed!\n2', 'mvf': u"['2', '4', '6']", 'pub_date': u'20090223000000', 'value': '000000000010', 'id': u'tests.mockmodel.2', 'slug': 'http://example.com/2', 'popularity': '\xb4`'},
{'flag': u't', 'name': u'david3', 'text': u'Indexed!\n3', 'mvf': u"['3', '6', '9']", 'pub_date': u'20090222000000', 'value': '000000000015', 'id': u'tests.mockmodel.3', 'slug': 'http://example.com/3', 'popularity': '\xcb\x98'}
])
def test_remove(self):
self.sb.update(self.msi, self.sample_objs)
self.assertEqual(len(self.xapian_search('')), 3)
@ -123,8 +127,8 @@ class XapianSearchBackendTestCase(TestCase):
self.sb.remove(self.sample_objs[0])
self.assertEqual(len(self.xapian_search('')), 2)
self.assertEqual([dict(doc) for doc in self.xapian_search('')], [
{'flag': u'f', 'name': u'david2', 'text': u'Indexed!\n2', 'pub_date': u'20090223000000', 'value': '000000000010', 'id': u'tests.mockmodel.2', 'slug': 'http://example.com/2', 'popularity': '\xb4`'},
{'flag': u't', 'name': u'david3', 'text': u'Indexed!\n3', 'pub_date': u'20090222000000', 'value': '000000000015', 'id': u'tests.mockmodel.3', 'slug': 'http://example.com/3', 'popularity': '\xcb\x98'}
{'flag': u'f', 'name': u'david2', 'text': u'Indexed!\n2', 'mvf': u"['2', '4', '6']", 'pub_date': u'20090223000000', 'value': '000000000010', 'id': u'tests.mockmodel.2', 'slug': 'http://example.com/2', 'popularity': '\xb4`'},
{'flag': u't', 'name': u'david3', 'text': u'Indexed!\n3', 'mvf': u"['3', '6', '9']", 'pub_date': u'20090222000000', 'value': '000000000015', 'id': u'tests.mockmodel.3', 'slug': 'http://example.com/3', 'popularity': '\xcb\x98'}
])
def test_clear(self):
@ -192,6 +196,10 @@ class XapianSearchBackendTestCase(TestCase):
results = self.sb.search('index', facets=['flag'])
self.assertEqual(results['hits'], 3)
self.assertEqual(results['facets']['fields']['flag'], [(False, 1), (True, 2)])
results = self.sb.search('index', facets=['mvf'])
self.assertEqual(results['hits'], 3)
self.assertEqual(results['facets']['fields']['mvf'], [('1', 1), ('3', 2), ('2', 2), ('4', 1), ('6', 2), ('9', 1)])
def test_date_facets(self):
self.sb.update(self.msi, self.sample_objs)
@ -343,12 +351,13 @@ class XapianSearchBackendTestCase(TestCase):
def test_build_schema(self):
(content_field_name, fields) = self.sb.build_schema(self.site.all_searchfields())
self.assertEqual(content_field_name, 'text')
self.assertEqual(len(fields), 6)
self.assertEqual(len(fields), 7)
self.assertEqual(fields, [
{'column': 0, 'type': 'text', 'field_name': 'name', 'multi_valued': 'false'},
{'column': 1, 'type': 'text', 'field_name': 'text', 'multi_valued': 'false'},
{'column': 2, 'type': 'float', 'field_name': 'popularity', 'multi_valued': 'false'},
{'column': 3, 'type': 'long', 'field_name': 'value', 'multi_valued': 'false'},
{'column': 4, 'type': 'boolean', 'field_name': 'flag', 'multi_valued': 'false'},
{'column': 5, 'type': 'date', 'field_name': 'pub_date', 'multi_valued': 'false'},
{'column': 5, 'type': 'text', 'field_name': 'mvf', 'multi_valued': 'true'},
{'column': 6, 'type': 'date', 'field_name': 'pub_date', 'multi_valued': 'false'},
])

View file

@ -202,18 +202,18 @@ class SearchBackend(BaseSearchBackend):
document = xapian.Document()
term_generator = self._term_generator(database, document)
document_id = self.get_identifier(obj)
model_data = index.prepare(obj)
data = index.prepare(obj)
for field in self.schema:
if field['field_name'] in model_data.keys():
if field['field_name'] in data.keys():
prefix = DOCUMENT_CUSTOM_TERM_PREFIX + field['field_name'].upper()
value = model_data[field['field_name']]
value = data[field['field_name']]
term_generator.index_text(force_unicode(value))
term_generator.index_text(force_unicode(value), 1, prefix)
document.add_value(field['column'], self._marshal_value(value))
document.set_data(pickle.dumps(
(obj._meta.app_label, obj._meta.module_name, obj.pk, model_data),
(obj._meta.app_label, obj._meta.module_name, obj.pk, data),
pickle.HIGHEST_PROTOCOL
))
document.add_term(document_id)
@ -522,6 +522,9 @@ class SearchBackend(BaseSearchBackend):
"""
Private method that facets a document by field name.
Fields of type MultiValueField will be faceted on each item in the
(containing) list.
Required arguments:
`results` -- A list SearchResults to facet
`field_facets` -- A list of fields to facet on
@ -533,7 +536,11 @@ class SearchBackend(BaseSearchBackend):
for result in results:
field_value = getattr(result, field)
facet_list[field_value] = facet_list.get(field_value, 0) + 1
if self._multi_value_field(field):
for item in field_value: # Facet each item in a MultiValueField
facet_list[item] = facet_list.get(item, 0) + 1
else:
facet_list[field_value] = facet_list.get(field_value, 0) + 1
facet_dict[field] = facet_list.items()
@ -859,6 +866,21 @@ class SearchBackend(BaseSearchBackend):
return field_dict['column']
return 0
def _multi_value_field(self, field):
"""
Private method that returns `True` if a field is multi-valued, else
`False`.
Required arguemnts:
`field` -- The field to lookup
Returns a boolean value indicating whether the field is multi-valued.
"""
for field_dict in self.schema:
if field_dict['field_name'] == field:
return field_dict['multi_valued'] == 'true'
return False
class SearchQuery(BaseSearchQuery):
"""