From 0543785bb9e74989036d99eb25cf23c3ef82cdf7 Mon Sep 17 00:00:00 2001 From: Jan Szejko Date: Tue, 5 Jun 2018 15:27:48 +0200 Subject: [PATCH] reading state api --- src/api/handlers.py | 60 +++++++++++++++++++++++++ src/api/migrations/0003_bookuserdata.py | 26 +++++++++++ src/api/models.py | 15 +++++++ src/api/urls.py | 8 ++++ 4 files changed, 109 insertions(+) create mode 100644 src/api/migrations/0003_bookuserdata.py diff --git a/src/api/handlers.py b/src/api/handlers.py index 77728208e..c89997617 100644 --- a/src/api/handlers.py +++ b/src/api/handlers.py @@ -13,6 +13,7 @@ from piston.handler import AnonymousBaseHandler, BaseHandler from piston.utils import rc from sorl.thumbnail import default +from api.models import BookUserData from catalogue.forms import BookImportForm from catalogue.models import Book, Tag, BookMedia, Fragment, Collection from catalogue.models.tag import prefetch_relations @@ -649,3 +650,62 @@ class PictureHandler(BaseHandler): return rc.CREATED else: return rc.NOT_FOUND + + +class UserDataHandler(BaseHandler): + model = BookUserData + fields = ('state',) + allowed_methods = ('GET', 'POST') + + def read(self, request, slug): + try: + book = Book.objects.get(slug=slug) + except Book.DoesNotExist: + return rc.NOT_FOUND + if not request.user.is_authenticated(): + return rc.FORBIDDEN + try: + data = BookUserData.objects.get(book=book, user=request.user) + except BookUserData.DoesNotExist: + return {'state': 'not_started'} + return data + + def create(self, request, slug, state): + try: + book = Book.objects.get(slug=slug) + except Book.DoesNotExist: + return rc.NOT_FOUND + if not request.user.is_authenticated(): + return rc.FORBIDDEN + if state not in ('reading', 'complete'): + return rc.NOT_FOUND + data, created = BookUserData.objects.get_or_create(book=book, user=request.user) + data.state = state + data.save() + return data + + +class UserShelfHandler(BookDetailHandler): + fields = book_tag_categories + [ + 'href', 'title', 'url', 'cover', 'cover_thumb', 'simple_thumb', 'slug', 'key'] + + def parse_bool(self, s): + if s in ('true', 'false'): + return s == 'true' + else: + return None + + def read(self, request, state): + if not request.user.is_authenticated(): + return rc.FORBIDDEN + if state not in ('reading', 'complete'): + return rc.NOT_FOUND + after = request.GET.get('after') + count = int(request.GET.get('count', 50)) + ids = BookUserData.objects.filter(user=request.user, complete=state == 'complete').values_list('book_id', flat=True) + books = Book.objects.filter(id__in=list(ids)).distinct().order_by('slug') + if after: + books = books.filter(slug__gt=after) + if count: + books = books[:count] + return books diff --git a/src/api/migrations/0003_bookuserdata.py b/src/api/migrations/0003_bookuserdata.py new file mode 100644 index 000000000..11a2f8ff0 --- /dev/null +++ b/src/api/migrations/0003_bookuserdata.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models +from django.conf import settings + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('catalogue', '0024_auto_20180510_1407'), + ('api', '0002_auto_20151221_1225'), + ] + + operations = [ + migrations.CreateModel( + name='BookUserData', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('complete', models.BooleanField(default=False)), + ('book', models.ForeignKey(to='catalogue.Book')), + ('user', models.ForeignKey(to=settings.AUTH_USER_MODEL)), + ], + ), + ] diff --git a/src/api/models.py b/src/api/models.py index 2f742834f..cc71a06f3 100644 --- a/src/api/models.py +++ b/src/api/models.py @@ -2,6 +2,7 @@ # This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later. # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. # +from django.contrib.auth.models import User from django.contrib.contenttypes.models import ContentType from django.db import models from django.db.models.signals import pre_delete @@ -38,3 +39,17 @@ def _pre_delete_handler(sender, instance, **kwargs): content_type=content_type, object_id=instance.id, created_at=instance.created_at, category=category, slug=instance.slug) pre_delete.connect(_pre_delete_handler) + + +class BookUserData(models.Model): + book = models.ForeignKey(Book) + user = models.ForeignKey(User) + complete = models.BooleanField(default=False) + + def get_state(self): + return 'complete' if self.complete else 'reading' + + def set_state(self, state): + self.complete = state == 'complete' + + state = property(get_state, set_state) diff --git a/src/api/urls.py b/src/api/urls.py index 28dc51c13..006abed60 100644 --- a/src/api/urls.py +++ b/src/api/urls.py @@ -22,6 +22,9 @@ book_resource = Resource(handler=handlers.BookDetailHandler) filter_book_resource = Resource(handler=handlers.FilterBooksHandler) epub_resource = Resource(handler=handlers.EpubHandler) +reading_resource = CsrfExemptResource(handler=handlers.UserDataHandler) +shelf_resource = Resource(handler=handlers.UserShelfHandler) + collection_resource = Resource(handler=handlers.CollectionDetailHandler) collection_list_resource = Resource(handler=handlers.CollectionsHandler) @@ -73,6 +76,11 @@ urlpatterns = [ # epub preview url(r'^epub/(?P[a-z0-9-]+)/$', epub_resource, name='api_epub'), + # reading data + url(r'^reading/(?P[a-z0-9-]+)/$', reading_resource, name='api_reading'), + url(r'^reading/(?P[a-z0-9-]+)/(?P[a-z]+)/$', reading_resource, name='api_reading'), + url(r'^shelf/(?P[a-z]+)/$', shelf_resource, name='api_shelf'), + # objects details url(r'^books/(?P[a-z0-9-]+)/$', book_resource, name="api_book"), url(r'^(?P[a-z0-9-]+)/(?P[a-z0-9-]+)/$', -- 2.20.1