search.py 9.65 KB
Newer Older
1
import haystack.backends.solr_backend
fred's avatar
fred committed
2
from haystack.query import SearchQuerySet, RelatedSearchQuerySet
3 4
from haystack.constants import ID, DJANGO_CT, DJANGO_ID
from haystack.utils import get_identifier
fred's avatar
fred committed
5
from haystack.backends import EmptyResults
6

fred's avatar
fred committed
7 8
from pysolr import SolrError

fred's avatar
fred committed
9
from django import forms
10
from django.conf import settings
fred's avatar
fred committed
11
from django.utils.translation import ugettext_lazy as _
12

fred's avatar
fred committed
13 14
from emissions.models import Emission, Episode, NewsItem, SoundFile

15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78

class MoreLikeThisSearchQuerySet(SearchQuerySet):
    def more_like_this(self, model_instance, **kwargs):
        clone = self._clone()
        clone.query.more_like_this(model_instance, **kwargs)
        return clone


class CustomSolrSearchQuery(haystack.backends.solr_backend.SolrSearchQuery):
    def more_like_this(self, model_instance, **kwargs):
        self._more_like_this = True
        self._mlt_instance = model_instance

    def run_mlt(self, **kwargs):
        """Builds and executes the query. Returns a list of search results."""
        if self._more_like_this is False or self._mlt_instance is None:
            raise MoreLikeThisError("No instance was provided to determine 'More Like This' results.")

        additional_query_string = self.build_query()
        search_kwargs = {
            'start_offset': self.start_offset,
            'result_class': self.result_class,
            'models': self.models,
            'mlt.fl': 'tags',
        }

        if self.end_offset is not None:
            search_kwargs['end_offset'] = self.end_offset - self.start_offset

        results = self.backend.more_like_this(self._mlt_instance, additional_query_string, **search_kwargs)
        self._results = results.get('results', [])
        self._hit_count = results.get('hits', 0)


class CustomSolrSearchBackend(haystack.backends.solr_backend.SolrSearchBackend):
    def more_like_this(self, model_instance, additional_query_string=None,
                       start_offset=0, end_offset=None, models=None,
                       limit_to_registered_models=None, result_class=None, **kwargs):
        from haystack import connections

        # Deferred models will have a different class ("RealClass_Deferred_fieldname")
        # which won't be in our registry:
        model_klass = model_instance._meta.concrete_model

        index = connections[self.connection_alias].get_unified_index().get_index(model_klass)
        field_name = index.get_content_field()
        params = {
            'fl': '*,score',
            'mlt.fl': 'text,title',
            'mlt.qf': 'text^0.3 tags^2.0 title^1.0',
        }

        if start_offset is not None:
            params['start'] = start_offset

        if end_offset is not None:
            params['rows'] = end_offset

        narrow_queries = set()

        if limit_to_registered_models is None:
            limit_to_registered_models = getattr(settings, 'HAYSTACK_LIMIT_TO_REGISTERED_MODELS', True)

        if models and len(models):
79
            model_choices = sorted(['%s.%s' % (model._meta.app_label, model._meta.model_name) for model in models])
80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115
        elif limit_to_registered_models:
            # Using narrow queries, limit the results to only models handled
            # with the current routers.
            model_choices = self.build_models_list()
        else:
            model_choices = []

        if len(model_choices) > 0:
            if narrow_queries is None:
                narrow_queries = set()

            narrow_queries.add('%s:(%s)' % (DJANGO_CT, ' OR '.join(model_choices)))

        if additional_query_string:
            narrow_queries.add(additional_query_string)

        if narrow_queries:
            params['fq'] = list(narrow_queries)

        query = "%s:%s" % (ID, get_identifier(model_instance))

        try:
            raw_results = self.conn.more_like_this(query, field_name, **params)
        except (IOError, SolrError) as e:
            if not self.silently_fail:
                raise

            self.log.error("Failed to fetch More Like This from Solr for document '%s': %s", query, e)
            raw_results = EmptyResults()

        return self._process_results(raw_results, result_class=result_class)


haystack.backends.solr_backend.SolrEngine.query = CustomSolrSearchQuery
haystack.backends.solr_backend.SolrEngine.backend = CustomSolrSearchBackend

fred's avatar
fred committed
116
import haystack.views
fred's avatar
fred committed
117
from haystack.views import search_view_factory, FacetedSearchView
fred's avatar
fred committed
118
from haystack.forms import FacetedSearchForm, SearchForm
fred's avatar
fred committed
119 120


fred's avatar
fred committed
121 122 123 124 125 126 127 128 129 130 131 132 133 134
class GlobalSearchForm(FacetedSearchForm):
    def no_query_found(self):
        sqs = super(GlobalSearchForm, self).no_query_found()
        if self.selected_facets:
            sqs = self.searchqueryset.all()
            for facet in self.selected_facets:
                if ":" not in facet:
                    continue
                field, value = facet.split(":", 1)
                if value:
                    sqs = sqs.narrow(u'%s:"%s"' % (field, sqs.query.clean(value)))
        return sqs


fred's avatar
fred committed
135 136 137
class SearchView(FacetedSearchView):
    def extra_context(self):
        context = super(SearchView, self).extra_context()
138 139
        if self.request.GET.getlist('selected_facets'):
            context['facets_qs'] = '&selected_facets=' + '&'.join(self.request.GET.getlist('selected_facets'))
fred's avatar
fred committed
140 141 142 143 144 145
        context['selected_categories'] = [
                x.split(':', 1)[1] for x in self.request.GET.getlist('selected_facets')
                if x.startswith('categories_exact')]
        context['selected_tags'] = [
                x.split(':', 1)[1] for x in self.request.GET.getlist('selected_facets')
                if x.startswith('tags_exact')]
fred's avatar
fred committed
146 147 148 149 150 151 152
        if 'categories' in context['facets'].get('fields', []):
            context['facets']['fields']['categories'] = [x for x in
                    context['facets']['fields']['categories'] if x[1] > 0]
            context['facets']['fields']['categories'].sort()
        if 'tags' in context['facets'].get('fields', []):
            context['facets']['fields']['tags'] = [x for x in
                    context['facets']['fields']['tags'] if x[1] > 0]
fred's avatar
fred committed
153 154
        return context

fred's avatar
fred committed
155
sqs = SearchQuerySet().models(Emission, Episode, NewsItem).facet('categories').facet('tags')
fred's avatar
fred committed
156 157

view = search_view_factory(SearchView,
fred's avatar
fred committed
158
        form_class=GlobalSearchForm,
fred's avatar
fred committed
159
        searchqueryset=sqs)
fred's avatar
fred committed
160 161 162



fred's avatar
fred committed
163
class ListenArchivesForm(FacetedSearchForm):
fred's avatar
fred committed
164
    q = forms.CharField(required=False, label='')
fred's avatar
fred committed
165

fred's avatar
fred committed
166 167 168 169 170 171 172 173
    def no_query_found(self):
        return self.searchqueryset.all()

    def search(self):
        sqs = super(ListenArchivesForm, self).search()
        return sqs.load_all()


fred's avatar
fred committed
174
class ListenArchivesView(FacetedSearchView):
fred's avatar
fred committed
175 176 177
    template = 'listen/archives.html'

    def __init__(self):
178
        sqs = RelatedSearchQuerySet().models(SoundFile).facet('format').facet('tags').order_by('-date')
fred's avatar
fred committed
179 180 181 182 183
        super(ListenArchivesView, self).__init__(searchqueryset=sqs,
                form_class=ListenArchivesForm, results_per_page=20)

    def extra_context(self):
        context = super(ListenArchivesView, self).extra_context()
184 185
        if self.request.GET.getlist('selected_facets'):
            context['facets_qs'] = '&selected_facets=' + '&'.join(self.request.GET.getlist('selected_facets'))
186
        context['selected_format'] = [
fred's avatar
fred committed
187
                x.split(':', 1)[1] for x in self.request.GET.getlist('selected_facets')
188
                if x.startswith('format_exact')]
fred's avatar
fred committed
189 190 191
        context['selected_tags'] = [
                x.split(':', 1)[1] for x in self.request.GET.getlist('selected_facets')
                if x.startswith('tags_exact')]
192 193 194 195
        if 'format' in context['facets'].get('fields', []):
            context['facets']['fields']['format'] = [x for x in
                    context['facets']['fields']['format'] if x[1] > 0]
            context['facets']['fields']['format'].sort()
fred's avatar
fred committed
196 197 198
        if 'tags' in context['facets'].get('fields', []):
            context['facets']['fields']['tags'] = [x for x in
                    context['facets']['fields']['tags'] if x[1] > 0]
fred's avatar
fred committed
199 200 201
        return context

listenArchives = search_view_factory(ListenArchivesView)
fred's avatar
fred committed
202 203 204


class NewsArchivesForm(FacetedSearchForm):
fred's avatar
fred committed
205
    q = forms.CharField(required=False, label='')
fred's avatar
fred committed
206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224

    def no_query_found(self):
        return self.searchqueryset.all()

    def search(self):
        sqs = super(NewsArchivesForm, self).search()
        return sqs.load_all()


class NewsArchivesView(FacetedSearchView):
    template = 'news/archives.html'

    def __init__(self):
        sqs = RelatedSearchQuerySet().models(NewsItem).facet('news_categories').facet('tags').order_by('-date')
        super(NewsArchivesView, self).__init__(searchqueryset=sqs,
                form_class=NewsArchivesForm, results_per_page=20)

    def extra_context(self):
        context = super(NewsArchivesView, self).extra_context()
225 226
        if self.request.GET.getlist('selected_facets'):
            context['facets_qs'] = '&selected_facets=' + '&'.join(self.request.GET.getlist('selected_facets'))
fred's avatar
fred committed
227 228 229 230 231 232
        context['selected_news_categories'] = [
                x.split(':', 1)[1] for x in self.request.GET.getlist('selected_facets')
                if x.startswith('news_categories_exact')]
        context['selected_tags'] = [
                x.split(':', 1)[1] for x in self.request.GET.getlist('selected_facets')
                if x.startswith('tags_exact')]
fred's avatar
fred committed
233 234 235 236 237 238 239
        if 'news_categories' in context['facets'].get('fields', []):
            context['facets']['fields']['news_categories'] = [x for x in
                    context['facets']['fields']['news_categories'] if x[1] > 0]
            context['facets']['fields']['news_categories'].sort()
        if 'tags' in context['facets'].get('fields', []):
            context['facets']['fields']['tags'] = [x for x in
                    context['facets']['fields']['tags'] if x[1] > 0]
fred's avatar
fred committed
240 241 242
        return context

newsArchives = search_view_factory(NewsArchivesView)