Use actual sync for bookmarks.
[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                 'slug': author.slug,
40                 'id': author.id,
41             }
42             for author in authors[:limit - len(data)]
43         ])
44     
45     if user is not None and user.is_authenticated and len(data) < limit:
46         tags = social.models.UserList.objects.filter(
47             user=user, name__iregex='\m' + prefix).only('name', 'id', 'slug')
48         data.extend([
49             {
50                 'type': 'userlist',
51                 'label': tag.name,
52                 'url': tag.get_absolute_url(),
53                 'slug': tag.slug,
54             }
55             for tag in tags[:limit - len(data)]
56         ])
57     if len(data) < limit:
58         tags = catalogue.models.Tag.objects.filter(
59             category__in=('theme', 'genre', 'epoch', 'kind'), name_pl__iregex='\m' + prefix).only('name', 'id', 'slug', 'category')
60         data.extend([
61             {
62                 'type': tag.category,
63                 'label': tag.name,
64                 'url': tag.get_absolute_url(),
65                 'slug': tag.slug,
66                 'id': tag.id,
67             }
68             for tag in tags[:limit - len(data)]
69         ])
70     if len(data) < limit:
71         collections = catalogue.models.Collection.objects.filter(
72             title_pl__iregex='\m' + prefix).only('title', 'slug')
73         data.extend([
74             {
75                 'type': 'collection',
76                 'label': collection.title,
77                 'url': collection.get_absolute_url(),
78                 'slug': collection.slug,
79             }
80             for collection in collections[:limit - len(data)]
81         ])
82     if len(data) < limit:
83         for b in catalogue.models.Book.objects.filter(findable=True, title__iregex='\m' + prefix)[:limit-len(data)]:
84             author_str = b.author_unicode()
85             translator = b.translator()
86             if translator:
87                 author_str += ' (tłum. ' + translator + ')'
88             data.append(
89                 {
90                     'type': 'book',
91                     'label': b.title,
92                     'author': author_str,
93                     'url': b.get_absolute_url(),
94                     'img': get_thumbnail(b.cover_clean, '72x72').url if b.cover_clean else '',
95                     'slug': b.slug,
96                 }
97             )
98     if len(data) < limit:
99         infos = infopages.models.InfoPage.objects.filter(
100             published=True,
101             findable=True,
102             title_pl__iregex='\m' + prefix).only('title', 'id', 'slug')
103         data.extend([
104             {
105                 'type': 'info',
106                 'label': info.title,
107                 'url': info.get_absolute_url(),
108                 'slug': info.slug,
109             }
110             for info in infos[:limit - len(data)]
111         ])
112     return data
113
114
115 @cache.never_cache
116 def hint(request, mozhint=False, param='term'):
117     prefix = request.GET.get(param, '')
118     if len(prefix) < 2:
119         return JsonResponse([], safe=False)
120
121     prefix = re_escape(' '.join(remove_query_syntax_chars(prefix).split()))
122
123     try:
124         limit = int(request.GET.get('max', ''))
125     except ValueError:
126         limit = 20
127     else:
128         if limit < 1:
129             limit = 20
130
131     data = get_hints(
132         prefix,
133         user=request.user if request.user.is_authenticated else None,
134         limit=limit
135     )
136
137     if mozhint:
138         data = [
139             prefix,
140             [
141                 item['label']
142                 for item in data
143             ],
144             [
145                 item.get('author', '')
146                 for item in data
147             ],
148             [
149                 item['url']
150                 for item in data
151             ]
152         ]
153
154     callback = request.GET.get('callback', None)
155     if callback:
156         return HttpResponse("%s(%s);" % (callback, json.dumps(data)),
157                             content_type="application/json; charset=utf-8")
158     else:
159         return JsonResponse(data, safe=False)
160
161
162
163 @cache.never_cache
164 def search(request):
165     filters = SearchFilters(request.GET)
166     ctx = {
167         'title': 'Wynik wyszukiwania',
168         'query': request.GET.get('q', ''),
169         'filters': filters,
170     }
171     if filters.is_valid():
172         ctx['results'] = filters.results()
173         for k, v in ctx['results'].items():
174             if v:
175                 ctx['hasresults'] = True
176                 break
177     return render(request, 'search/results.html', ctx)