Archived
- 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
88 lines
2.6 KiB
Python
88 lines
2.6 KiB
Python
from django.db import models
|
|
|
|
|
|
class Book(models.Model):
|
|
"""Represents a book in the user's library."""
|
|
|
|
title = models.CharField(max_length=512)
|
|
author = models.CharField(max_length=256, blank=True, default="")
|
|
total_pages = models.PositiveIntegerField(default=0)
|
|
cover_image = models.URLField(blank=True, default="")
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
updated_at = models.DateTimeField(auto_now=True)
|
|
|
|
class Meta:
|
|
db_table = "books_book"
|
|
verbose_name = "Book"
|
|
verbose_name_plural = "Books"
|
|
ordering = ["title"]
|
|
|
|
def __str__(self) -> str:
|
|
return self.title
|
|
|
|
|
|
class Chapter(models.Model):
|
|
"""A chapter within a book, containing the text/markdown content."""
|
|
|
|
book = models.ForeignKey(
|
|
Book,
|
|
on_delete=models.CASCADE,
|
|
related_name="chapters",
|
|
db_index=True,
|
|
)
|
|
title = models.CharField(max_length=512)
|
|
number = models.PositiveIntegerField()
|
|
content = models.TextField(blank=True, default="")
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
updated_at = models.DateTimeField(auto_now=True)
|
|
|
|
class Meta:
|
|
db_table = "books_chapter"
|
|
verbose_name = "Chapter"
|
|
verbose_name_plural = "Chapters"
|
|
ordering = ["book", "number"]
|
|
constraints = [
|
|
models.UniqueConstraint(
|
|
fields=["book", "number"],
|
|
name="uq_book_chapter_number",
|
|
)
|
|
]
|
|
|
|
def __str__(self) -> str:
|
|
return f"{self.book.title} — Ch. {self.number}: {self.title}"
|
|
|
|
|
|
class ReadingProgress(models.Model):
|
|
"""Tracks a user's reading progress within a book."""
|
|
|
|
user = models.ForeignKey(
|
|
"users.User",
|
|
on_delete=models.CASCADE,
|
|
related_name="reading_progress",
|
|
db_index=True,
|
|
)
|
|
book = models.ForeignKey(
|
|
Book,
|
|
on_delete=models.CASCADE,
|
|
related_name="reading_progress",
|
|
db_index=True,
|
|
)
|
|
current_chapter = models.PositiveIntegerField(default=1)
|
|
current_position = models.PositiveIntegerField(default=0)
|
|
percentage = models.FloatField(default=0.0)
|
|
updated_at = models.DateTimeField(auto_now=True)
|
|
|
|
class Meta:
|
|
db_table = "books_reading_progress"
|
|
verbose_name = "Reading Progress"
|
|
verbose_name_plural = "Reading Progress"
|
|
ordering = ["-updated_at"]
|
|
constraints = [
|
|
models.UniqueConstraint(
|
|
fields=["user", "book"],
|
|
name="uq_reading_progress_user_book",
|
|
)
|
|
]
|
|
|
|
def __str__(self) -> str:
|
|
return f"{self.user} — {self.book.title} ({self.percentage:.0f}%)" |