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