X-Git-Url: https://git.mdrn.pl/wolnelektury.git/blobdiff_plain/d2a9ebf1eae1ee5fa8a09a7dfea76995274f7716..d7b89d3f41ba44a7f327652014e6baff6999f70e:/src/social/api/views.py diff --git a/src/social/api/views.py b/src/social/api/views.py index 9d8fd4a59..2c2383591 100644 --- a/src/social/api/views.py +++ b/src/social/api/views.py @@ -5,10 +5,10 @@ from datetime import datetime from django.db.models import Q from django.http import Http404 from django.utils.timezone import now, utc +from rest_framework.exceptions import MethodNotAllowed from rest_framework.generics import ListAPIView, ListCreateAPIView, RetrieveAPIView, RetrieveUpdateAPIView, RetrieveUpdateDestroyAPIView, get_object_or_404 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 @@ -20,18 +20,14 @@ 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 SettingsSerializer(serializers.ModelSerializer): - class Meta: - model = models.UserProfile - fields = ['notifications'] - class SettingsView(RetrieveUpdateAPIView): permission_classes = [IsAuthenticated] - serializer_class = SettingsSerializer + serializer_class = serializers.SettingsSerializer def get_object(self): return models.UserProfile.get_for(self.request.user) @@ -102,106 +98,15 @@ 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): - client_id = serializers.CharField(write_only=True, required=False) - books = UserListItemsField(source='*', required=False) - timestamp = serializers.IntegerField(required=False) - - class Meta: - model = models.UserList - fields = [ - 'timestamp', - 'client_id', - 'name', - 'slug', - 'favorites', - 'deleted', - 'books', - ] - read_only_fields = ['favorites'] - extra_kwargs = { - 'slug': { - 'required': False - } - } - - 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'] - - -class UserListItemSerializer(serializers.ModelSerializer): - client_id = serializers.CharField(write_only=True, required=False) - favorites = serializers.BooleanField(required=False) - list_slug = serializers.SlugRelatedField( - queryset=models.UserList.objects.all(), - source='list', - slug_field='slug', - required=False, - ) - timestamp = serializers.IntegerField(required=False) - book_slug = serializers.SlugRelatedField( - queryset=Book.objects.all(), - source='book', - slug_field='slug', - required=False - ) - - class Meta: - model = models.UserListItem - fields = [ - 'client_id', - 'uuid', - 'order', - 'list_slug', - 'timestamp', - 'favorites', - 'deleted', - - 'book_slug', - 'fragment', - 'quote', - 'bookmark', - 'note', - ] - extra_kwargs = { - 'order': { - 'required': False - } - } - - @never_cache class ListsView(ListCreateAPIView): permission_classes = [IsAuthenticated] #pagination_class = None - serializer_class = UserListSerializer + + def get_serializer_class(self): + if self.request.version == 'v2': + return serializers.UserListSerializerV2 + return serializers.UserListSerializerV3 def get_queryset(self): return models.UserList.objects.filter( @@ -214,38 +119,51 @@ class ListsView(ListCreateAPIView): serializer.save(user=self.request.user) +def get_userlist(slug, request): + if request.method in SAFE_METHODS: + q = Q(deleted=False) + if request.user.is_authenticated: + q |= Q(user=request.user) + return get_object_or_404( + models.UserList, + q, + slug=slug, + ) + else: + return get_object_or_404( + models.UserList.all_objects.all(), + slug=slug, + user=request.user + ) + + @never_cache class ListView(RetrieveUpdateDestroyAPIView): # TODO: check if can modify permission_classes = [IsAuthenticatedOrReadOnly] - serializer_class = UserListSerializer + + def get_serializer_class(self): + if self.request.version == 'v2': + return serializers.UserListSerializerV2 + return serializers.UserListSerializerV3 def get_object(self): - 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, - slug=self.kwargs['slug'], - user=self.request.user) + return get_userlist(self.kwargs['slug'], self.request) def perform_update(self, serializer): serializer.save(user=self.request.user) def post(self, request, slug): - serializer = UserListBooksSerializer(data=request.data) - serializer.is_valid(raise_exception=True) - instance = self.get_object() - for book in serializer.validated_data['books']: - instance.append(book) - return Response(self.get_serializer(instance).data) + if request.version == 'v2': + # Accept posting a list of books here. + serializer = serializers.UserListBooksSerializer(data=request.data) + serializer.is_valid(raise_exception=True) + instance = self.get_object() + for book in serializer.validated_data['books']: + instance.append(book) + return Response(self.get_serializer(instance).data) + else: + raise MethodNotAllowed(method=request.method) def perform_destroy(self, instance): instance.deleted = True @@ -254,7 +172,8 @@ class ListView(RetrieveUpdateDestroyAPIView): @never_cache -class ListItemView(APIView): +class ListItemViewV2(APIView): + """v2 only""" permission_classes = [IsAuthenticated] def delete(self, request, slug, book): @@ -262,7 +181,42 @@ class ListItemView(APIView): models.UserList, slug=slug, user=self.request.user) book = get_object_or_404(catalogue.models.Book, slug=book) instance.remove(book=book) - return Response(UserListSerializer(instance).data) + return Response(serializers.UserListSerializerV2(instance).data) + + +@never_cache +class ListItemListViewV3(ListCreateAPIView): + permission_classes = [IsAuthenticatedOrReadOnly] + serializer_class = serializers.UserListItemSerializer + + def get_queryset(self): + lst = get_userlist(self.kwargs['slug'], self.request) + return lst.userlistitem_set.all().order_by('order') + + def get_serializer(self, *args, **kwargs): + serializer_class = self.get_serializer_class() + kwargs.setdefault('context', self.get_serializer_context()) + + if isinstance(self.request.data, list): + kwargs['many'] = True + + return serializer_class(*args, **kwargs) + + def perform_create(self, serializer): + lst = get_userlist(self.kwargs['slug'], self.request) + serializer.save(list=lst) + + +@never_cache +class ListItemViewV3(RetrieveUpdateDestroyAPIView): + permission_classes = [IsAuthenticated] + serializer_class = serializers.UserListItemSerializer + lookup_field = 'uuid' + + def get_queryset(self): + return models.UserListItem.objects.filter( + list__user=self.request.user + ) @vary_on_auth @@ -293,60 +247,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( - queryset=Book.objects.all(), - source='book', - slug_field='slug') - timestamp = serializers.IntegerField(required=False) - - class Meta: - model = models.Progress - fields = [ - 'timestamp', - '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', - ] - extra_kwargs = { - 'last_mode': { - 'required': False, - 'default': 'text', - } - } - - -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') @@ -365,15 +269,16 @@ 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.reported_timestamp = now() serializer.instance.last_mode = 'text' serializer.save() @@ -381,9 +286,10 @@ 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.reported_timestamp = now() serializer.instance.last_mode = 'audio' serializer.save() @@ -451,7 +357,7 @@ class SyncView(ListAPIView): class ProgressSyncView(SyncView): model = models.Progress - serializer_class = ProgressSerializer + serializer_class = serializers.ProgressSerializer sync_id_field = 'book__slug' sync_id_serializer_field = 'book_slug' @@ -459,19 +365,23 @@ class ProgressSyncView(SyncView): class UserListSyncView(SyncView): model = models.UserList - serializer_class = UserListSerializer + + def get_serializer_class(self): + if self.request.version == 'v2': + return serializers.UserListSerializerV2 + return serializers.UserListSerializerV3 class UserListItemSyncView(SyncView): model = models.UserListItem - serializer_class = UserListItemSerializer + 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.objects.filter( + qs = self.model.all_objects.filter( updated_at__gt=timestamp, **{ self.sync_user_field: self.request.user