reading state api
authorJan Szejko <janek37@gmail.com>
Tue, 5 Jun 2018 13:27:48 +0000 (15:27 +0200)
committerJan Szejko <janek37@gmail.com>
Tue, 5 Jun 2018 13:27:48 +0000 (15:27 +0200)
src/api/handlers.py
src/api/migrations/0003_bookuserdata.py [new file with mode: 0644]
src/api/models.py
src/api/urls.py

index 7772820..c899976 100644 (file)
@@ -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 (file)
index 0000000..11a2f8f
--- /dev/null
@@ -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)),
+            ],
+        ),
+    ]
index 2f74283..cc71a06 100644 (file)
@@ -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)
index 28dc51c..006abed 100644 (file)
@@ -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<slug>[a-z0-9-]+)/$', epub_resource, name='api_epub'),
 
+    # reading data
+    url(r'^reading/(?P<slug>[a-z0-9-]+)/$', reading_resource, name='api_reading'),
+    url(r'^reading/(?P<slug>[a-z0-9-]+)/(?P<state>[a-z]+)/$', reading_resource, name='api_reading'),
+    url(r'^shelf/(?P<state>[a-z]+)/$', shelf_resource, name='api_shelf'),
+
     # objects details
     url(r'^books/(?P<book>[a-z0-9-]+)/$', book_resource, name="api_book"),
     url(r'^(?P<category>[a-z0-9-]+)/(?P<slug>[a-z0-9-]+)/$',