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