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