from typing import Any from django.db.models import QuerySet, Q from django_filters.rest_framework import DjangoFilterBackend from rest_framework import parsers, permissions, status, viewsets from rest_framework.decorators import action from rest_framework.filters import OrderingFilter, SearchFilter from rest_framework.permissions import AllowAny, IsAuthenticated from rest_framework.request import Request from rest_framework.response import Response from apps.books.models import Book, EBook, ReadingProgress, ReadingSettings from apps.books.serializers import ( BookDetailSerializer, BookListSerializer, BookSerializer, EBookDetailSerializer, EBookListSerializer, EBookUploadSerializer, ReadingProgressSerializer, ReadingSettingsSerializer, ) class BookViewSet(viewsets.ModelViewSet): queryset = Book.objects.all() permission_classes = [IsAuthenticated] filter_backends = [DjangoFilterBackend, SearchFilter, OrderingFilter] filterset_fields = ["author", "genre", "reading_status"] search_fields = ["title", "author", "genre"] ordering_fields = ["title", "author", "genre", "created_at"] ordering = ["title"] def get_serializer_class(self): if self.action == "retrieve": return BookDetailSerializer if self.action == "list": return BookListSerializer return BookSerializer def get_queryset(self) -> QuerySet[Book]: qs = super().get_queryset() query = self.request.query_params.get("q", "").strip() if query: qs = qs.filter(Q(title__icontains=query) | Q(author__icontains=query) | Q(genre__icontains=query)) return qs @action(detail=False, methods=["get"], permission_classes=[AllowAny]) def genres(self, request: Request) -> Response: genre_list = Book.objects.values_list("genre", flat=True).distinct().order_by("genre") return Response([g for g in genre_list if g]) @action(detail=False, methods=["get"], permission_classes=[AllowAny]) def authors(self, request: Request) -> Response: author_list = Book.objects.values_list("author", flat=True).distinct().order_by("author") return Response([a for a in author_list if a]) class IsEBookOwner(permissions.BasePermission): def has_object_permission(self, request: Request, view: object, obj: EBook) -> bool: return obj.user == request.user class EBookViewSet(viewsets.ModelViewSet): parser_classes = [parsers.MultiPartParser, parsers.FormParser, parsers.JSONParser] permission_classes = [IsAuthenticated, IsEBookOwner] def get_serializer_class(self): if self.action == "create": return EBookUploadSerializer if self.action == "list": return EBookListSerializer return EBookDetailSerializer def get_queryset(self): return EBook.objects.filter(user=self.request.user).select_related("reading_progress", "user") @action(detail=True, methods=["get", "patch"]) def progress(self, request: Request, pk: int | None = None) -> Response: ebook = self.get_object() progress_obj, _created = ReadingProgress.objects.get_or_create(user=request.user, ebook=ebook) if request.method == "GET": serializer = ReadingProgressSerializer(progress_obj) return Response(serializer.data) serializer = ReadingProgressSerializer(progress_obj, data=request.data, partial=True) serializer.is_valid(raise_exception=True) serializer.save() return Response(serializer.data) class ReadingSettingsViewSet(viewsets.GenericViewSet): permission_classes = [IsAuthenticated] serializer_class = ReadingSettingsSerializer def get_queryset(self): return ReadingSettings.objects.filter(user=self.request.user) def list(self, request: Request) -> Response: settings_obj, _created = ReadingSettings.objects.get_or_create(user=request.user) serializer = self.get_serializer(settings_obj) return Response(serializer.data) def partial_update(self, request: Request) -> Response: settings_obj, _created = ReadingSettings.objects.get_or_create(user=request.user) serializer = self.get_serializer(settings_obj, data=request.data, partial=True) serializer.is_valid(raise_exception=True) serializer.save() return Response(serializer.data)