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 django.http import Http404
5 from rest_framework.generics import ListAPIView, ListCreateAPIView, RetrieveAPIView, RetrieveUpdateAPIView, RetrieveUpdateDestroyAPIView, DestroyAPIView, get_object_or_404
6 from rest_framework.permissions import IsAuthenticated, IsAuthenticatedOrReadOnly
7 from rest_framework.response import Response
8 from rest_framework import serializers
9 from rest_framework.views import APIView
10 from api.models import BookUserData
11 from api.utils import vary_on_auth, never_cache
12 from catalogue.api.helpers import order_books, books_after
13 from catalogue.api.serializers import BookSerializer
14 from catalogue.models import Book
15 import catalogue.models
16 from social.utils import likes, get_set
17 from social.views import get_sets_for_book_ids
18 from social import models
22 class LikeView(APIView):
23 permission_classes = [IsAuthenticated]
25 def get(self, request, slug):
26 book = get_object_or_404(Book, slug=slug)
27 return Response({"likes": likes(request.user, book)})
29 def post(self, request, slug):
30 book = get_object_or_404(Book, slug=slug)
31 action = request.query_params.get('action', 'like')
33 book.like(request.user)
34 elif action == 'unlike':
35 book.unlike(request.user)
40 class LikeView2(APIView):
41 permission_classes = [IsAuthenticated]
43 def get(self, request, slug):
44 book = get_object_or_404(Book, slug=slug)
45 return Response({"likes": likes(request.user, book)})
47 def put(self, request, slug):
48 book = get_object_or_404(Book, slug=slug)
49 book.like(request.user)
50 return Response({"likes": likes(request.user, book)})
52 def delete(self, request, slug):
53 book = get_object_or_404(Book, slug=slug)
54 book.unlike(request.user)
55 return Response({"likes": likes(request.user, book)})
59 class LikesView(APIView):
60 permission_classes = [IsAuthenticated]
62 def get(self, request):
63 slugs = request.GET.getlist('slug')
64 books = Book.objects.filter(slug__in=slugs)
65 books = {b.id: b.slug for b in books}
67 res = get_sets_for_book_ids(ids, request.user)
68 res = {books[bid]: v for bid, v in res.items()}
74 class MyLikesView(APIView):
75 permission_classes = [IsAuthenticated]
77 def get(self, request):
78 ids = catalogue.models.tag.TagRelation.objects.filter(tag__user=request.user).values_list('object_id', flat=True).distinct()
79 books = Book.objects.filter(id__in=ids)
80 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()}
84 res = list(books.values())
89 class TaggedBooksField(serializers.Field):
90 def to_representation(self, value):
91 return catalogue.models.Book.tagged.with_all([value]).values_list('slug', flat=True)
93 def to_internal_value(self, value):
94 return {'books': catalogue.models.Book.objects.filter(slug__in=value)}
97 class UserListSerializer(serializers.ModelSerializer):
98 books = TaggedBooksField(source='*')
101 model = catalogue.models.Tag
102 fields = ['name', 'slug', 'books']
103 read_only_fields = ['slug']
105 def create(self, validated_data):
106 instance = get_set(validated_data['user'], validated_data['name'])
107 catalogue.models.tag.TagRelation.objects.filter(tag=instance).delete()
108 for book in validated_data['books']:
109 catalogue.models.Tag.objects.add_tag(book, instance)
112 def update(self, instance, validated_data):
113 catalogue.models.tag.TagRelation.objects.filter(tag=instance).delete()
114 for book in validated_data['books']:
115 catalogue.models.Tag.objects.add_tag(book, instance)
118 class UserListBooksSerializer(UserListSerializer):
120 model = catalogue.models.Tag
125 class ListsView(ListCreateAPIView):
126 permission_classes = [IsAuthenticated]
127 #pagination_class = None
128 serializer_class = UserListSerializer
130 def get_queryset(self):
131 return catalogue.models.Tag.objects.filter(user=self.request.user).exclude(name='')
133 def perform_create(self, serializer):
134 serializer.save(user=self.request.user)
138 class ListView(RetrieveUpdateDestroyAPIView):
139 # TODO: check if can modify
140 permission_classes = [IsAuthenticated]
141 serializer_class = UserListSerializer
143 def get_object(self):
144 return get_object_or_404(catalogue.models.Tag, slug=self.kwargs['slug'], user=self.request.user)
146 def perform_update(self, serializer):
147 serializer.save(user=self.request.user)
149 def post(self, request, slug):
150 serializer = UserListBooksSerializer(data=request.data)
151 serializer.is_valid(raise_exception=True)
152 instance = self.get_object()
153 for book in serializer.validated_data['books']:
154 catalogue.models.Tag.objects.add_tag(book, instance)
155 return Response(self.get_serializer(instance).data)
159 class ListItemView(APIView):
160 permission_classes = [IsAuthenticated]
162 def delete(self, request, slug, book):
163 instance = get_object_or_404(catalogue.models.Tag, slug=slug, user=self.request.user)
164 book = get_object_or_404(catalogue.models.Book, slug=book)
165 catalogue.models.Tag.objects.remove_tag(book, instance)
166 return Response(UserListSerializer(instance).data)
170 class ShelfView(ListAPIView):
171 permission_classes = [IsAuthenticated]
172 serializer_class = BookSerializer
173 pagination_class = None
175 def get_queryset(self):
176 state = self.kwargs['state']
177 if state not in ('reading', 'complete', 'likes'):
179 new_api = self.request.query_params.get('new_api')
180 after = self.request.query_params.get('after')
181 count = int(self.request.query_params.get('count', 50))
183 books = Book.tagged.with_any(self.request.user.tag_set.all())
185 ids = BookUserData.objects.filter(user=self.request.user, complete=state == 'complete')\
186 .values_list('book_id', flat=True)
187 books = Book.objects.filter(id__in=list(ids)).distinct()
188 books = order_books(books, new_api)
190 books = books_after(books, after, new_api)
192 books = books[:count]
198 class ProgressSerializer(serializers.ModelSerializer):
199 book = serializers.HyperlinkedRelatedField(
201 view_name='catalogue_api_book',
204 book_slug = serializers.SlugRelatedField(source='book', read_only=True, slug_field='slug')
207 model = models.Progress
208 fields = ['book', 'book_slug', 'last_mode', 'text_percent',
212 'implicit_text_percent',
213 'implicit_text_anchor',
214 'implicit_audio_percent',
215 'implicit_audio_timestamp',
219 class TextProgressSerializer(serializers.ModelSerializer):
221 model = models.Progress
226 read_only_fields = ['text_percent']
228 class AudioProgressSerializer(serializers.ModelSerializer):
230 model = models.Progress
231 fields = ['audio_percent', 'audio_timestamp']
232 read_only_fields = ['audio_percent']
236 class ProgressListView(ListAPIView):
237 permission_classes = [IsAuthenticated]
238 serializer_class = ProgressSerializer
240 def get_queryset(self):
241 return models.Progress.objects.filter(user=self.request.user).order_by('-updated_at')
245 def get_object(self):
247 return models.Progress.objects.get(user=self.request.user, book__slug=self.kwargs['slug'])
248 except models.Progress.DoesNotExist:
249 book = get_object_or_404(Book, slug=self.kwargs['slug'])
250 return models.Progress(user=self.request.user, book=book)
255 class ProgressView(ProgressMixin, RetrieveAPIView):
256 permission_classes = [IsAuthenticated]
257 serializer_class = ProgressSerializer
261 class TextProgressView(ProgressMixin, RetrieveUpdateAPIView):
262 permission_classes = [IsAuthenticated]
263 serializer_class = TextProgressSerializer
265 def perform_update(self, serializer):
266 serializer.instance.last_mode = 'text'
271 class AudioProgressView(ProgressMixin, RetrieveUpdateAPIView):
272 permission_classes = [IsAuthenticated]
273 serializer_class = AudioProgressSerializer
275 def perform_update(self, serializer):
276 serializer.instance.last_mode = 'audio'