7efd7479d0f34dcd0562da14bcc9c6fbdc381186
[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.contrib.postgres.search import SearchHeadline, SearchRank, SearchQuery
6 from django import forms
7 from django.utils.translation import gettext_lazy as _
8
9 from .fields import JQueryAutoCompleteSearchField, InlineRadioWidget
10 from .utils import build_search_query
11
12
13 class SearchForm(forms.Form):
14     q = JQueryAutoCompleteSearchField(label=_('Search'))
15     # {'minChars': 2, 'selectFirst': True, 'cacheLength': 50, 'matchContains': "word"})
16
17     def __init__(self, source, *args, **kwargs):
18         kwargs['auto_id'] = False
19         super(SearchForm, self).__init__(*args, **kwargs)
20         self.fields['q'].widget.attrs['id'] = 'search'
21         self.fields['q'].widget.attrs['autocomplete'] = 'off'
22         self.fields['q'].widget.attrs['data-source'] = source
23         if 'q' not in self.data:
24             self.fields['q'].widget.attrs['placeholder'] = _('title, author, epoch, kind, genre, phrase')
25
26
27 class SearchFilters(forms.Form):
28     q = forms.CharField(required=False, widget=forms.HiddenInput())
29     format = forms.ChoiceField(required=False, choices=[
30         ('', 'wszystkie'),
31         ('text', 'tekst'),
32         ('audio', 'audiobook'),
33         ('daisy', 'Daisy'),
34         ('art', 'obraz'),
35         #('theme', 'motywy'),
36     ], widget=InlineRadioWidget())
37     lang = forms.ChoiceField(required=False)
38     epoch = forms.ChoiceField(required=False)
39     genre = forms.ChoiceField(required=False)
40     category = forms.ChoiceField(required=False, choices=[
41         ('', 'wszystkie'),
42         ('author', 'autor'),
43         #('translator', 'tłumacz'),
44         ('theme', 'motyw'),
45         ('genre', 'gatunek'),
46         ('book', 'tytuł'),
47         ('art', 'obraz'),
48         ('collection', 'kolekcja'),
49         ('quote', 'cytat'),
50     ], widget=InlineRadioWidget())
51
52     def __init__(self, *args, **kwargs):
53         super().__init__(*args, **kwargs)
54         from catalogue.models import Book, Tag
55
56         self.fields['lang'].choices = [('', 'wszystkie')] + [
57             (b, b)
58             for b in Book.objects.values_list(
59                     'language', flat=True
60             ).distinct().order_by()
61         ]
62         self.fields['epoch'].choices = [('', 'wszystkie')] + [
63             (b.slug, b.name)
64             for b in Tag.objects.filter(category='epoch')
65         ]
66         self.fields['genre'].choices = [('', 'wszystkie')] + [
67             (b.slug, b.name)
68             for b in Tag.objects.filter(category='genre')
69         ]
70
71     def get_querysets(self):
72         Tag = apps.get_model('catalogue', 'Tag')
73         Book = apps.get_model('catalogue', 'Book')
74         Picture = apps.get_model('picture', 'Picture')
75         Snippet = apps.get_model('catalogue', 'Snippet')
76         Collection = apps.get_model('catalogue', 'Collection')
77         qs = {
78             'author': Tag.objects.filter(category='author'),
79             'theme': Tag.objects.filter(category='theme'),
80             'genre': Tag.objects.filter(category='genre'),
81             'collection': Collection.objects.all(),
82             'book': Book.objects.all(), #findable
83             'snippet': Snippet.objects.all(),
84             'art': Picture.objects.all(),
85             # art pieces
86             # pdbooks
87             # pdauthors
88         }
89         if self.cleaned_data['category']:
90             c = self.cleaned_data['category']
91             if c != 'author': qs['author'] = Tag.objects.none()
92             if c != 'theme': qs['theme'] = Tag.objects.none()
93             if c != 'genre': qs['genre'] = Tag.objects.none()
94             if c != 'collection': qs['collection'] = Collection.objects.none()
95             if c != 'book': qs['book'] = Book.objects.none()
96             if c != 'quote': qs['snippet'] = Snippet.objects.none()
97             if c != 'art': qs['art'] = Picture.objects.none()
98             qs['art'] = Picture.objects.none()
99
100         if self.cleaned_data['format']:
101             c = self.cleaned_data['format']
102             qs['author'] = Tag.objects.none()
103             qs['theme'] = Tag.objects.none()
104             qs['genre'] = Tag.objects.none()
105             qs['collection'] = Collection.objects.none()
106             if c == 'art':
107                 qs['book'] = Book.objects.none()
108                 qs['snippet'] = Snippet.objects.none()
109             if c in ('text', 'audio', 'daisy'):
110                 qs['art'] = Picture.objects.none()
111                 if c == 'audio':
112                     qs['book'] = qs['book'].filter(media__type='mp3')
113                     qs['snippet'] = qs['snippet'].filter(book__media__type='mp3')
114                 elif c == 'daisy':
115                     qs['book'] = qs['book'].filter(media__type='daisy')
116                     qs['snippet'] = qs['snippet'].filter(book__media__type='daisy')
117
118         if self.cleaned_data['lang']:
119             qs['author'] = Tag.objects.none()
120             qs['theme'] = Tag.objects.none()
121             qs['genre'] = Tag.objects.none()
122             qs['art'] = Picture.objects.none()
123             qs['collection'] = Collection.objects.none()
124             qs['book'] = qs['book'].filter(language=self.cleaned_data['lang'])
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 = Tag.objects.get(category=tag_cat, slug=c)
132                 qs['author'] = Tag.objects.none()
133                 qs['theme'] = Tag.objects.none()
134                 qs['genre'] = Tag.objects.none()
135                 qs['collection'] = Collection.objects.none()
136                 qs['book'] = qs['book'].filter(tag_relations__tag=t)
137                 qs['snippet'] = qs['snippet'].filter(book__tag_relations__tag=t)
138                 qs['art'] = qs['art'].filter(tag_relations__tag=t)
139             
140         return qs
141
142     def results(self):
143         qs = self.get_querysets()
144         query = self.cleaned_data['q']
145         squery = build_search_query(query, config='polish')
146         query = SearchQuery(query, config='polish')
147         books = qs['book'].filter(title__search=query)
148         books = books.exclude(ancestor__in=books)
149         return {
150             'author': qs['author'].filter(slug__search=query),
151             'theme': qs['theme'].filter(slug__search=query),
152             'genre': qs['genre'].filter(slug__search=query),
153             'collection': qs['collection'].filter(title__search=query),
154             'book': books[:100],
155             'snippet': qs['snippet'].annotate(
156                     rank=SearchRank('search_vector', squery)
157                 ).filter(rank__gt=0).order_by('-rank').annotate(
158                     headline=SearchHeadline(
159                         'text',
160                         query,
161                         config='polish',
162                         start_sel='<strong>',
163                         stop_sel='</strong>',
164                         highlight_all=True
165                     )
166                 )[:100],
167             'art': qs['art'].filter(title__search=query)[:100],
168         }
169