1 # This file is part of Wolne Lektury, licensed under GNU Affero GPLv3 or later.
2 # Copyright © Fundacja Wolne Lektury. See NOTICE for more information.
4 from datetime import datetime
5 from django.db.models import Q
6 from django.http import Http404
7 from django.utils.timezone import now, utc
8 from rest_framework.generics import ListAPIView, ListCreateAPIView, RetrieveAPIView, RetrieveUpdateAPIView, RetrieveUpdateDestroyAPIView, get_object_or_404
9 from rest_framework.permissions import SAFE_METHODS, IsAuthenticated, IsAuthenticatedOrReadOnly
10 from rest_framework.response import Response
11 from rest_framework.views import APIView
12 from api.models import BookUserData
13 from api.utils import vary_on_auth, never_cache
14 from catalogue.api.helpers import order_books, books_after
15 from catalogue.api.serializers import BookSerializer
16 from catalogue.models import Book
17 import catalogue.models
18 from social.views import get_sets_for_book_ids
19 from social.utils import likes
20 from social import models
21 import bookmarks.models
22 from . import serializers
23 from bookmarks.api.views import BookmarkSerializer
27 class SettingsView(RetrieveUpdateAPIView):
28 permission_classes = [IsAuthenticated]
29 serializer_class = serializers.SettingsSerializer
32 return models.UserProfile.get_for(self.request.user)
36 class LikeView(APIView):
37 permission_classes = [IsAuthenticated]
39 def get(self, request, slug):
40 book = get_object_or_404(Book, slug=slug)
41 return Response({"likes": likes(request.user, book)})
43 def post(self, request, slug):
44 book = get_object_or_404(Book, slug=slug)
45 action = request.query_params.get('action', 'like')
47 models.UserList.like(request.user, book)
48 elif action == 'unlike':
49 models.UserList.unlike(request.user, book)
54 class LikeView2(APIView):
55 permission_classes = [IsAuthenticated]
57 def get(self, request, slug):
58 book = get_object_or_404(Book, slug=slug)
59 return Response({"likes": likes(request.user, book)})
61 def put(self, request, slug):
62 book = get_object_or_404(Book, slug=slug)
63 models.UserList.like(request.user, book)
64 return Response({"likes": likes(request.user, book)})
66 def delete(self, request, slug):
67 book = get_object_or_404(Book, slug=slug)
68 models.UserList.unlike(request.user, book)
69 return Response({"likes": likes(request.user, book)})
73 class LikesView(APIView):
74 permission_classes = [IsAuthenticated]
76 def get(self, request):
77 slugs = request.GET.getlist('slug')
78 books = Book.objects.filter(slug__in=slugs)
79 books = {b.id: b.slug for b in books}
81 res = get_sets_for_book_ids(ids, request.user)
82 res = {books[bid]: v for bid, v in res.items()}
88 class MyLikesView(APIView):
89 permission_classes = [IsAuthenticated]
91 def get(self, request):
92 ul = models.UserList.get_favorites_list(request.user)
96 ul.userlistitem_set.exclude(deleted=True).exclude(book=None).values_list('book__slug', flat=True)
101 class ListsView(ListCreateAPIView):
102 permission_classes = [IsAuthenticated]
103 #pagination_class = None
104 serializer_class = serializers.UserListSerializer
106 def get_queryset(self):
107 return models.UserList.objects.filter(
108 user=self.request.user,
113 def perform_create(self, serializer):
114 serializer.save(user=self.request.user)
118 class ListView(RetrieveUpdateDestroyAPIView):
119 # TODO: check if can modify
120 permission_classes = [IsAuthenticatedOrReadOnly]
121 serializer_class = serializers.UserListSerializer
123 def get_object(self):
124 if self.request.method in SAFE_METHODS:
126 if self.request.user.is_authenticated:
127 q |= Q(user=self.request.user)
128 return get_object_or_404(
131 slug=self.kwargs['slug'],
134 return get_object_or_404(
135 models.UserList.all_objects.all(),
136 slug=self.kwargs['slug'],
137 user=self.request.user)
139 def perform_update(self, serializer):
140 serializer.save(user=self.request.user)
142 def post(self, request, slug):
143 serializer = serializers.UserListBooksSerializer(data=request.data)
144 serializer.is_valid(raise_exception=True)
145 instance = self.get_object()
146 for book in serializer.validated_data['books']:
147 instance.append(book)
148 return Response(self.get_serializer(instance).data)
150 def perform_destroy(self, instance):
151 instance.deleted = True
152 instance.updated_at = now()
157 class ListItemView(APIView):
158 permission_classes = [IsAuthenticated]
160 def delete(self, request, slug, book):
161 instance = get_object_or_404(
162 models.UserList, slug=slug, user=self.request.user)
163 book = get_object_or_404(catalogue.models.Book, slug=book)
164 instance.remove(book=book)
165 return Response(UserListSerializer(instance).data)
169 class ShelfView(ListAPIView):
170 permission_classes = [IsAuthenticated]
171 serializer_class = BookSerializer
172 pagination_class = None
174 def get_queryset(self):
175 state = self.kwargs['state']
176 if state not in ('reading', 'complete', 'likes'):
178 new_api = self.request.query_params.get('new_api')
179 after = self.request.query_params.get('after')
180 count = int(self.request.query_params.get('count', 50))
182 books = Book.objects.filter(userlistitem__list__user=self.request.user)
184 ids = BookUserData.objects.filter(user=self.request.user, complete=state == 'complete')\
185 .values_list('book_id', flat=True)
186 books = Book.objects.filter(id__in=list(ids)).distinct()
187 books = order_books(books, new_api)
189 books = books_after(books, after, new_api)
191 books = books[:count]
197 class ProgressListView(ListAPIView):
198 permission_classes = [IsAuthenticated]
199 serializer_class = serializers.ProgressSerializer
201 def get_queryset(self):
202 return models.Progress.objects.filter(user=self.request.user).order_by('-updated_at')
206 def get_object(self):
208 return models.Progress.objects.get(user=self.request.user, book__slug=self.kwargs['slug'])
209 except models.Progress.DoesNotExist:
210 book = get_object_or_404(Book, slug=self.kwargs['slug'])
211 return models.Progress(user=self.request.user, book=book)
216 class ProgressView(ProgressMixin, RetrieveAPIView):
217 permission_classes = [IsAuthenticated]
218 serializer_class = serializers.ProgressSerializer
222 class TextProgressView(ProgressMixin, RetrieveUpdateAPIView):
223 permission_classes = [IsAuthenticated]
224 serializer_class = serializers.TextProgressSerializer
226 def perform_update(self, serializer):
227 serializer.instance.last_mode = 'text'
232 class AudioProgressView(ProgressMixin, RetrieveUpdateAPIView):
233 permission_classes = [IsAuthenticated]
234 serializer_class = serializers.AudioProgressSerializer
236 def perform_update(self, serializer):
237 serializer.instance.last_mode = 'audio'
243 class SyncView(ListAPIView):
244 permission_classes = [IsAuthenticated]
245 sync_id_field = 'slug'
246 sync_id_serializer_field = 'slug'
247 sync_user_field = 'user'
249 def get_queryset(self):
251 timestamp = int(self.request.GET.get('ts'))
255 timestamp = datetime.fromtimestamp(timestamp, tz=utc)
258 return self.get_queryset_for_ts(timestamp)
260 def get_queryset_for_ts(self, timestamp):
261 return self.model.objects.filter(
262 updated_at__gt=timestamp,
264 self.sync_user_field: self.request.user
266 ).order_by('updated_at')
268 def get_instance(self, user, data):
269 sync_id = data.get(self.sync_id_serializer_field)
272 return self.model.objects.filter(**{
273 self.sync_user_field: user,
274 self.sync_id_field: sync_id
277 def post(self, request):
280 if not isinstance(data, list):
281 raise serializers.ValidationError('Payload should be a list')
283 instance = self.get_instance(request.user, item)
284 ser = self.get_serializer(
288 ser.is_valid(raise_exception=True)
289 synced_instance = self.model.sync(
294 if instance is None and 'client_id' in ser.validated_data and synced_instance is not None:
296 'client_id': ser.validated_data['client_id'],
297 self.sync_id_serializer_field: getattr(synced_instance, self.sync_id_field),
299 return Response(new_ids)
302 class ProgressSyncView(SyncView):
303 model = models.Progress
304 serializer_class = serializers.ProgressSerializer
306 sync_id_field = 'book__slug'
307 sync_id_serializer_field = 'book_slug'
310 class UserListSyncView(SyncView):
311 model = models.UserList
312 serializer_class = serializers.UserListSerializer
315 class UserListItemSyncView(SyncView):
316 model = models.UserListItem
317 serializer_class = serializers.UserListItemSerializer
319 sync_id_field = 'uuid'
320 sync_id_serializer_field = 'uuid'
321 sync_user_field = 'list__user'
323 def get_queryset_for_ts(self, timestamp):
324 qs = self.model.all_objects.filter(
325 updated_at__gt=timestamp,
327 self.sync_user_field: self.request.user
330 if self.request.query_params.get('favorites'):
331 qs = qs.filter(list__favorites=True)
332 return qs.order_by('updated_at')
335 class BookmarkSyncView(SyncView):
336 model = bookmarks.models.Bookmark
337 serializer_class = BookmarkSerializer
339 sync_id_field = 'uuid'
340 sync_id_serializer_field = 'uuid'
342 def get_instance(self, user, data):
343 ret = super().get_instance(user, data)
345 if data.get('location'):
346 ret = self.model.get_by_location(user, data['location'])