]> git.0d.be Git - panikweb.git/blob - panikweb/search.py
search: include facets in previous & next links
[panikweb.git] / panikweb / search.py
1 import haystack.backends.solr_backend
2 from haystack.query import SearchQuerySet, RelatedSearchQuerySet
3 from haystack.constants import ID, DJANGO_CT, DJANGO_ID
4 from haystack.utils import get_identifier
5 from haystack.backends import EmptyResults
6
7 from pysolr import SolrError
8
9 from django import forms
10 from django.conf import settings
11 from django.utils.translation import ugettext_lazy as _
12
13 from emissions.models import Emission, Episode, NewsItem, SoundFile
14
15
16 class MoreLikeThisSearchQuerySet(SearchQuerySet):
17     def more_like_this(self, model_instance, **kwargs):
18         clone = self._clone()
19         clone.query.more_like_this(model_instance, **kwargs)
20         return clone
21
22
23 class CustomSolrSearchQuery(haystack.backends.solr_backend.SolrSearchQuery):
24     def more_like_this(self, model_instance, **kwargs):
25         self._more_like_this = True
26         self._mlt_instance = model_instance
27
28     def run_mlt(self, **kwargs):
29         """Builds and executes the query. Returns a list of search results."""
30         if self._more_like_this is False or self._mlt_instance is None:
31             raise MoreLikeThisError("No instance was provided to determine 'More Like This' results.")
32
33         additional_query_string = self.build_query()
34         search_kwargs = {
35             'start_offset': self.start_offset,
36             'result_class': self.result_class,
37             'models': self.models,
38             'mlt.fl': 'tags',
39         }
40
41         if self.end_offset is not None:
42             search_kwargs['end_offset'] = self.end_offset - self.start_offset
43
44         results = self.backend.more_like_this(self._mlt_instance, additional_query_string, **search_kwargs)
45         self._results = results.get('results', [])
46         self._hit_count = results.get('hits', 0)
47
48
49 class CustomSolrSearchBackend(haystack.backends.solr_backend.SolrSearchBackend):
50     def more_like_this(self, model_instance, additional_query_string=None,
51                        start_offset=0, end_offset=None, models=None,
52                        limit_to_registered_models=None, result_class=None, **kwargs):
53         from haystack import connections
54
55         # Deferred models will have a different class ("RealClass_Deferred_fieldname")
56         # which won't be in our registry:
57         model_klass = model_instance._meta.concrete_model
58
59         index = connections[self.connection_alias].get_unified_index().get_index(model_klass)
60         field_name = index.get_content_field()
61         params = {
62             'fl': '*,score',
63             'mlt.fl': 'text,title',
64             'mlt.qf': 'text^0.3 tags^2.0 title^1.0',
65         }
66
67         if start_offset is not None:
68             params['start'] = start_offset
69
70         if end_offset is not None:
71             params['rows'] = end_offset
72
73         narrow_queries = set()
74
75         if limit_to_registered_models is None:
76             limit_to_registered_models = getattr(settings, 'HAYSTACK_LIMIT_TO_REGISTERED_MODELS', True)
77
78         if models and len(models):
79             model_choices = sorted(['%s.%s' % (model._meta.app_label, model._meta.module_name) for model in models])
80         elif limit_to_registered_models:
81             # Using narrow queries, limit the results to only models handled
82             # with the current routers.
83             model_choices = self.build_models_list()
84         else:
85             model_choices = []
86
87         if len(model_choices) > 0:
88             if narrow_queries is None:
89                 narrow_queries = set()
90
91             narrow_queries.add('%s:(%s)' % (DJANGO_CT, ' OR '.join(model_choices)))
92
93         if additional_query_string:
94             narrow_queries.add(additional_query_string)
95
96         if narrow_queries:
97             params['fq'] = list(narrow_queries)
98
99         query = "%s:%s" % (ID, get_identifier(model_instance))
100
101         try:
102             raw_results = self.conn.more_like_this(query, field_name, **params)
103         except (IOError, SolrError) as e:
104             if not self.silently_fail:
105                 raise
106
107             self.log.error("Failed to fetch More Like This from Solr for document '%s': %s", query, e)
108             raw_results = EmptyResults()
109
110         return self._process_results(raw_results, result_class=result_class)
111
112
113 haystack.backends.solr_backend.SolrEngine.query = CustomSolrSearchQuery
114 haystack.backends.solr_backend.SolrEngine.backend = CustomSolrSearchBackend
115
116 import haystack.views
117 from haystack.views import search_view_factory, FacetedSearchView
118 from haystack.forms import FacetedSearchForm, SearchForm
119
120
121 class GlobalSearchForm(FacetedSearchForm):
122     def no_query_found(self):
123         sqs = super(GlobalSearchForm, self).no_query_found()
124         if self.selected_facets:
125             sqs = self.searchqueryset.all()
126             for facet in self.selected_facets:
127                 if ":" not in facet:
128                     continue
129                 field, value = facet.split(":", 1)
130                 if value:
131                     sqs = sqs.narrow(u'%s:"%s"' % (field, sqs.query.clean(value)))
132         return sqs
133
134
135 class SearchView(FacetedSearchView):
136     def extra_context(self):
137         context = super(SearchView, self).extra_context()
138         context['sectionName'] = 'Search'
139         if self.request.GET.getlist('selected_facets'):
140             context['facets_qs'] = '&selected_facets=' + '&'.join(self.request.GET.getlist('selected_facets'))
141         context['selected_categories'] = [
142                 x.split(':', 1)[1] for x in self.request.GET.getlist('selected_facets')
143                 if x.startswith('categories_exact')]
144         context['selected_tags'] = [
145                 x.split(':', 1)[1] for x in self.request.GET.getlist('selected_facets')
146                 if x.startswith('tags_exact')]
147         if 'categories' in context['facets'].get('fields', []):
148             context['facets']['fields']['categories'] = [x for x in
149                     context['facets']['fields']['categories'] if x[1] > 0]
150             context['facets']['fields']['categories'].sort()
151         if 'tags' in context['facets'].get('fields', []):
152             context['facets']['fields']['tags'] = [x for x in
153                     context['facets']['fields']['tags'] if x[1] > 0]
154         return context
155
156 sqs = SearchQuerySet().models(Emission, Episode, NewsItem).facet('categories').facet('tags')
157
158 view = search_view_factory(SearchView,
159         form_class=GlobalSearchForm,
160         searchqueryset=sqs)
161
162
163
164 class ListenArchivesForm(FacetedSearchForm):
165     q = forms.CharField(required=False, label='')
166
167     def no_query_found(self):
168         return self.searchqueryset.all()
169
170     def search(self):
171         sqs = super(ListenArchivesForm, self).search()
172         return sqs.load_all()
173
174
175 class ListenArchivesView(FacetedSearchView):
176     template = 'listen/archives.html'
177
178     def __init__(self):
179         sqs = RelatedSearchQuerySet().models(SoundFile).facet('format').facet('tags').order_by('-date')
180         super(ListenArchivesView, self).__init__(searchqueryset=sqs,
181                 form_class=ListenArchivesForm, results_per_page=20)
182
183     def extra_context(self):
184         context = super(ListenArchivesView, self).extra_context()
185         context['sectionName'] = "Listen"
186         if self.request.GET.getlist('selected_facets'):
187             context['facets_qs'] = '&selected_facets=' + '&'.join(self.request.GET.getlist('selected_facets'))
188         context['selected_format'] = [
189                 x.split(':', 1)[1] for x in self.request.GET.getlist('selected_facets')
190                 if x.startswith('format_exact')]
191         context['selected_tags'] = [
192                 x.split(':', 1)[1] for x in self.request.GET.getlist('selected_facets')
193                 if x.startswith('tags_exact')]
194         if 'format' in context['facets'].get('fields', []):
195             context['facets']['fields']['format'] = [x for x in
196                     context['facets']['fields']['format'] if x[1] > 0]
197             context['facets']['fields']['format'].sort()
198         if 'tags' in context['facets'].get('fields', []):
199             context['facets']['fields']['tags'] = [x for x in
200                     context['facets']['fields']['tags'] if x[1] > 0]
201         return context
202
203 listenArchives = search_view_factory(ListenArchivesView)
204
205
206 class NewsArchivesForm(FacetedSearchForm):
207     q = forms.CharField(required=False, label='')
208
209     def no_query_found(self):
210         return self.searchqueryset.all()
211
212     def search(self):
213         sqs = super(NewsArchivesForm, self).search()
214         return sqs.load_all()
215
216
217 class NewsArchivesView(FacetedSearchView):
218     template = 'news/archives.html'
219
220     def __init__(self):
221         sqs = RelatedSearchQuerySet().models(NewsItem).facet('news_categories').facet('tags').order_by('-date')
222         super(NewsArchivesView, self).__init__(searchqueryset=sqs,
223                 form_class=NewsArchivesForm, results_per_page=20)
224
225     def extra_context(self):
226         context = super(NewsArchivesView, self).extra_context()
227         if self.request.GET.getlist('selected_facets'):
228             context['facets_qs'] = '&selected_facets=' + '&'.join(self.request.GET.getlist('selected_facets'))
229         context['sectionName'] = 'News'
230         context['selected_news_categories'] = [
231                 x.split(':', 1)[1] for x in self.request.GET.getlist('selected_facets')
232                 if x.startswith('news_categories_exact')]
233         context['selected_tags'] = [
234                 x.split(':', 1)[1] for x in self.request.GET.getlist('selected_facets')
235                 if x.startswith('tags_exact')]
236         if 'news_categories' in context['facets'].get('fields', []):
237             context['facets']['fields']['news_categories'] = [x for x in
238                     context['facets']['fields']['news_categories'] if x[1] > 0]
239             context['facets']['fields']['news_categories'].sort()
240         if 'tags' in context['facets'].get('fields', []):
241             context['facets']['fields']['tags'] = [x for x in
242                     context['facets']['fields']['tags'] if x[1] > 0]
243         return context
244
245 newsArchives = search_view_factory(NewsArchivesView)