onix import/export
[wolnelektury.git] / src / search / views.py
1 # -*- coding: utf-8 -*-
2 # This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
3 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
4 #
5 from django.conf import settings
6 from django.shortcuts import render_to_response
7 from django.template import RequestContext
8 from django.views.decorators import cache
9 from django.http import HttpResponse, JsonResponse
10
11 from catalogue.utils import split_tags
12 from catalogue.models import Book, Tag
13 from search.index import Search, SearchResult
14 from suggest.forms import PublishingSuggestForm
15 import re
16 import json
17
18 from wolnelektury.utils import re_escape
19
20
21 def match_word_re(word):
22     if 'sqlite' in settings.DATABASES['default']['ENGINE']:
23         return r"\b%s\b" % word
24     elif 'mysql' in settings.DATABASES['default']['ENGINE']:
25         return "[[:<:]]%s[[:>:]]" % word
26
27
28 query_syntax_chars = re.compile(r"[\\/*:(){}]")
29
30
31 def remove_query_syntax_chars(query, replace=' '):
32     return query_syntax_chars.sub(replace, query)
33
34
35 def did_you_mean(query, tokens):
36     return query
37     # change = {}
38     # for t in tokens:
39     #     authors = Tag.objects.filter(category='author', name__iregex=match_word_re(t))
40     #     if len(authors) > 0:
41     #         continue
42
43     #     if False:
44     #         if not dictionary.check(t):
45     #             try:
46     #                 change_to = dictionary.suggest(t)[0].lower()
47     #                 if change_to != t.lower():
48     #                     change[t] = change_to
49     #             except IndexError:
50     #                 pass
51
52     # if change == {}:
53     #     return None
54
55     # for frm, to in change.items():
56     #     query = query.replace(frm, to)
57
58     # return query
59
60
61 @cache.never_cache
62 def hint(request):
63     prefix = request.GET.get('term', '')
64     if len(prefix) < 2:
65         return JsonResponse([], safe=False)
66
67     prefix = re_escape(' '.join(remove_query_syntax_chars(prefix).split()))
68
69     try:
70         limit = int(request.GET.get('max', ''))
71     except ValueError:
72         limit = 20
73     else:
74         if limit < 1:
75             limit = 20
76
77     authors = Tag.objects.filter(
78         category='author', name_pl__iregex='\m' + prefix).only('name', 'id', 'slug', 'category')
79     data = [
80         {
81             'label': author.name,
82             'id': author.id,
83             'url': author.get_absolute_url(),
84         }
85         for author in authors[:limit]
86     ]
87     if len(data) < limit:
88         data += [
89             {
90                 'label': b.title,
91                 'author': b.author_unicode(),
92                 'id': b.id,
93                 'url': b.get_absolute_url()
94             }
95             for b in Book.objects.filter(title__iregex='\m' + prefix)[:limit-len(data)]
96         ]
97     callback = request.GET.get('callback', None)
98     if callback:
99         return HttpResponse("%s(%s);" % (callback, json.dumps(data)),
100                             content_type="application/json; charset=utf-8")
101     else:
102         return JsonResponse(data, safe=False)
103
104
105 @cache.never_cache
106 def main(request):
107     query = request.GET.get('q', '')
108     query = ' '.join(query.split())
109     # filter out private use characters
110     import unicodedata
111     query = ''.join(ch for ch in query if unicodedata.category(ch) != 'Co')
112
113     if len(query) < 2:
114         return render_to_response(
115             'catalogue/search_too_short.html', {'prefix': query},
116             context_instance=RequestContext(request))
117     elif len(query) > 256:
118         return render_to_response(
119             'catalogue/search_too_long.html', {'prefix': query}, context_instance=RequestContext(request))
120
121     query = remove_query_syntax_chars(query)
122
123     words = query.split()
124     if len(words) > 10:
125         query = ' '.join(words[:10])
126
127     search = Search()
128
129     tags = search.hint_tags(query, pdcounter=True, prefix=False)
130     tags = split_tags(tags)
131
132     results_parts = []
133
134     search_fields = []
135     fieldsets = (
136         (['authors'], True),
137         (['title'], True),
138         (['metadata'], True),
139         (['text', 'themes_pl'], False),
140     )
141     for fieldset, is_book in fieldsets:
142         search_fields += fieldset
143         results_parts.append(search.search_words(words, search_fields, book=is_book))
144
145     results = []
146     ids_results = {}
147     for results_part in results_parts:
148         for result in sorted(SearchResult.aggregate(results_part), reverse=True):
149             book_id = result.book_id
150             if book_id in ids_results:
151                 ids_results[book_id].merge(result)
152             else:
153                 results.append(result)
154                 ids_results[book_id] = result
155
156     for result in results:
157         search.get_snippets(result, query, num=3)
158
159     suggestion = u''
160
161     def ensure_exists(r):
162         try:
163             return r.book
164         except Book.DoesNotExist:
165             return False
166
167     results = filter(ensure_exists, results)
168
169     if not results:
170         form = PublishingSuggestForm(initial={"books": query + ", "})
171         return render_to_response(
172             'catalogue/search_no_hits.html',
173             {
174                 'tags': tags,
175                 'prefix': query,
176                 'form': form,
177                 'did_you_mean': suggestion
178             },
179             context_instance=RequestContext(request))
180
181     return render_to_response(
182         'catalogue/search_multiple_hits.html',
183         {
184             'tags': tags['author'] + tags['kind'] + tags['genre'] + tags['epoch'] + tags['theme'],
185             'prefix': query,
186             'results': results,
187             'did_you_mean': suggestion
188         },
189         context_instance=RequestContext(request))