dc56f59b6851ebba16d70c130c3b300c07955c8b
[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     ], widget=InlineRadioWidget())
29     lang = forms.ChoiceField(required=False)
30     epoch = forms.ChoiceField(required=False)
31     genre = forms.ChoiceField(required=False)
32     category = forms.ChoiceField(required=False, choices=[
33         ('', _('wszystkie')),
34         ('author', _('autor')),
35         #('translator', _('tłumacz')),
36         ('theme', _('motyw')),
37         ('genre', _('gatunek')),
38         ('book', _('tytuł')),
39         ('art', _('obraz')),
40         ('collection', _('kolekcja')),
41         ('quote', _('cytat')),
42     ], widget=InlineRadioWidget())
43
44     def __init__(self, *args, **kwargs):
45         super().__init__(*args, **kwargs)
46
47         langs = dict(settings.LANGUAGES)
48         self.fields['lang'].choices = [('', _('wszystkie'))] + [
49             (
50                 b,
51                 langs.get(LANGUAGES_3TO2.get(b, b), b)
52             )
53             for b in catalogue.models.Book.objects.values_list(
54                     'language', flat=True
55             ).distinct().order_by()
56         ]
57         self.fields['epoch'].choices = [('', _('wszystkie'))] + [
58             (b.slug, b.name)
59             for b in catalogue.models.Tag.objects.filter(category='epoch')
60         ]
61         self.fields['genre'].choices = [('', _('wszystkie'))] + [
62             (b.slug, b.name)
63             for b in catalogue.models.Tag.objects.filter(category='genre')
64         ]
65
66     def get_querysets(self):
67         qs = {
68             'author': catalogue.models.Tag.objects.filter(category='author'),
69             'pdauthor': pdcounter.models.Author.objects.all(),
70             'theme': catalogue.models.Tag.objects.filter(category='theme'),
71             'genre': catalogue.models.Tag.objects.filter(category='genre'),
72             'collection': catalogue.models.Collection.objects.all(),
73             'book': catalogue.models.Book.objects.filter(findable=True),
74             'pdbook': pdcounter.models.BookStub.objects.all(),
75             'snippet': catalogue.models.Snippet.objects.filter(book__findable=True),
76             'art': picture.models.Picture.objects.all(),
77             # art pieces
78         }
79         if self.cleaned_data['category']:
80             c = self.cleaned_data['category']
81             if c != 'author':
82                 qs['author'] = qs['author'].none()
83                 qs['pdauthor'] = qs['pdauthor'].none()
84             if c != 'theme': qs['theme'] = qs['theme'].none()
85             if c != 'genre': qs['genre'] = qs['genre'].none()
86             if c != 'collection': qs['collection'] = qs['collection'].none()
87             if c != 'book':
88                 qs['book'] = qs['book'].none()
89                 qs['pdbook'] = qs['pdbook'].none()
90             if c != 'quote': qs['snippet'] = qs['snippet'].none()
91             if c != 'art': qs['art'] = qs['art'].none()
92             qs['art'] = picture.models.Picture.objects.none()
93
94         if self.cleaned_data['format']:
95             c = self.cleaned_data['format']
96             qs['author'] = qs['author'].none()
97             qs['pdauthor'] = qs['pdauthor'].none()
98             qs['theme'] = qs['theme'].none()
99             qs['genre'] = qs['genre'].none()
100             qs['collection'] = qs['collection'].none()
101             if c == 'art':
102                 qs['book'] = qs['book'].none()
103                 qs['pdbook'] = qs['pdbook'].none()
104                 qs['snippet'] = qs['snippet'].none()
105             if c in ('text', 'audio', 'daisy'):
106                 qs['art'] = qs['art'].none()
107                 if c == 'audio':
108                     qs['book'] = qs['book'].filter(media__type='mp3')
109                     qs['pdbook'] = qs['book'].none()
110                     qs['snippet'] = qs['snippet'].filter(book__media__type='mp3')
111                 elif c == 'daisy':
112                     qs['book'] = qs['book'].filter(media__type='daisy')
113                     qs['snippet'] = qs['snippet'].filter(book__media__type='daisy')
114
115         if self.cleaned_data['lang']:
116             qs['author'] = qs['author'].none()
117             qs['pdauthor'] = qs['pdauthor'].none()
118             qs['theme'] = qs['theme'].none()
119             qs['genre'] = qs['genre'].none()
120             qs['art'] = qs['art'].none()
121             qs['collection'] = qs['collection'].none()
122             qs['book'] = qs['book'].filter(language=self.cleaned_data['lang'])
123             qs['pdbook'] = qs['pdbook'].none()
124             qs['snippet'] = qs['snippet'].filter(book__language=self.cleaned_data['lang'])
125
126         for tag_cat in ('epoch', 'genre'):
127             c = self.cleaned_data[tag_cat]
128             if c:
129                 # FIXME nonexistent
130                 t = catalogue.models.Tag.objects.get(category=tag_cat, slug=c)
131                 qs['author'] = qs['author'].none()
132                 qs['pdauthor'] = qs['pdauthor'].none()
133                 qs['theme'] = qs['theme'].none()
134                 qs['genre'] = qs['genre'].none()
135                 qs['collection'] = qs['collection'].none()
136                 qs['book'] = qs['book'].filter(tag_relations__tag=t)
137                 qs['pdbook'] = qs['pdbook'].none()
138                 qs['snippet'] = qs['snippet'].filter(book__tag_relations__tag=t)
139                 qs['art'] = qs['art'].filter(tag_relations__tag=t)
140             
141         return qs
142
143     def results(self):
144         qs = self.get_querysets()
145         query = self.cleaned_data['q']
146         squery = UnaccentSearchQuery(query, config=settings.SEARCH_CONFIG)
147         query = SearchQuery(query, config=settings.SEARCH_CONFIG)
148         books = qs['book'].annotate(
149             search_vector=UnaccentSearchVector('title')
150         ).filter(search_vector=squery)
151         books = books.exclude(ancestor__in=books).order_by('-popularity__count')
152
153         snippets = qs['snippet'].filter(search_vector=squery).annotate(
154                     headline=SearchHeadline(
155                         'text',
156                         query,
157                         config=settings.SEARCH_CONFIG,
158                         start_sel='<strong>',
159                         stop_sel='</strong>',
160                     )
161                 ).order_by('-book__popularity__count', 'sec')[:100]
162         snippets_by_book = {}
163         for snippet in snippets:
164             snippet_list = snippets_by_book.setdefault(snippet.book, [])
165             if len(snippet_list) < 3:
166                 snippet_list.append(snippet)
167
168         return {
169             'author': qs['author'].annotate(
170                 search_vector=UnaccentSearchVector('name_pl')
171             ).filter(search_vector=squery),
172             'theme': qs['theme'].annotate(
173                 search_vector=UnaccentSearchVector('name_pl')
174             ).filter(search_vector=squery),
175             'genre': qs['genre'].annotate(
176                 search_vector=UnaccentSearchVector('name_pl')
177             ).filter(search_vector=squery),
178             'collection': qs['collection'].annotate(
179                 search_vector=UnaccentSearchVector('title')
180             ).filter(search_vector=squery),
181             'book': books[:100],
182             'art': qs['art'].annotate(
183                 search_vector=UnaccentSearchVector('title')
184             ).filter(search_vector=squery)[:100],
185             'snippet': snippets_by_book,
186             'pdauthor': pdcounter.models.Author.search(squery, qs=qs['pdauthor']),
187             'pdbook': pdcounter.models.BookStub.search(squery, qs=qs['pdbook']),
188         }
189