'''
Haystack does not yet support range facets on Solr. This module
provides subclasses of SolrSearchQuery and SolrSearchBackend to
patch in range facet functionalty.
'''
from haystack import connections
from haystack.backends.solr_backend import SolrSearchQuery, SolrSearchBackend, \
SolrEngine
from unidecode import unidecode
[docs]class RangeSolrSearchQuery(SolrSearchQuery):
def __init__(self, *args, **kwargs):
super(RangeSolrSearchQuery, self).__init__(*args, **kwargs)
self.range_facets = {}
[docs] def add_field_facet(self, field, **options):
# extend default facet field method to handle a special
# range=True case
if options.get('range', None):
self.add_range_facet(field, **options)
else:
return super(RangeSolrSearchQuery, self).add_field_facet(field, **options)
[docs] def add_range_facet(self, field, **options):
"""Adds a solr range facet on a field. Options must include
start, end, and gap."""
# using same logic as normal facets; for range facets this
# is probably unnecessary since they have to be numeric anyway
field_name = connections[self._using].get_unified_index() \
.get_facet_fieldname(field)
self.range_facets[field_name] = options.copy()
[docs] def build_params(self, *args, **kwargs):
# extend default build params logic to include any facet range
# options
search_kwargs = super(RangeSolrSearchQuery, self).build_params(*args, **kwargs)
if not self.range_facets:
return search_kwargs
range_kwargs = {
'facet.range': list(self.range_facets.keys())
}
for field, opts in self.range_facets.items():
# NOTE: not exposing other range facet params for now
for solr_opt in ['start', 'end', 'gap']:
if solr_opt in opts:
range_kwargs['f.%s.facet.range.%s' % (field, solr_opt)] \
= opts[solr_opt]
# support hard end option; convert python boolean to solr bool
if 'hardend' in opts:
val = 'true' if bool(opts['hardend']) else 'false'
range_kwargs['f.%s.facet.range.hardend' % field] = val
search_kwargs.update(range_kwargs)
return search_kwargs
[docs] def post_process_facets(self, results):
'''
Extend post processing logic to include facet range data in returned
facets.
'''
facets = super(RangeSolrSearchQuery, self).post_process_facets(results)
if 'facet_ranges' in results:
# copy facet range data into existing facet data
facets['ranges'] = results['facet_ranges'][0]
for data in facets['ranges'].values():
# possible to get no counts, in which case we can't calculate a max
if data['counts']:
# find the max value for the facet_ranges
data['max'] = max(data['counts'][1::2])
# solr returns a list of value, count, value, count
# use zip to convert into a list of two-tuples
# (thanks to https://stackoverflow.com/questions/14902686/turn-flat-list-into-two-tuples)
data['counts'] = list(zip(data['counts'][::2], data['counts'][1::2]))
return facets
def _clone(self, *args, **kwargs):
# extend clone to ensure range facets are preserved
clone = super(RangeSolrSearchQuery, self)._clone(klass=self.__class__,
*args, **kwargs)
clone.range_facets = self.range_facets.copy()
return clone
[docs]class SolrRangeSearchBackend(SolrSearchBackend):
# extend default solr backend to ensure facet ranges are accessible
# in the result for processing by RangeSolrSearchQuery
def _process_results(self, raw_results, *args, **kwargs):
results = super(SolrRangeSearchBackend, self)._process_results(raw_results,
*args, **kwargs)
if hasattr(raw_results, 'facets'):
results['facet_ranges'] = raw_results.facets.get('facet_ranges', {}),
return results
[docs] def build_schema(self, fields):
# haystack doesn't have any customization points for schema generation
# or types, and Solr won't allow tokenization/customization on
# the built string field; customize the generated schema here
# to use local 'string_en' solr field for fields ending in "_isort"
schema = super(SolrRangeSearchBackend, self).build_schema(fields)
for field_cfg in schema[1]:
if field_cfg['field_name'].endswith('_isort'):
field_cfg['type'] = 'string_en'
return schema
class RangeSolrEngine(SolrEngine):
# extend default solr engine to make range backend and query defaults
backend = SolrRangeSearchBackend
query = RangeSolrSearchQuery
[docs]def facet_sort_ignoreaccents(facets, *fields):
'''Update alpha facet so that sorting ignores accents.'''
# update alpha facet so that sorting ignores accents
# (can't be done in solr because then facets would display without accents)
if not facets:
return facets
for field in fields:
if field in facets['fields']:
facets['fields'][field].sort(key=lambda elem: unidecode(elem[0]))
return facets