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.
6 #############################################################################
7 # from: http://djangosnippets.org/snippets/243/
11 from django.http import HttpResponse
12 from django.contrib.auth import authenticate, login
15 def view_or_basicauth(view, request, test_func, realm = "", *args, **kwargs):
17 This is a helper function used by 'logged_in_or_basicauth' and
18 'has_perm_or_basicauth' (deleted) that does the nitty of determining if they
19 are already logged in or if they have provided proper http-authorization
20 and returning the view if all goes well, otherwise responding with a 401.
22 if test_func(request.user):
23 # Already logged in, just return the view.
25 return view(request, *args, **kwargs)
27 # They are not logged in. See if they provided login credentials
29 if 'HTTP_AUTHORIZATION' in request.META:
30 auth = request.META['HTTP_AUTHORIZATION'].split()
32 # NOTE: We are only support basic authentication for now.
34 if auth[0].lower() == "basic":
35 uname, passwd = base64.b64decode(auth[1]).split(':')
36 user = authenticate(username=uname, password=passwd)
41 return view(request, *args, **kwargs)
43 # Either they did not provide an authorization header or
44 # something in the authorization attempt failed. Send a 401
45 # back to them to ask them to authenticate.
47 response = HttpResponse()
48 response.status_code = 401
49 response['WWW-Authenticate'] = 'Basic realm="%s"' % realm
54 def logged_in_or_basicauth(realm = ""):
56 A simple decorator that requires a user to be logged in. If they are not
57 logged in the request is examined for a 'authorization' header.
59 If the header is present it is tested for basic authentication and
60 the user is logged in with the provided credentials.
62 If the header is not present a http 401 is sent back to the
63 requestor to provide credentials.
65 The purpose of this is that in several django projects I have needed
66 several specific views that need to support basic authentication, yet the
67 web site as a whole used django's provided authentication.
69 The uses for this are for urls that are access programmatically such as
70 by rss feed readers, yet the view requires a user to be logged in. Many rss
71 readers support supplying the authentication credentials via http basic
72 auth (and they do NOT support a redirect to a form where they post a
77 @logged_in_or_basicauth
81 You can provide the name of the realm to ask for authentication within.
83 def view_decorator(func):
84 def wrapper(request, *args, **kwargs):
85 return view_or_basicauth(func, request,
86 lambda u: u.is_authenticated(),
87 realm, *args, **kwargs)
92 #############################################################################
95 from base64 import b64encode
98 from django.contrib.syndication.views import Feed
99 from django.core.urlresolvers import reverse
100 from django.shortcuts import get_object_or_404
101 from django.utils.feedgenerator import Atom1Feed
102 from django.conf import settings
103 from django.http import Http404
104 from django.contrib.sites.models import Site
106 from catalogue.models import Book, Tag
112 u"link": u"opds_user",
114 u"title": u"Moje półki",
115 u"description": u"Półki użytkownika dostępne po zalogowaniu"
118 u"category": u"author",
119 u"link": u"opds_by_category",
120 u"link_args": [u"author"],
121 u"title": u"Autorzy",
122 u"description": u"Utwory wg autorów"
125 u"category": u"kind",
126 u"link": u"opds_by_category",
127 u"link_args": [u"kind"],
128 u"title": u"Rodzaje",
129 u"description": u"Utwory wg rodzajów"
132 u"category": u"genre",
133 u"link": u"opds_by_category",
134 u"link_args": [u"kind"],
135 u"title": u"Gatunki",
136 u"description": u"Utwory wg gatunków"
139 u"category": u"epoch",
140 u"link": u"opds_by_category",
141 u"link_args": [u"epoch"],
143 u"description": u"Utwory wg epok"
148 def factory_decorator(decorator):
149 """ generates a decorator for a function factory class
150 if A(*) == f, factory_decorator(D)(A)(*) == D(f)
153 def wrapper(*args, **kwargs):
154 return decorator(func(*args, **kwargs))
159 class OPDSFeed(Atom1Feed):
160 link_rel = u"subsection"
161 link_type = u"application/atom+xml"
163 _book_parent_img = "http://%s%s" % (Site.objects.get_current().domain, os.path.join(settings.STATIC_URL, "img/book-parent.png"))
165 _book_parent_img_size = unicode(os.path.getsize(os.path.join(settings.STATIC_ROOT, "img/book-parent.png")))
167 _book_parent_img_size = ''
169 _book_img = "http://%s%s" % (Site.objects.get_current().domain, os.path.join(settings.STATIC_URL, "img/book.png"))
171 _book_img_size = unicode(os.path.getsize(os.path.join(settings.STATIC_ROOT, "img/book.png")))
175 def add_root_elements(self, handler):
176 super(OPDSFeed, self).add_root_elements(handler)
177 handler.addQuickElement(u"link", u"", {u"href": reverse("opds_authors"), u"rel": u"start", u"type": u"application/atom+xml"})
180 def add_item_elements(self, handler, item):
181 """ modified from Atom1Feed.add_item_elements """
182 handler.addQuickElement(u"title", item['title'])
184 # add a OPDS Navigation link if there's no enclosure
185 if item['enclosure'] is None:
186 handler.addQuickElement(u"link", u"", {u"href": item['link'], u"rel": u"subsection", u"type": u"application/atom+xml"})
187 # add a "green book" icon
188 handler.addQuickElement(u"link", '',
189 {u"rel": u"http://opds-spec.org/thumbnail",
190 u"href": self._book_parent_img,
191 u"length": self._book_parent_img_size,
192 u"type": u"image/png"})
193 if item['pubdate'] is not None:
194 handler.addQuickElement(u"updated", rfc3339_date(item['pubdate']).decode('utf-8'))
196 # Author information.
197 if item['author_name'] is not None:
198 handler.startElement(u"author", {})
199 handler.addQuickElement(u"name", item['author_name'])
200 if item['author_email'] is not None:
201 handler.addQuickElement(u"email", item['author_email'])
202 if item['author_link'] is not None:
203 handler.addQuickElement(u"uri", item['author_link'])
204 handler.endElement(u"author")
207 if item['unique_id'] is not None:
208 unique_id = item['unique_id']
210 unique_id = get_tag_uri(item['link'], item['pubdate'])
211 handler.addQuickElement(u"id", unique_id)
214 # OPDS needs type=text
215 if item['description'] is not None:
216 handler.addQuickElement(u"summary", item['description'], {u"type": u"text"})
218 # Enclosure as OPDS Acquisition Link
219 if item['enclosure'] is not None:
220 handler.addQuickElement(u"link", '',
221 {u"rel": u"http://opds-spec.org/acquisition",
222 u"href": item['enclosure'].url,
223 u"length": item['enclosure'].length,
224 u"type": item['enclosure'].mime_type})
225 # add a "red book" icon
226 handler.addQuickElement(u"link", '',
227 {u"rel": u"http://opds-spec.org/thumbnail",
228 u"href": self._book_img,
229 u"length": self._book_img_size,
230 u"type": u"image/png"})
233 for cat in item['categories']:
234 handler.addQuickElement(u"category", u"", {u"term": cat})
237 if item['item_copyright'] is not None:
238 handler.addQuickElement(u"rights", item['item_copyright'])
241 class RootFeed(Feed):
243 title = u'Wolne Lektury'
244 link = u'http://www.wolnelektury.pl/'
245 description = u"Spis utworów na stronie http://WolneLektury.pl"
246 author_name = u"Wolne Lektury"
247 author_link = u"http://www.wolnelektury.pl/"
252 def item_title(self, item):
255 def item_link(self, item):
256 return reverse(item['link'], args=item['link_args'])
258 def item_description(self, item):
259 return item['description']
262 class ByCategoryFeed(Feed):
264 link = u'http://www.wolnelektury.pl/'
265 description = u"Spis utworów na stronie http://WolneLektury.pl"
266 author_name = u"Wolne Lektury"
267 author_link = u"http://www.wolnelektury.pl/"
269 def get_object(self, request, category):
270 feed = [feed for feed in _root_feeds if feed['category']==category]
278 def title(self, feed):
281 def items(self, feed):
282 return (tag for tag in Tag.objects.filter(category=feed['category']) if tag.get_count() > 0)
284 def item_title(self, item):
287 def item_link(self, item):
288 return reverse("opds_by_tag", args=[item.category, item.slug])
290 def item_description(self):
294 class ByTagFeed(Feed):
296 link = u'http://www.wolnelektury.pl/'
297 item_enclosure_mime_type = "application/epub+zip"
298 author_name = u"Wolne Lektury"
299 author_link = u"http://www.wolnelektury.pl/"
302 return tag.get_absolute_url()
304 def title(self, tag):
307 def description(self, tag):
308 return u"Spis utworów na stronie http://WolneLektury.pl"
310 def get_object(self, request, category, slug):
311 return get_object_or_404(Tag, category=category, slug=slug)
313 def items(self, tag):
314 books = Book.tagged.with_any([tag])
315 l_tags = Tag.objects.filter(category='book', slug__in=[book.book_tag_slug() for book in books])
316 descendants_keys = [book.pk for book in Book.tagged.with_any(l_tags)]
318 books = books.exclude(pk__in=descendants_keys)
322 def item_title(self, book):
325 def item_description(self):
328 def item_link(self, book):
329 return book.get_absolute_url()
331 def item_author_name(self, book):
333 return book.tags.filter(category='author')[0].name
337 def item_author_link(self, book):
339 return book.tags.filter(category='author')[0].get_absolute_url()
343 def item_enclosure_url(self, book):
344 return "http://%s%s" % (Site.objects.get_current().domain, book.root_ancestor.epub_file.url)
346 def item_enclosure_length(self, book):
347 return book.root_ancestor.epub_file.size
350 @factory_decorator(logged_in_or_basicauth())
351 class UserFeed(Feed):
353 link = u'http://www.wolnelektury.pl/'
354 description = u"Półki użytkownika na stronie http://WolneLektury.pl"
355 author_name = u"Wolne Lektury"
356 author_link = u"http://www.wolnelektury.pl/"
358 def get_object(self, request):
361 def title(self, user):
362 return u"Półki użytkownika %s" % user.username
364 def items(self, user):
365 return (tag for tag in Tag.objects.filter(category='set', user=user) if tag.get_count() > 0)
367 def item_title(self, item):
370 def item_link(self, item):
371 return reverse("opds_user_set", args=[item.slug])
373 def item_description(self):
377 @factory_decorator(logged_in_or_basicauth())
378 class UserSetFeed(Feed):
380 link = u'http://www.wolnelektury.pl/'
381 item_enclosure_mime_type = "application/epub+zip"
382 author_name = u"Wolne Lektury"
383 author_link = u"http://www.wolnelektury.pl/"
386 return tag.get_absolute_url()
388 def title(self, tag):
391 def description(self, tag):
392 return u"Spis utworów na stronie http://WolneLektury.pl"
394 def get_object(self, request, slug):
395 return get_object_or_404(Tag, category='set', slug=slug, user=request.user)
397 def items(self, tag):
398 return Book.tagged.with_any([tag])
400 def item_title(self, book):
403 def item_description(self):
406 def item_link(self, book):
407 return book.get_absolute_url()
409 def item_author_name(self, book):
411 return book.tags.filter(category='author')[0].name
415 def item_author_link(self, book):
417 return book.tags.filter(category='author')[0].get_absolute_url()
421 def item_enclosure_url(self, book):
422 return "http://%s%s" % (Site.objects.get_current().domain, book.root_ancestor.epub_file.url)
424 def item_enclosure_length(self, book):
425 return book.root_ancestor.epub_file.size
427 @logged_in_or_basicauth()
428 def user_set_feed(request):
429 return UserSetFeed()(request)