867a05a547014d528ebb79bebb8f7503804566f6
[wolnelektury.git] / src / social / api / views.py
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.
3 #
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
19
20
21 @never_cache
22 class LikeView(APIView):
23     permission_classes = [IsAuthenticated]
24
25     def get(self, request, slug):
26         book = get_object_or_404(Book, slug=slug)
27         return Response({"likes": likes(request.user, book)})
28
29     def post(self, request, slug):
30         book = get_object_or_404(Book, slug=slug)
31         action = request.query_params.get('action', 'like')
32         if action == 'like':
33             book.like(request.user)
34         elif action == 'unlike':
35             book.unlike(request.user)
36         return Response({})
37
38
39 @never_cache
40 class LikeView2(APIView):
41     permission_classes = [IsAuthenticated]
42
43     def get(self, request, slug):
44         book = get_object_or_404(Book, slug=slug)
45         return Response({"likes": likes(request.user, book)})
46
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)})
51
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)})
56
57
58 @never_cache
59 class LikesView(APIView):
60     permission_classes = [IsAuthenticated]
61
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}
66         ids = books.keys()
67         res = get_sets_for_book_ids(ids, request.user)
68         res = {books[bid]: v for bid, v in res.items()}
69
70         return Response(res)
71
72
73 @never_cache
74 class MyLikesView(APIView):
75     permission_classes = [IsAuthenticated]
76
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()}
83
84         res = list(books.values())
85         res.sort()
86         return Response(res)
87
88
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)
92
93     def to_internal_value(self, value):
94         return {'books': catalogue.models.Book.objects.filter(slug__in=value)}
95
96
97 class UserListSerializer(serializers.ModelSerializer):
98     books = TaggedBooksField(source='*')
99
100     class Meta:
101         model = catalogue.models.Tag
102         fields = ['name', 'slug', 'books']
103         read_only_fields = ['slug']
104
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)
110         return instance
111
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)
116         return instance
117
118 class UserListBooksSerializer(UserListSerializer):
119     class Meta:
120         model = catalogue.models.Tag
121         fields = ['books']
122
123
124 @never_cache
125 class ListsView(ListCreateAPIView):
126     permission_classes = [IsAuthenticated]
127     #pagination_class = None
128     serializer_class = UserListSerializer
129
130     def get_queryset(self):
131         return catalogue.models.Tag.objects.filter(user=self.request.user).exclude(name='')
132
133     def perform_create(self, serializer):
134         serializer.save(user=self.request.user)
135
136
137 @never_cache
138 class ListView(RetrieveUpdateDestroyAPIView):
139     # TODO: check if can modify
140     permission_classes = [IsAuthenticated]
141     serializer_class = UserListSerializer
142
143     def get_object(self):
144         return get_object_or_404(catalogue.models.Tag, slug=self.kwargs['slug'], user=self.request.user)
145
146     def perform_update(self, serializer):
147         serializer.save(user=self.request.user)
148
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)
156
157
158 @never_cache
159 class ListItemView(APIView):
160     permission_classes = [IsAuthenticated]
161
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)
167
168
169 @vary_on_auth
170 class ShelfView(ListAPIView):
171     permission_classes = [IsAuthenticated]
172     serializer_class = BookSerializer
173     pagination_class = None
174
175     def get_queryset(self):
176         state = self.kwargs['state']
177         if state not in ('reading', 'complete', 'likes'):
178             raise Http404
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))
182         if state == 'likes':
183             books = Book.tagged.with_any(self.request.user.tag_set.all())
184         else:
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)
189         if after:
190             books = books_after(books, after, new_api)
191         if count:
192             books = books[:count]
193
194         return books
195
196
197
198 class ProgressSerializer(serializers.ModelSerializer):
199     book = serializers.HyperlinkedRelatedField(
200         read_only=True,
201         view_name='catalogue_api_book',
202         lookup_field='slug'
203     )
204     book_slug = serializers.SlugRelatedField(source='book', read_only=True, slug_field='slug')
205
206     class Meta:
207         model = models.Progress
208         fields = ['book', 'book_slug', 'last_mode', 'text_percent',
209     'text_anchor',
210     'audio_percent',
211     'audio_timestamp',
212     'implicit_text_percent',
213     'implicit_text_anchor',
214     'implicit_audio_percent',
215     'implicit_audio_timestamp',
216     ]
217
218
219 class TextProgressSerializer(serializers.ModelSerializer):
220     class Meta:
221         model = models.Progress
222         fields = [
223                 'text_percent',
224                 'text_anchor',
225                 ]
226         read_only_fields = ['text_percent']
227
228 class AudioProgressSerializer(serializers.ModelSerializer):
229     class Meta:
230         model = models.Progress
231         fields = ['audio_percent', 'audio_timestamp']
232         read_only_fields = ['audio_percent']
233
234
235 @never_cache
236 class ProgressListView(ListAPIView):
237     permission_classes = [IsAuthenticated]
238     serializer_class = ProgressSerializer
239
240     def get_queryset(self):
241         return models.Progress.objects.filter(user=self.request.user).order_by('-updated_at')
242
243
244 class ProgressMixin:
245     def get_object(self):
246         try:
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)
251
252
253
254 @never_cache
255 class ProgressView(ProgressMixin, RetrieveAPIView):
256     permission_classes = [IsAuthenticated]
257     serializer_class = ProgressSerializer
258
259
260 @never_cache
261 class TextProgressView(ProgressMixin, RetrieveUpdateAPIView):
262     permission_classes = [IsAuthenticated]
263     serializer_class = TextProgressSerializer
264
265     def perform_update(self, serializer):
266         serializer.instance.last_mode = 'text'
267         serializer.save()
268
269
270 @never_cache
271 class AudioProgressView(ProgressMixin, RetrieveUpdateAPIView):
272     permission_classes = [IsAuthenticated]
273     serializer_class = AudioProgressSerializer
274
275     def perform_update(self, serializer):
276         serializer.instance.last_mode = 'audio'
277         serializer.save()
278