maint: move code
[wolnelektury.git] / src / social / api / views.py
index 4f4937b..a43bec2 100644 (file)
@@ -2,13 +2,12 @@
 # Copyright © Fundacja Wolne Lektury. See NOTICE for more information.
 #
 from datetime import datetime
-from pytz import utc
+from django.db.models import Q
 from django.http import Http404
-from django.utils.timezone import now
+from django.utils.timezone import now, utc
 from rest_framework.generics import ListAPIView, ListCreateAPIView, RetrieveAPIView, RetrieveUpdateAPIView, RetrieveUpdateDestroyAPIView, get_object_or_404
-from rest_framework.permissions import IsAuthenticated, IsAuthenticatedOrReadOnly
+from rest_framework.permissions import SAFE_METHODS, IsAuthenticated, IsAuthenticatedOrReadOnly
 from rest_framework.response import Response
-from rest_framework import serializers
 from rest_framework.views import APIView
 from api.models import BookUserData
 from api.utils import vary_on_auth, never_cache
@@ -17,7 +16,20 @@ from catalogue.api.serializers import BookSerializer
 from catalogue.models import Book
 import catalogue.models
 from social.views import get_sets_for_book_ids
+from social.utils import likes
 from social import models
+import bookmarks.models
+from . import serializers
+from bookmarks.api.views import BookmarkSerializer
+
+
+
+class SettingsView(RetrieveUpdateAPIView):
+    permission_classes = [IsAuthenticated]
+    serializer_class = serializers.SettingsSerializer
+
+    def get_object(self):
+        return models.UserProfile.get_for(self.request.user)
 
 
 @never_cache
@@ -85,50 +97,11 @@ class MyLikesView(APIView):
         )
 
 
-class UserListItemsField(serializers.Field):
-    def to_representation(self, value):
-        return value.userlistitem_set.exclude(deleted=True).exclude(book=None).values_list('book__slug', flat=True)
-
-    def to_internal_value(self, value):
-        return {'books': catalogue.models.Book.objects.filter(slug__in=value)}
-
-
-class UserListSerializer(serializers.ModelSerializer):
-    books = UserListItemsField(source='*')
-
-    class Meta:
-        model = models.UserList
-        fields = ['name', 'slug', 'books']
-        read_only_fields = ['slug']
-
-    def create(self, validated_data):
-        instance = models.UserList.get_by_name(
-            validated_data['user'],
-            validated_data['name'],
-            create=True
-        )
-        instance.userlistitem_set.all().delete()
-        for book in validated_data['books']:
-            instance.append(book)
-        return instance
-
-    def update(self, instance, validated_data):
-        instance.userlistitem_set.all().delete()
-        for book in validated_data['books']:
-            instance.append(instance)
-        return instance
-
-class UserListBooksSerializer(UserListSerializer):
-    class Meta:
-        model = models.UserList
-        fields = ['books']
-
-
 @never_cache
 class ListsView(ListCreateAPIView):
     permission_classes = [IsAuthenticated]
     #pagination_class = None
-    serializer_class = UserListSerializer
+    serializer_class = serializers.UserListSerializer
 
     def get_queryset(self):
         return models.UserList.objects.filter(
@@ -144,20 +117,30 @@ class ListsView(ListCreateAPIView):
 @never_cache
 class ListView(RetrieveUpdateDestroyAPIView):
     # TODO: check if can modify
-    permission_classes = [IsAuthenticated]
-    serializer_class = UserListSerializer
+    permission_classes = [IsAuthenticatedOrReadOnly]
+    serializer_class = serializers.UserListSerializer
 
     def get_object(self):
-        return get_object_or_404(
-            models.UserList,
-            slug=self.kwargs['slug'],
-            user=self.request.user)
+        if self.request.method in SAFE_METHODS:
+            q = Q(deleted=False)
+            if self.request.user.is_authenticated:
+                q |= Q(user=self.request.user)
+            return get_object_or_404(
+                models.UserList,
+                q,
+                slug=self.kwargs['slug'],
+            )
+        else:
+            return get_object_or_404(
+                models.UserList.all_objects.all(),
+                slug=self.kwargs['slug'],
+                user=self.request.user)
 
     def perform_update(self, serializer):
         serializer.save(user=self.request.user)
 
     def post(self, request, slug):
-        serializer = UserListBooksSerializer(data=request.data)
+        serializer = serializers.UserListBooksSerializer(data=request.data)
         serializer.is_valid(raise_exception=True)
         instance = self.get_object()
         for book in serializer.validated_data['books']:
@@ -165,10 +148,9 @@ class ListView(RetrieveUpdateDestroyAPIView):
         return Response(self.get_serializer(instance).data)
 
     def perform_destroy(self, instance):
-        instance.update(
-            deleted=True,
-            updated_at=now()
-        )
+        instance.deleted = True
+        instance.updated_at = now()
+        instance.save()
 
 
 @never_cache
@@ -211,48 +193,10 @@ class ShelfView(ListAPIView):
         return books
 
 
-
-class ProgressSerializer(serializers.ModelSerializer):
-    book = serializers.HyperlinkedRelatedField(
-        read_only=True,
-        view_name='catalogue_api_book',
-        lookup_field='slug'
-    )
-    book_slug = serializers.SlugRelatedField(source='book', read_only=True, slug_field='slug')
-
-    class Meta:
-        model = models.Progress
-        fields = ['book', 'book_slug', 'last_mode', 'text_percent',
-    'text_anchor',
-    'audio_percent',
-    'audio_timestamp',
-    'implicit_text_percent',
-    'implicit_text_anchor',
-    'implicit_audio_percent',
-    'implicit_audio_timestamp',
-    ]
-
-
-class TextProgressSerializer(serializers.ModelSerializer):
-    class Meta:
-        model = models.Progress
-        fields = [
-                'text_percent',
-                'text_anchor',
-                ]
-        read_only_fields = ['text_percent']
-
-class AudioProgressSerializer(serializers.ModelSerializer):
-    class Meta:
-        model = models.Progress
-        fields = ['audio_percent', 'audio_timestamp']
-        read_only_fields = ['audio_percent']
-
-
 @never_cache
 class ProgressListView(ListAPIView):
     permission_classes = [IsAuthenticated]
-    serializer_class = ProgressSerializer
+    serializer_class = serializers.ProgressSerializer
 
     def get_queryset(self):
         return models.Progress.objects.filter(user=self.request.user).order_by('-updated_at')
@@ -271,13 +215,13 @@ class ProgressMixin:
 @never_cache
 class ProgressView(ProgressMixin, RetrieveAPIView):
     permission_classes = [IsAuthenticated]
-    serializer_class = ProgressSerializer
+    serializer_class = serializers.ProgressSerializer
 
 
 @never_cache
 class TextProgressView(ProgressMixin, RetrieveUpdateAPIView):
     permission_classes = [IsAuthenticated]
-    serializer_class = TextProgressSerializer
+    serializer_class = serializers.TextProgressSerializer
 
     def perform_update(self, serializer):
         serializer.instance.last_mode = 'text'
@@ -287,7 +231,7 @@ class TextProgressView(ProgressMixin, RetrieveUpdateAPIView):
 @never_cache
 class AudioProgressView(ProgressMixin, RetrieveUpdateAPIView):
     permission_classes = [IsAuthenticated]
-    serializer_class = AudioProgressSerializer
+    serializer_class = serializers.AudioProgressSerializer
 
     def perform_update(self, serializer):
         serializer.instance.last_mode = 'audio'
@@ -295,25 +239,12 @@ class AudioProgressView(ProgressMixin, RetrieveUpdateAPIView):
 
 
 
-class SyncSerializer(serializers.Serializer):
-    timestamp = serializers.IntegerField()
-    type = serializers.CharField()
-    id = serializers.CharField()
-
-    def to_representation(self, instance):
-        rep = super().to_representation(instance)
-        rep['object'] = instance['object'].data
-        return rep
-
-    def to_internal_value(self, data):
-        ret = super().to_internal_value(data)
-        ret['object'] = data['object']
-        return ret
-
-
+@never_cache
 class SyncView(ListAPIView):
     permission_classes = [IsAuthenticated]
-    serializer_class = SyncSerializer
+    sync_id_field = 'slug'
+    sync_id_serializer_field = 'slug'
+    sync_user_field = 'user'
 
     def get_queryset(self):
         try:
@@ -324,30 +255,93 @@ class SyncView(ListAPIView):
         timestamp = datetime.fromtimestamp(timestamp, tz=utc)
         
         data = []
-        for p in models.Progress.objects.filter(
-                user=self.request.user,
-                updated_at__gt=timestamp).order_by('updated_at'):
-            data.append({
-                'timestamp': p.updated_at.timestamp(),
-                'type': 'progress',
-                'id': p.book.slug,
-                'object': ProgressSerializer(
-                    p, context={'request': self.request}
-                ) if not p.deleted else None
-            })
-        return data
+        return self.get_queryset_for_ts(timestamp)
+
+    def get_queryset_for_ts(self, timestamp):
+        return self.model.objects.filter(
+            updated_at__gt=timestamp,
+            **{
+                self.sync_user_field: self.request.user
+            }
+        ).order_by('updated_at')
+
+    def get_instance(self, user, data):
+        sync_id = data.get(self.sync_id_serializer_field)
+        if not sync_id:
+            return None
+        return self.model.objects.filter(**{
+            self.sync_user_field: user,
+            self.sync_id_field: sync_id
+        }).first()
 
     def post(self, request):
+        new_ids = []
         data = request.data
+        if not isinstance(data, list):
+            raise serializers.ValidationError('Payload should be a list')
         for item in data:
-            ser = SyncSerializer(data=item)
+            instance = self.get_instance(request.user, item)
+            ser = self.get_serializer(
+                instance=instance,
+                data=item
+            )
             ser.is_valid(raise_exception=True)
-            d = ser.validated_data
-            if d['type'] == 'progress':
-                models.Progress.sync(
-                    user=request.user,
-                    slug=d['id'],
-                    ts=datetime.fromtimestamp(d['timestamp'], tz=utc),
-                    data=d['object']
-                )
-        return Response()
+            synced_instance = self.model.sync(
+                request.user,
+                instance,
+                ser.validated_data
+            )
+            if instance is None and 'client_id' in ser.validated_data and synced_instance is not None:
+                new_ids.append({
+                    'client_id': ser.validated_data['client_id'],
+                    self.sync_id_serializer_field: getattr(synced_instance, self.sync_id_field),
+                })
+        return Response(new_ids)
+
+
+class ProgressSyncView(SyncView):
+    model = models.Progress
+    serializer_class = serializers.ProgressSerializer
+    
+    sync_id_field = 'book__slug'
+    sync_id_serializer_field = 'book_slug'
+
+
+class UserListSyncView(SyncView):
+    model = models.UserList
+    serializer_class = serializers.UserListSerializer
+
+
+class UserListItemSyncView(SyncView):
+    model = models.UserListItem
+    serializer_class = serializers.UserListItemSerializer
+
+    sync_id_field = 'uuid'
+    sync_id_serializer_field = 'uuid'
+    sync_user_field = 'list__user'
+
+    def get_queryset_for_ts(self, timestamp):
+        qs = self.model.all_objects.filter(
+            updated_at__gt=timestamp,
+            **{
+                self.sync_user_field: self.request.user
+            }
+        )
+        if self.request.query_params.get('favorites'):
+            qs = qs.filter(list__favorites=True)
+        return qs.order_by('updated_at')
+
+
+class BookmarkSyncView(SyncView):
+    model = bookmarks.models.Bookmark
+    serializer_class = BookmarkSerializer
+
+    sync_id_field = 'uuid'
+    sync_id_serializer_field = 'uuid'
+
+    def get_instance(self, user, data):
+        ret = super().get_instance(user, data)
+        if ret is None:
+            if data.get('location'):
+                ret = self.model.get_by_location(user, data['location'])
+        return ret