X-Git-Url: https://git.mdrn.pl/wolnelektury.git/blobdiff_plain/9fbf0c4158a9427442171629a564e2de7780b5b7..fac40627abbe9e6466b4442bfd45e9ed037dc199:/apps/api/handlers.py diff --git a/apps/api/handlers.py b/apps/api/handlers.py index d34806f0a..4216df9fa 100644 --- a/apps/api/handlers.py +++ b/apps/api/handlers.py @@ -4,9 +4,11 @@ from datetime import datetime, timedelta import json +from urlparse import urljoin from django.conf import settings from django.contrib.sites.models import Site +from django.core.cache import get_cache from django.core.urlresolvers import reverse from piston.handler import AnonymousBaseHandler, BaseHandler from piston.utils import rc @@ -35,6 +37,9 @@ category_plural={} for k, v in category_singular.items(): category_plural[v] = k +book_tag_categories = ['author', 'epoch', 'kind', 'genre'] + + def read_tags(tags, allowed): """ Reads a path of filtering tags. @@ -87,53 +92,67 @@ class BookMediaHandler(BaseHandler): return MEDIA_BASE + media.file.url -class BookDetailHandler(BaseHandler): - """ Main handler for Book objects. +class BookDetails(object): + """Custom fields used for representing Books.""" + + @classmethod + def author(cls, book): + return ",".join(t[0] for t in book.related_info()['tags']['author']) + + @classmethod + def href(cls, book): + """ Returns an URI for a Book in the API. """ + return API_BASE + reverse("api_book", args=[book.slug]) - Responsible for lists of Book objects - and fields used for representing Books. + @classmethod + def url(cls, book): + """ Returns Book's URL on the site. """ + + return WL_BASE + book.get_absolute_url() + + @classmethod + def children(cls, book): + """ Returns all children for a book. """ + + return book.children.all() + + @classmethod + def audio(cls, book): + """ Returns all media for a book. """ + return book.media.all() + + @classmethod + def cover(cls, book): + return MEDIA_BASE + book.cover.url if book.cover else '' + + + +class BookDetailHandler(BaseHandler, BookDetails): + """ Main handler for Book objects. + Responsible for single Book details. """ allowed_methods = ['GET'] - fields = ['title', 'parent'] + Book.formats + [ - 'media', 'url'] + category_singular.keys() + fields = ['title', 'parent', 'children'] + Book.formats + [ + 'audio', 'url'] + book_tag_categories @piwik_track def read(self, request, book): """ Returns details of a book, identified by a slug and lang. """ - kwargs = Book.split_urlid(book) - if not kwargs: - return rc.NOT_FOUND - try: - return Book.objects.get(**kwargs) + return Book.objects.get(slug=book) except Book.DoesNotExist: return rc.NOT_FOUND -class AnonymousBooksHandler(AnonymousBaseHandler): +class AnonymousBooksHandler(AnonymousBaseHandler, BookDetails): """ Main handler for Book objects. - Responsible for lists of Book objects - and fields used for representing Books. - + Responsible for lists of Book objects. """ allowed_methods = ('GET',) model = Book - fields = ['href', 'title'] - - categories = set(['author', 'epoch', 'kind', 'genre']) - - @classmethod - def href(cls, book): - """ Returns an URI for a Book in the API. """ - return API_BASE + reverse("api_book", args=[book.urlid()]) - - @classmethod - def url(cls, book): - """ Returns Book's URL on the site. """ - - return WL_BASE + book.get_absolute_url() + fields = ['author', 'href', 'title', 'url', 'cover'] @piwik_track def read(self, request, tags, top_level=False): @@ -145,13 +164,19 @@ class AnonymousBooksHandler(AnonymousBaseHandler): it's children are aren't. By default all books matching the tags are returned. """ - tags = read_tags(tags, allowed=self.categories) + try: + tags = read_tags(tags, allowed=book_tag_categories) + except ValueError: + return rc.NOT_FOUND + if tags: if top_level: books = Book.tagged_top_level(tags) return books if books else rc.NOT_FOUND else: books = Book.tagged.with_all(tags) + elif top_level: + books = Book.objects.filter(parent=None) else: books = Book.objects.all() @@ -161,18 +186,13 @@ class AnonymousBooksHandler(AnonymousBaseHandler): return rc.NOT_FOUND def create(self, request, tags, top_level=False): - return 'aaa' + return rc.FORBIDDEN - @classmethod - def media(self, book): - """ Returns all media for a book. """ - return book.media.all() - - -class BooksHandler(BaseHandler): +class BooksHandler(BookDetailHandler): + allowed_methods = ('GET', 'POST') model = Book - fields = ('slug', 'title') + fields = ['author', 'href', 'title', 'url'] anonymous = AnonymousBooksHandler def create(self, request, tags, top_level=False): @@ -187,6 +207,7 @@ class BooksHandler(BaseHandler): else: return rc.NOT_FOUND + # add categorized tags fields for Book def _tags_getter(category): @classmethod @@ -194,7 +215,7 @@ def _tags_getter(category): return book.tags.filter(category=category) return get_tags for plural, singular in category_singular.items(): - setattr(BooksHandler, plural, _tags_getter(singular)) + setattr(BookDetails, plural, _tags_getter(singular)) # add fields for files in Book def _file_getter(format): @@ -208,13 +229,29 @@ def _file_getter(format): return '' return get_file for format in Book.formats: - setattr(BooksHandler, format, _file_getter(format)) + setattr(BookDetails, format, _file_getter(format)) + + +class TagDetails(object): + """Custom Tag fields.""" + @classmethod + def href(cls, tag): + """ Returns URI in the API for the tag. """ + + return API_BASE + reverse("api_tag", args=[category_plural[tag.category], tag.slug]) + + @classmethod + def url(cls, tag): + """ Returns URL on the site. """ -class TagDetailHandler(BaseHandler): + return WL_BASE + tag.get_absolute_url() + + +class TagDetailHandler(BaseHandler, TagDetails): """ Responsible for details of a single Tag object. """ - fields = ['name', 'sort_key', 'description'] + fields = ['name', 'url', 'sort_key', 'description'] @piwik_track def read(self, request, category, slug): @@ -231,7 +268,7 @@ class TagDetailHandler(BaseHandler): return rc.NOT_FOUND -class TagsHandler(BaseHandler): +class TagsHandler(BaseHandler, TagDetails): """ Main handler for Tag objects. Responsible for lists of Tag objects @@ -240,7 +277,7 @@ class TagsHandler(BaseHandler): """ allowed_methods = ('GET',) model = Tag - fields = ['name', 'href'] + fields = ['name', 'href', 'url'] @piwik_track def read(self, request, category): @@ -258,34 +295,42 @@ class TagsHandler(BaseHandler): return rc.NOT_FOUND +class FragmentDetails(object): + """Custom Fragment fields.""" + @classmethod - def href(cls, tag): - """ Returns URI in the API for the tag. """ + def href(cls, fragment): + """ Returns URI in the API for the fragment. """ - return API_BASE + reverse("api_tag", args=[category_plural[tag.category], tag.slug]) + return API_BASE + reverse("api_fragment", + args=[fragment.book.slug, fragment.anchor]) + + @classmethod + def url(cls, fragment): + """ Returns URL on the site for the fragment. """ + + return WL_BASE + fragment.get_absolute_url() + + @classmethod + def themes(cls, fragment): + """ Returns a list of theme tags for the fragment. """ + + return fragment.tags.filter(category='theme') -class FragmentDetailHandler(BaseHandler): +class FragmentDetailHandler(BaseHandler, FragmentDetails): fields = ['book', 'anchor', 'text', 'url', 'themes'] @piwik_track def read(self, request, book, anchor): """ Returns details of a fragment, identified by book slug and anchor. """ - kwargs = Book.split_urlid(book) - if not kwargs: - return rc.NOT_FOUND - - fragment_kwargs = {} - for field, value in kwargs.items(): - fragment_kwargs['book__' + field] = value - try: - return Fragment.objects.get(anchor=anchor, **fragment_kwargs) + return Fragment.objects.get(book__slug=book, anchor=anchor) except Fragment.DoesNotExist: return rc.NOT_FOUND -class FragmentsHandler(BaseHandler): +class FragmentsHandler(BaseHandler, FragmentDetails): """ Main handler for Fragments. Responsible for lists of Fragment objects @@ -293,7 +338,7 @@ class FragmentsHandler(BaseHandler): """ model = Fragment - fields = ['book', 'anchor', 'href'] + fields = ['book', 'url', 'anchor', 'href'] allowed_methods = ('GET',) categories = set(['author', 'epoch', 'kind', 'genre', 'book', 'theme']) @@ -306,32 +351,16 @@ class FragmentsHandler(BaseHandler): books/book-slug/authors/an-author/themes/a-theme/ """ - tags = read_tags(tags, allowed=self.categories) + try: + tags = read_tags(tags, allowed=self.categories) + except ValueError: + return rc.NOT_FOUND fragments = Fragment.tagged.with_all(tags).select_related('book') if fragments.exists(): return fragments else: return rc.NOT_FOUND - @classmethod - def href(cls, fragment): - """ Returns URI in the API for the fragment. """ - - return API_BASE + reverse("api_fragment", args=[fragment.book.urlid(), fragment.anchor]) - - @classmethod - def url(cls, fragment): - """ Returns URL on the site for the fragment. """ - - return WL_BASE + fragment.get_absolute_url() - - @classmethod - def themes(cls, fragment): - """ Returns a list of theme tags for the fragment. """ - - return fragment.tags.filter(category='theme') - - # Changes handlers @@ -542,6 +571,16 @@ class CatalogueHandler(BaseHandler): def changes(cls, request=None, since=0, until=None, book_fields=None, tag_fields=None, tag_categories=None): until = cls.until(until) + since = int(since) + + if not since: + cache = get_cache('api') + key = hash((book_fields, tag_fields, tag_categories, + tuple(sorted(request.GET.items())) + )) + value = cache.get(key) + if value is not None: + return value changes = { 'time_checked': timestamp(until) @@ -557,6 +596,10 @@ class CatalogueHandler(BaseHandler): if field == 'time_checked': continue changes.setdefault(field, {})[model] = changes_by_type[model][field] + + if not since: + cache.set(key, changes) + return changes