X-Git-Url: https://git.mdrn.pl/wolnelektury.git/blobdiff_plain/145e56d0df5162f8b57574e5b5656c6354274280..8f98ff35ae8cd6ebe07d73112f8c476011ac78d6:/apps/api/handlers.py diff --git a/apps/api/handlers.py b/apps/api/handlers.py index a4782dcb1..706e0cdac 100644 --- a/apps/api/handlers.py +++ b/apps/api/handlers.py @@ -2,13 +2,15 @@ # This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later. # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. -from datetime import datetime +from datetime import datetime, timedelta from piston.handler import BaseHandler +from django.conf import settings from api.helpers import timestamp from api.models import Deleted from catalogue.models import Book, Tag + class CatalogueHandler(BaseHandler): @staticmethod @@ -16,6 +18,22 @@ class CatalogueHandler(BaseHandler): fields_str = request.GET.get(name) if request is not None else None return fields_str.split(',') if fields_str is not None else None + @staticmethod + def until(t=None): + """ Returns time suitable for use as upper time boundary for check. + + Defaults to 'five minutes ago' to avoid issues with time between + change stamp set and model save. + Cuts the microsecond part to avoid issues with DBs where time has + more precision. + + """ + # set to five minutes ago, to avoid concurrency issues + if t is None: + t = datetime.now() - timedelta(seconds=settings.API_WAIT) + # set to whole second in case DB supports something smaller + return t.replace(microsecond=0) + @staticmethod def book_dict(book, fields=None): all_fields = ('url', 'title', 'description', @@ -26,6 +44,7 @@ class CatalogueHandler(BaseHandler): 'tags', 'license', 'license_description', 'source_name', 'technical_editors', 'editors', + 'author', 'sort_key', ) if fields: fields = (f for f in fields if f in all_fields) @@ -47,9 +66,9 @@ class CatalogueHandler(BaseHandler): elif field in ('mp3', 'ogg', 'daisy'): media = [] - for m in book.medias.filter(type=''): - files.append({ - 'url': m.file.get_absolute_url(), + for m in book.media.filter(type=field): + media.append({ + 'url': m.file.url, 'size': m.file.size, }) if media: @@ -61,6 +80,12 @@ class CatalogueHandler(BaseHandler): elif field == 'tags': obj[field] = [t.id for t in book.tags.exclude(category__in=('book', 'set'))] + elif field == 'author': + obj[field] = ", ".join(t.name for t in book.tags.filter(category='author')) + + elif field == 'parent': + obj[field] = book.parent_id + elif field in ('license', 'license_description', 'source_name', 'technical_editors', 'editors'): f = extra_info.get(field) @@ -76,8 +101,14 @@ class CatalogueHandler(BaseHandler): return obj @classmethod - def book_changes(cls, since=0, request=None, fields=None): + def book_changes(cls, request=None, since=0, until=None, fields=None): since = datetime.fromtimestamp(int(since)) + until = cls.until(until) + + changes = { + 'time_checked': timestamp(until) + } + if not fields: fields = cls.fields(request, 'book_fields') @@ -86,19 +117,28 @@ class CatalogueHandler(BaseHandler): deleted = [] last_change = since - for book in Book.objects.filter(changed_at__gte=since): + for book in Book.objects.filter(changed_at__gte=since, + changed_at__lt=until): book_d = cls.book_dict(book, fields) updated.append(book_d) + if updated: + changes['updated'] = updated - for book in Deleted.objects.filter(content_type=Book, deleted_at__gte=since, created_at__lt=since): + for book in Deleted.objects.filter(content_type=Book, + deleted_at__gte=since, + deleted_at__lt=until, + created_at__lt=since): deleted.append(book.id) - return {'updated': updated, 'deleted': deleted} + if deleted: + changes['deleted'] = deleted + + return changes @staticmethod def tag_dict(tag, fields=None): all_fields = ('name', 'category', 'sort_key', 'description', 'gazeta_link', 'wiki_link', - 'url', + 'url', 'books', ) if fields: @@ -112,6 +152,12 @@ class CatalogueHandler(BaseHandler): if field == 'url': obj[field] = tag.get_absolute_url() + elif field == 'books': + obj[field] = [b.id for b in Book.tagged_top_level([tag])] + + elif field == 'sort_key': + obj[field] = tag.sort_key + else: f = getattr(tag, field) if f: @@ -121,14 +167,20 @@ class CatalogueHandler(BaseHandler): return obj @classmethod - def tag_changes(cls, since=0, request=None, fields=None, categories=None): + def tag_changes(cls, request=None, since=0, until=None, fields=None, categories=None): since = datetime.fromtimestamp(int(since)) + until = cls.until(until) + + changes = { + 'time_checked': timestamp(until) + } + if not fields: fields = cls.fields(request, 'tag_fields') if not categories: categories = cls.fields(request, 'tag_categories') - all_categories = ('author', 'theme', 'epoch', 'kind', 'genre') + all_categories = ('author', 'epoch', 'kind', 'genre') if categories: categories = (c for c in categories if c in all_categories) else: @@ -137,29 +189,47 @@ class CatalogueHandler(BaseHandler): updated = [] deleted = [] - for tag in Tag.objects.filter(category__in=categories, changed_at__gte=since): - tag_d = cls.tag_dict(tag, fields) - updated.append(tag_d) + for tag in Tag.objects.filter(category__in=categories, + changed_at__gte=since, + changed_at__lt=until): + # only serve non-empty tags + if tag.get_count(): + tag_d = cls.tag_dict(tag, fields) + updated.append(tag_d) + elif tag.created_at < since: + deleted.append(tag.id) + if updated: + changes['updated'] = updated for tag in Deleted.objects.filter(category__in=categories, - content_type=Tag, deleted_at__gte=since, created_at__lt=since): + content_type=Tag, + deleted_at__gte=since, + deleted_at__lt=until, + created_at__lt=since): deleted.append(tag.id) - return {'updated': updated, 'deleted': deleted} + if deleted: + changes['deleted'] = deleted + + return changes @classmethod - def changes(cls, since=0, request=None, book_fields=None, + def changes(cls, request=None, since=0, until=None, book_fields=None, tag_fields=None, tag_categories=None): + until = cls.until(until) + changes = { - 'time_checked': timestamp(datetime.now()) + 'time_checked': timestamp(until) } changes_by_type = { - 'books': cls.book_changes(since, request, book_fields), - 'tags': cls.tag_changes(since, request, tag_fields, tag_categories), + 'books': cls.book_changes(request, since, until, book_fields), + 'tags': cls.tag_changes(request, since, until, tag_fields, tag_categories), } for model in changes_by_type: for field in changes_by_type[model]: + if field == 'time_checked': + continue changes.setdefault(field, {})[model] = changes_by_type[model][field] return changes @@ -168,18 +238,18 @@ class BookChangesHandler(CatalogueHandler): allowed_methods = ('GET',) def read(self, request, since): - return self.book_changes(since, request) + return self.book_changes(request, since) class TagChangesHandler(CatalogueHandler): allowed_methods = ('GET',) def read(self, request, since): - return self.tag_changes(since, request) + return self.tag_changes(request, since) class ChangesHandler(CatalogueHandler): allowed_methods = ('GET',) def read(self, request, since): - return self.changes(since, request) + return self.changes(request, since)