from django_filters.rest_framework import DjangoFilterBackend from rest_framework import status, viewsets from rest_framework.decorators import action from rest_framework.filters import OrderingFilter, SearchFilter from rest_framework.permissions import IsAuthenticated from rest_framework.request import Request from rest_framework.response import Response from apps.books.models import Book, Chapter, ReadingProgress from apps.books.serializers import ( BookListSerializer, BookSerializer, ChapterDetailSerializer, ChapterSummarySerializer, ReadingProgressSerializer, ) class BookViewSet(viewsets.ModelViewSet): """CRUD for books. Includes nested chapter and progress endpoints.""" queryset = Book.objects.all().prefetch_related("chapters") permission_classes = [IsAuthenticated] filter_backends = [DjangoFilterBackend, SearchFilter, OrderingFilter] filterset_fields = ["author"] search_fields = ["title", "author"] ordering_fields = ["title", "author", "created_at"] ordering = ["title"] def get_serializer_class(self): if self.action == "list": return BookListSerializer return BookSerializer @action(detail=True, methods=["get"]) def chapters(self, request: Request, pk: int | None = None) -> Response: """List all chapters for this book (TOC).""" book = self.get_object() chapters = book.chapters.all().order_by("number") serializer = ChapterSummarySerializer(chapters, many=True) return Response(serializer.data) @action(detail=True, methods=["get"], url_path="chapters/(?P[0-9]+)") def chapter_detail( self, request: Request, pk: int | None = None, chapter_number: str | None = None ) -> Response: """Get a specific chapter with full content.""" book = self.get_object() try: chapter = book.chapters.get(number=int(chapter_number)) except Chapter.DoesNotExist: return Response( {"detail": "Chapter not found."}, status=status.HTTP_404_NOT_FOUND ) serializer = ChapterDetailSerializer(chapter) return Response(serializer.data) @action(detail=True, methods=["get", "put"]) def progress(self, request: Request, pk: int | None = None) -> Response: """Get or update reading progress for this book.""" book = self.get_object() progress, created = ReadingProgress.objects.get_or_create( user=request.user, book=book ) if request.method == "GET": serializer = ReadingProgressSerializer(progress) return Response(serializer.data) serializer = ReadingProgressSerializer( progress, data=request.data, partial=True ) serializer.is_valid(raise_exception=True) serializer.save() return Response(serializer.data, status=status.HTTP_200_OK)