This repository has been archived on 2026-07-21. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
cloud-reader/backend/apps/books/views.py
T
Marko (Hermes Implementer) b29ea8211c feat: customizable mobile reading experience
- Backend: Chapter, ReadingProgress, ReadingSettings models
- Backend: Chapter API (TOC + content), progress tracking, settings CRUD
- Frontend: ReadingPage with chapter navigation
- Frontend: TableOfContents drawer
- Frontend: ReadingSettingsPanel (theme, font, size, orientation)
- Frontend: Custom hooks for settings, chapters, progress tracking
- CSS: Mobile-first reading view with sepia/dark/light/paper themes
- Route: /reader/:bookId reading view from book detail page
- Docs: 001-customizable-mobile-reading-experience.md
2026-05-29 02:55:36 +00:00

75 lines
2.9 KiB
Python

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<chapter_number>[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)