# Copyright © Fundacja Wolne Lektury. See NOTICE for more information.
#
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 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
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)
)
-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(
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 = [IsAuthenticated]
- serializer_class = UserListSerializer
+ permission_classes = [IsAuthenticatedOrReadOnly]
+
+ def get_serializer_class(self):
+ if self.request.version == 'v2':
+ return serializers.UserListSerializerV2
+ return serializers.UserListSerializerV3
def get_object(self):
- 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
@never_cache
-class ListItemView(APIView):
+class ListItemViewV2(APIView):
+ """v2 only"""
permission_classes = [IsAuthenticated]
def delete(self, request, slug, book):
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
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')
@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()
@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()
class ProgressSyncView(SyncView):
model = models.Progress
- serializer_class = ProgressSerializer
+ serializer_class = serializers.ProgressSerializer
sync_id_field = 'book__slug'
sync_id_serializer_field = 'book_slug'
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