dee9c7774ecb35505c80086f1ae19024e6b3f7e5
[wolnelektury.git] / src / search / forms.py
1 # This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
2 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
3 #
4 from django.apps import apps
5 from django.conf import settings
6 from django.contrib.postgres.search import SearchHeadline, SearchQuery
7 from django import forms
8 from django.utils.translation import gettext_lazy as _
9 from catalogue.constants import LANGUAGES_3TO2
10 import catalogue.models
11 import pdcounter.models
12 import picture.models
13 from .fields import InlineRadioWidget
14 from .utils import UnaccentSearchQuery, UnaccentSearchVector
15
16
17 class SearchFilters(forms.Form):
18     q = forms.CharField(
19         required=False, widget=forms.HiddenInput(),
20         min_length=2, max_length=256,
21     )
22     format = forms.ChoiceField(required=False, choices=[
23         ('', 'wszystkie'),
24         ('text', 'tekst'),
25         ('audio', 'audiobook'),
26         ('daisy', 'Daisy'),
27         ('art', 'obraz'),
28         #('theme', 'motywy'),
29     ], widget=InlineRadioWidget())
30     lang = forms.ChoiceField(required=False)
31     epoch = forms.ChoiceField(required=False)
32     genre = forms.ChoiceField(required=False)
33     category = forms.ChoiceField(required=False, choices=[
34         ('', 'wszystkie'),
35         ('author', 'autor'),
36         #('translator', 'tłumacz'),
37         ('theme', 'motyw'),
38         ('genre', 'gatunek'),
39         ('book', 'tytuł'),
40         ('art', 'obraz'),
41         ('collection', 'kolekcja'),
42         ('quote', 'cytat'),
43     ], widget=InlineRadioWidget())
44
45     def __init__(self, *args, **kwargs):
46         super().__init__(*args, **kwargs)
47
48         langs = dict(settings.LANGUAGES)
49         self.fields['lang'].choices = [('', 'wszystkie')] + [
50             (
51                 b,
52                 langs.get(LANGUAGES_3TO2.get(b, b), b)
53             )
54             for b in catalogue.models.Book.objects.values_list(
55                     'language', flat=True
56             ).distinct().order_by()
57         ]
58         self.fields['epoch'].choices = [('', 'wszystkie')] + [
59             (b.slug, b.name)
60             for b in catalogue.models.Tag.objects.filter(category='epoch')
61         ]
62         self.fields['genre'].choices = [('', 'wszystkie')] + [
63             (b.slug, b.name)
64             for b in catalogue.models.Tag.objects.filter(category='genre')
65         ]
66
67     def get_querysets(self):
68         qs = {
69             'author': catalogue.models.Tag.objects.filter(category='author'),
70             'pdauthor': pdcounter.models.Author.objects.all(),
71             'theme': catalogue.models.Tag.objects.filter(category='theme'),
72             'genre': catalogue.models.Tag.objects.filter(category='genre'),
73             'collection': catalogue.models.Collection.objects.all(),
74             'book': catalogue.models.Book.objects.filter(findable=True),
75             'pdbook': pdcounter.models.BookStub.objects.all(),
76             'snippet': catalogue.models.Snippet.objects.filter(book__findable=True),
77             'art': picture.models.Picture.objects.all(),
78             # art pieces
79         }
80         if self.cleaned_data['category']:
81             c = self.cleaned_data['category']
82             if c != 'author':
83                 qs['author'] = qs['author'].none()
84                 qs['pdauthor'] = qs['pdauthor'].none()
85             if c != 'theme': qs['theme'] = qs['theme'].none()
86             if c != 'genre': qs['genre'] = qs['genre'].none()
87             if c != 'collection': qs['collection'] = qs['collection'].none()
88             if c != 'book':
89                 qs['book'] = qs['book'].none()
90                 qs['pdbook'] = qs['pdbook'].none()
91             if c != 'quote': qs['snippet'] = qs['snippet'].none()
92             if c != 'art': qs['art'] = qs['art'].none()
93             qs['art'] = picture.models.Picture.objects.none()
94
95         if self.cleaned_data['format']:
96             c = self.cleaned_data['format']
97             qs['author'] = qs['author'].none()
98             qs['pdauthor'] = qs['pdauthor'].none()
99             qs['theme'] = qs['theme'].none()
100             qs['genre'] = qs['genre'].none()
101             qs['collection'] = qs['collection'].none()
102             if c == 'art':
103                 qs['book'] = qs['book'].none()
104                 qs['pdbook'] = qs['pdbook'].none()
105                 qs['snippet'] = qs['snippet'].none()
106             if c in ('text', 'audio', 'daisy'):
107                 qs['art'] = qs['art'].none()
108                 if c == 'audio':
109                     qs['book'] = qs['book'].filter(media__type='mp3')
110                     qs['pdbook'] = qs['book'].none()
111                     qs['snippet'] = qs['snippet'].filter(book__media__type='mp3')
112                 elif c == 'daisy':
113                     qs['book'] = qs['book'].filter(media__type='daisy')
114                     qs['snippet'] = qs['snippet'].filter(book__media__type='daisy')
115
116         if self.cleaned_data['lang']:
117             qs['author'] = qs['author'].none()
118             qs['pdauthor'] = qs['pdauthor'].none()
119             qs['theme'] = qs['theme'].none()
120             qs['genre'] = qs['genre'].none()
121             qs['art'] = qs['art'].none()
122             qs['collection'] = qs['collection'].none()
123             qs['book'] = qs['book'].filter(language=self.cleaned_data['lang'])
124             qs['pdbook'] = qs['pdbook'].none()
125             qs['snippet'] = qs['snippet'].filter(book__language=self.cleaned_data['lang'])
126
127         for tag_cat in ('epoch', 'genre'):
128             c = self.cleaned_data[tag_cat]
129             if c:
130                 # FIXME nonexistent
131                 t = catalogue.models.Tag.objects.get(category=tag_cat, slug=c)
132                 qs['author'] = qs['author'].none()
133                 qs['pdauthor'] = qs['pdauthor'].none()
134                 qs['theme'] = qs['theme'].none()
135                 qs['genre'] = qs['genre'].none()
136                 qs['collection'] = qs['collection'].none()
137                 qs['book'] = qs['book'].filter(tag_relations__tag=t)
138                 qs['pdbook'] = qs['pdbook'].none()
139                 qs['snippet'] = qs['snippet'].filter(book__tag_relations__tag=t)
140                 qs['art'] = qs['art'].filter(tag_relations__tag=t)
141             
142         return qs
143
144     def results(self):
145         qs = self.get_querysets()
146         query = self.cleaned_data['q']
147         squery = UnaccentSearchQuery(query, config=settings.SEARCH_CONFIG)
148         query = SearchQuery(query, config=settings.SEARCH_CONFIG)
149         books = qs['book'].annotate(
150             search_vector=UnaccentSearchVector('title')
151         ).filter(search_vector=squery)
152         books = books.exclude(ancestor__in=books).order_by('-popularity__count')
153
154         snippets = qs['snippet'].filter(search_vector=squery).annotate(
155                     headline=SearchHeadline(
156                         'text',
157                         query,
158                         config=settings.SEARCH_CONFIG,
159                         start_sel='<strong>',
160                         stop_sel='</strong>',
161                     )
162                 ).order_by('-book__popularity__count', 'sec')[:100]
163         snippets_by_book = {}
164         for snippet in snippets:
165             snippet_list = snippets_by_book.setdefault(snippet.book, [])
166             if len(snippet_list) < 3:
167                 snippet_list.append(snippet)
168
169         return {
170             'author': qs['author'].annotate(
171                 search_vector=UnaccentSearchVector('name_pl')
172             ).filter(search_vector=squery),
173             'theme': qs['theme'].annotate(
174                 search_vector=UnaccentSearchVector('name_pl')
175             ).filter(search_vector=squery),
176             'genre': qs['genre'].annotate(
177                 search_vector=UnaccentSearchVector('name_pl')
178             ).filter(search_vector=squery),
179             'collection': qs['collection'].annotate(
180                 search_vector=UnaccentSearchVector('title')
181             ).filter(search_vector=squery),
182             'book': books[:100],
183             'art': qs['art'].annotate(
184                 search_vector=UnaccentSearchVector('title')
185             ).filter(search_vector=squery)[:100],
186             'snippet': snippets_by_book,
187             'pdauthor': pdcounter.models.Author.search(squery, qs=qs['pdauthor']),
188             'pdbook': pdcounter.models.BookStub.search(squery, qs=qs['pdbook']),
189         }
190