Merge branch 'search' of git+ssh://stigma.fnp/home/mkoziej/wl into search
[wolnelektury.git] / apps / opds / views.py
1 # -*- coding: utf-8 -*-
2 # This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
3 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
4 #
5 from base64 import b64encode
6 import os.path
7 from urlparse import urljoin
8
9 from django.contrib.syndication.views import Feed
10 from django.core.urlresolvers import reverse
11 from django.shortcuts import get_object_or_404
12 from django.utils.feedgenerator import Atom1Feed
13 from django.conf import settings
14 from django.http import Http404
15 from django.contrib.sites.models import Site
16
17 from basicauth import logged_in_or_basicauth, factory_decorator
18 from catalogue.models import Book, Tag
19 from catalogue.views import books_starting_with
20
21 from stats.utils import piwik_track
22
23 _root_feeds = (
24     {
25         u"category": u"",
26         u"link": u"opds_user",
27         u"link_args": [],
28         u"title": u"Moje półki",
29         u"description": u"Półki użytkownika dostępne po zalogowaniu"
30     },
31     {
32         u"category": u"author",
33         u"link": u"opds_by_category",
34         u"link_args": [u"author"],
35         u"title": u"Autorzy",
36         u"description": u"Utwory wg autorów"
37     },
38     {
39         u"category": u"kind",
40         u"link": u"opds_by_category",
41         u"link_args": [u"kind"],
42         u"title": u"Rodzaje",
43         u"description": u"Utwory wg rodzajów"
44     },
45     {
46         u"category": u"genre",
47         u"link": u"opds_by_category",
48         u"link_args": [u"genre"],
49         u"title": u"Gatunki",
50         u"description": u"Utwory wg gatunków"
51     },
52     {
53         u"category": u"epoch",
54         u"link": u"opds_by_category",
55         u"link_args": [u"epoch"],
56         u"title": u"Epoki",
57         u"description": u"Utwory wg epok"
58     },
59 )
60
61
62 def full_url(url):
63     return urljoin("http://%s" % Site.objects.get_current().domain, url)
64
65
66 class OPDSFeed(Atom1Feed):
67     link_rel = u"subsection"
68     link_type = u"application/atom+xml"
69
70     _book_parent_img = full_url(os.path.join(settings.STATIC_URL, "img/book-parent.png"))
71     try:
72         _book_parent_img_size = unicode(os.path.getsize(os.path.join(settings.STATIC_ROOT, "img/book-parent.png")))
73     except:
74         _book_parent_img_size = ''
75
76     _book_img = full_url(os.path.join(settings.STATIC_URL, "img/book.png"))
77     try:
78         _book_img_size = unicode(os.path.getsize(os.path.join(settings.STATIC_ROOT, "img/book.png")))
79     except:
80         _book_img_size = ''
81
82
83     def add_root_elements(self, handler):
84         super(OPDSFeed, self).add_root_elements(handler)
85         handler.addQuickElement(u"link", None,
86                                 {u"href": reverse("opds_authors"),
87                                  u"rel": u"start",
88                                  u"type": u"application/atom+xml"})
89         handler.addQuickElement(u"link", None, 
90                                 {u"href": full_url(os.path.join(settings.STATIC_URL, "opensearch.xml")),
91                                  u"rel": u"search",
92                                  u"type": u"application/opensearchdescription+xml"})
93
94
95     def add_item_elements(self, handler, item):
96         """ modified from Atom1Feed.add_item_elements """
97         handler.addQuickElement(u"title", item['title'])
98
99         # add a OPDS Navigation link if there's no enclosure
100         if item['enclosure'] is None:
101             handler.addQuickElement(u"link", u"", {u"href": item['link'], u"rel": u"subsection", u"type": u"application/atom+xml"})
102             # add a "green book" icon
103             handler.addQuickElement(u"link", '',
104                 {u"rel": u"http://opds-spec.org/thumbnail",
105                  u"href": self._book_parent_img,
106                  u"length": self._book_parent_img_size,
107                  u"type": u"image/png"})
108         if item['pubdate'] is not None:
109             handler.addQuickElement(u"updated", rfc3339_date(item['pubdate']).decode('utf-8'))
110
111         # Author information.
112         if item['author_name'] is not None:
113             handler.startElement(u"author", {})
114             handler.addQuickElement(u"name", item['author_name'])
115             if item['author_email'] is not None:
116                 handler.addQuickElement(u"email", item['author_email'])
117             if item['author_link'] is not None:
118                 handler.addQuickElement(u"uri", item['author_link'])
119             handler.endElement(u"author")
120
121         # Unique ID.
122         if item['unique_id'] is not None:
123             unique_id = item['unique_id']
124         else:
125             unique_id = get_tag_uri(item['link'], item['pubdate'])
126         handler.addQuickElement(u"id", unique_id)
127
128         # Summary.
129         # OPDS needs type=text
130         if item['description'] is not None:
131             handler.addQuickElement(u"summary", item['description'], {u"type": u"text"})
132
133         # Enclosure as OPDS Acquisition Link
134         if item['enclosure'] is not None:
135             handler.addQuickElement(u"link", '',
136                 {u"rel": u"http://opds-spec.org/acquisition",
137                  u"href": item['enclosure'].url,
138                  u"length": item['enclosure'].length,
139                  u"type": item['enclosure'].mime_type})
140             # add a "red book" icon
141             handler.addQuickElement(u"link", '',
142                 {u"rel": u"http://opds-spec.org/thumbnail",
143                  u"href": self._book_img,
144                  u"length": self._book_img_size,
145                  u"type": u"image/png"})
146
147         # Categories.
148         for cat in item['categories']:
149             handler.addQuickElement(u"category", u"", {u"term": cat})
150
151         # Rights.
152         if item['item_copyright'] is not None:
153             handler.addQuickElement(u"rights", item['item_copyright'])
154
155
156 class AcquisitionFeed(Feed):
157     feed_type = OPDSFeed
158     link = u'http://www.wolnelektury.pl/'
159     item_enclosure_mime_type = "application/epub+zip"
160     author_name = u"Wolne Lektury"
161     author_link = u"http://www.wolnelektury.pl/"
162
163     def item_title(self, book):
164         return book.title
165
166     def item_description(self):
167         return u''
168
169     def item_link(self, book):
170         return book.get_absolute_url()
171
172     def item_author_name(self, book):
173         try:
174             return book.tags.filter(category='author')[0].name
175         except KeyError:
176             return u''
177
178     def item_author_link(self, book):
179         try:
180             return book.tags.filter(category='author')[0].get_absolute_url()
181         except KeyError:
182             return u''
183
184     def item_enclosure_url(self, book):
185         return full_url(book.root_ancestor.epub_file.url)
186
187     def item_enclosure_length(self, book):
188         return book.root_ancestor.epub_file.size
189
190 @piwik_track
191 class RootFeed(Feed):
192     feed_type = OPDSFeed
193     title = u'Wolne Lektury'
194     link = u'http://www.wolnelektury.pl/'
195     description = u"Spis utworów na stronie http://WolneLektury.pl"
196     author_name = u"Wolne Lektury"
197     author_link = u"http://www.wolnelektury.pl/"
198
199     def items(self):
200         return _root_feeds
201
202     def item_title(self, item):
203         return item['title']
204
205     def item_link(self, item):
206         return reverse(item['link'], args=item['link_args'])
207
208     def item_description(self, item):
209         return item['description']
210
211 @piwik_track
212 class ByCategoryFeed(Feed):
213     feed_type = OPDSFeed
214     link = u'http://www.wolnelektury.pl/'
215     description = u"Spis utworów na stronie http://WolneLektury.pl"
216     author_name = u"Wolne Lektury"
217     author_link = u"http://www.wolnelektury.pl/"
218
219     def get_object(self, request, category):
220         feed = [feed for feed in _root_feeds if feed['category']==category]
221         if feed:
222             feed = feed[0]
223         else:
224             raise Http404
225
226         return feed
227
228     def title(self, feed):
229         return feed['title']
230
231     def items(self, feed):
232         return (tag for tag in Tag.objects.filter(category=feed['category']) if tag.get_count() > 0)
233
234     def item_title(self, item):
235         return item.name
236
237     def item_link(self, item):
238         return reverse("opds_by_tag", args=[item.category, item.slug])
239
240     def item_description(self):
241         return u''
242
243 @piwik_track
244 class ByTagFeed(AcquisitionFeed):
245     def link(self, tag):
246         return tag.get_absolute_url()
247
248     def title(self, tag):
249         return tag.name
250
251     def description(self, tag):
252         return u"Spis utworów na stronie http://WolneLektury.pl"
253
254     def get_object(self, request, category, slug):
255         return get_object_or_404(Tag, category=category, slug=slug)
256
257     def items(self, tag):
258         books = Book.tagged.with_any([tag])
259         l_tags = Tag.objects.filter(category='book', slug__in=[book.book_tag_slug() for book in books])
260         descendants_keys = [book.pk for book in Book.tagged.with_any(l_tags)]
261         if descendants_keys:
262             books = books.exclude(pk__in=descendants_keys)
263
264         return books
265
266
267 @factory_decorator(logged_in_or_basicauth())
268 @piwik_track
269 class UserFeed(Feed):
270     feed_type = OPDSFeed
271     link = u'http://www.wolnelektury.pl/'
272     description = u"Półki użytkownika na stronie http://WolneLektury.pl"
273     author_name = u"Wolne Lektury"
274     author_link = u"http://www.wolnelektury.pl/"
275
276     def get_object(self, request):
277         return request.user
278
279     def title(self, user):
280         return u"Półki użytkownika %s" % user.username
281
282     def items(self, user):
283         return (tag for tag in Tag.objects.filter(category='set', user=user) if tag.get_count() > 0)
284
285     def item_title(self, item):
286         return item.name
287
288     def item_link(self, item):
289         return reverse("opds_user_set", args=[item.slug])
290
291     def item_description(self):
292         return u''
293
294 # no class decorators in python 2.5
295 #UserFeed = factory_decorator(logged_in_or_basicauth())(UserFeed)
296
297
298 @factory_decorator(logged_in_or_basicauth())
299 @piwik_track
300 class UserSetFeed(AcquisitionFeed):
301     def link(self, tag):
302         return tag.get_absolute_url()
303
304     def title(self, tag):
305         return tag.name
306
307     def description(self, tag):
308         return u"Spis utworów na stronie http://WolneLektury.pl"
309
310     def get_object(self, request, slug):
311         return get_object_or_404(Tag, category='set', slug=slug, user=request.user)
312
313     def items(self, tag):
314         return Book.tagged.with_any([tag])
315
316 # no class decorators in python 2.5
317 #UserSetFeed = factory_decorator(logged_in_or_basicauth())(UserSetFeed)
318
319 @piwik_track
320 class SearchFeed(AcquisitionFeed):
321     description = u"Wyniki wyszukiwania na stronie WolneLektury.pl"
322     title = u"Wyniki wyszukiwania"
323     
324     def get_object(self, request):
325         return request.GET.get('q', '')
326
327     def get_link(self, query):
328         return "%s?q=%s" % (reverse('search'), query) 
329
330     def items(self, query):
331         try:
332             return books_starting_with(query)
333         except ValueError:
334             # too short a query
335             return []