search
[wolnelektury.git] / src / search / views.py
1 # This file is part of Wolne Lektury, licensed under GNU Affero GPLv3 or later.
2 # Copyright © Fundacja Wolne Lektury. See NOTICE for more information.
3 #
4 from django.conf import settings
5 from django.shortcuts import render
6 from django.views.decorators import cache
7 from django.http import HttpResponse, JsonResponse
8 from sorl.thumbnail import get_thumbnail
9
10 import catalogue.models
11 import infopages.models
12 import social.models
13 from .forms import SearchFilters
14 import re
15 import json
16
17 from wolnelektury.utils import re_escape
18
19
20 query_syntax_chars = re.compile(r"[\\/*:(){}?.[\]+]")
21
22
23 def remove_query_syntax_chars(query, replace=' '):
24     return query_syntax_chars.sub(replace, query)
25
26
27 def get_hints(prefix, user=None, limit=10):
28     if not prefix: return []
29     data = []
30     if len(data) < limit:
31         authors = catalogue.models.Tag.objects.filter(
32             category='author', name_pl__iregex='\m' + prefix).only('name', 'id', 'slug', 'category')
33         data.extend([
34             {
35                 'type': 'author',
36                 'label': author.name,
37                 'url': author.get_absolute_url(),
38                 'img': get_thumbnail(author.photo, '72x72', crop='top').url if author.photo else '',
39             }
40             for author in authors[:limit - len(data)]
41         ])
42     
43     if user is not None and user.is_authenticated and len(data) < limit:
44         tags = social.models.UserList.objects.filter(
45             user=user, name__iregex='\m' + prefix).only('name', 'id', 'slug')
46         data.extend([
47             {
48                 'type': 'userlist',
49                 'label': tag.name,
50                 'url': tag.get_absolute_url(),
51             }
52             for tag in tags[:limit - len(data)]
53         ])
54     if len(data) < limit:
55         tags = catalogue.models.Tag.objects.filter(
56             category__in=('theme', 'genre', 'epoch', 'kind'), name_pl__iregex='\m' + prefix).only('name', 'id', 'slug', 'category')
57         data.extend([
58             {
59                 'type': tag.category,
60                 'label': tag.name,
61                 'url': tag.get_absolute_url(),
62             }
63             for tag in tags[:limit - len(data)]
64         ])
65     if len(data) < limit:
66         collections = catalogue.models.Collection.objects.filter(
67             title_pl__iregex='\m' + prefix).only('title', 'slug')
68         data.extend([
69             {
70                 'type': 'collection',
71                 'label': collection.title,
72                 'url': collection.get_absolute_url(),
73             }
74             for collection in collections[:limit - len(data)]
75         ])
76     if len(data) < limit:
77         for b in catalogue.models.Book.objects.filter(findable=True, title__iregex='\m' + prefix)[:limit-len(data)]:
78             author_str = b.author_unicode()
79             translator = b.translator()
80             if translator:
81                 author_str += ' (tłum. ' + translator + ')'
82             data.append(
83                 {
84                     'type': 'book',
85                     'label': b.title,
86                     'author': author_str,
87                     'url': b.get_absolute_url(),
88                     'img': get_thumbnail(b.cover_clean, '72x72').url if b.cover_clean else '',
89                 }
90             )
91     if len(data) < limit:
92         infos = infopages.models.InfoPage.objects.filter(
93             published=True,
94             findable=True,
95             title_pl__iregex='\m' + prefix).only('title', 'id', 'slug')
96         data.extend([
97             {
98                 'type': 'info',
99                 'label': info.title,
100                 'url': info.get_absolute_url(),
101             }
102             for info in infos[:limit - len(data)]
103         ])
104     return data
105
106
107 @cache.never_cache
108 def hint(request, mozhint=False, param='term'):
109     prefix = request.GET.get(param, '')
110     if len(prefix) < 2:
111         return JsonResponse([], safe=False)
112
113     prefix = re_escape(' '.join(remove_query_syntax_chars(prefix).split()))
114
115     try:
116         limit = int(request.GET.get('max', ''))
117     except ValueError:
118         limit = 20
119     else:
120         if limit < 1:
121             limit = 20
122
123     data = get_hints(
124         prefix,
125         user=request.user if request.user.is_authenticated else None,
126         limit=limit
127     )
128
129     if mozhint:
130         data = [
131             prefix,
132             [
133                 item['label']
134                 for item in data
135             ],
136             [
137                 item.get('author', '')
138                 for item in data
139             ],
140             [
141                 item['url']
142                 for item in data
143             ]
144         ]
145
146     callback = request.GET.get('callback', None)
147     if callback:
148         return HttpResponse("%s(%s);" % (callback, json.dumps(data)),
149                             content_type="application/json; charset=utf-8")
150     else:
151         return JsonResponse(data, safe=False)
152
153
154
155 @cache.never_cache
156 def search(request):
157     filters = SearchFilters(request.GET)
158     ctx = {
159         'title': 'Wynik wyszukiwania',
160         'query': request.GET.get('q', ''),
161         'filters': filters,
162     }
163     if filters.is_valid():
164         ctx['results'] = filters.results()
165         for k, v in ctx['results'].items():
166             if v:
167                 ctx['hasresults'] = True
168                 break
169     return render(request, 'search/results.html', ctx)