mirror of
https://github.com/ajurna/cbwebreader.git
synced 2025-12-06 06:17:17 +00:00
Merge pull request #77
* added timestamp to comicstatus. * added timestamp to comicstatus.
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from django.contrib import admin
|
||||
|
||||
from .models import Directory, ComicBook, ComicPage, ComicStatus, UserMisc
|
||||
from .models import Directory, ComicBook, ComicStatus, UserMisc
|
||||
|
||||
|
||||
@admin.register(Directory)
|
||||
@@ -26,12 +26,6 @@ class ComicBookAdmin(admin.ModelAdmin):
|
||||
search_fields = ['file_name']
|
||||
|
||||
|
||||
@admin.register(ComicPage)
|
||||
class ComicPageAdmin(admin.ModelAdmin):
|
||||
list_display = ('id', 'Comic', 'index', 'page_file_name', 'content_type')
|
||||
raw_id_fields = ('Comic',)
|
||||
|
||||
|
||||
@admin.register(ComicStatus)
|
||||
class ComicStatusAdmin(admin.ModelAdmin):
|
||||
list_display = (
|
||||
|
||||
@@ -1,114 +1,38 @@
|
||||
from pathlib import Path
|
||||
from typing import Optional, Union
|
||||
|
||||
from PIL import UnidentifiedImageError
|
||||
from django.conf import settings
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.contrib.auth.models import User
|
||||
from django.core.management.base import BaseCommand, CommandParser
|
||||
from loguru import logger
|
||||
|
||||
from comic.models import ComicBook, Directory
|
||||
from comic.processing import generate_directory
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = "Scan directories to Update Comic DB"
|
||||
|
||||
def __init__(self):
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
self.OUTPUT = False
|
||||
self.VERIFY_PAGES = False
|
||||
|
||||
def add_arguments(self, parser):
|
||||
def add_arguments(self, parser: CommandParser) -> None:
|
||||
parser.add_argument(
|
||||
'--out',
|
||||
action='store_true',
|
||||
help='Output to console',
|
||||
)
|
||||
parser.add_argument(
|
||||
'--verify_pages',
|
||||
action='store_true',
|
||||
help='Output to console',
|
||||
)
|
||||
|
||||
def handle(self, *args, **options):
|
||||
self.VERIFY_PAGES = options.get('verify_pages', False)
|
||||
def handle(self, *args, **options) -> None:
|
||||
self.OUTPUT = options.get('out', False)
|
||||
self.scan_directory()
|
||||
|
||||
def scan_directory(self, directory=False):
|
||||
|
||||
"""
|
||||
|
||||
:type directory: Directory
|
||||
"""
|
||||
if not directory:
|
||||
comic_dir = settings.COMIC_BOOK_VOLUME
|
||||
else:
|
||||
comic_dir = Path(settings.COMIC_BOOK_VOLUME, directory.path)
|
||||
if directory:
|
||||
books = ComicBook.objects.filter(directory=directory)
|
||||
else:
|
||||
books = ComicBook.objects.filter(directory__isnull=True)
|
||||
for book in books:
|
||||
Path(comic_dir, book.file_name).is_file()
|
||||
if not Path(comic_dir, book.file_name).is_file():
|
||||
book.delete()
|
||||
for file in sorted(comic_dir.glob('*')):
|
||||
if file.is_dir():
|
||||
if self.OUTPUT:
|
||||
logger.info(f"Scanning Directory {file}")
|
||||
try:
|
||||
if directory:
|
||||
next_directory, created = Directory.objects.get_or_create(name=file.name, parent=directory)
|
||||
else:
|
||||
next_directory, created = Directory.objects.get_or_create(name=file.name, parent__isnull=True)
|
||||
except Directory.MultipleObjectsReturned:
|
||||
if directory:
|
||||
next_directories = Directory.objects.filter(name=file.name, parent=directory)
|
||||
else:
|
||||
next_directories = Directory.objects.filter(name=file.name, parent__isnull=True)
|
||||
next_directories = next_directories.order_by('id')
|
||||
next_directory = next_directories.first()
|
||||
next_directories.exclude(id=next_directory.id).delete()
|
||||
logger.error(f'Duplicate Directory {file}')
|
||||
created = False
|
||||
if created:
|
||||
next_directory.save()
|
||||
self.scan_directory(next_directory)
|
||||
else:
|
||||
if file.suffix.lower() not in settings.SUPPORTED_FILES:
|
||||
continue
|
||||
if self.OUTPUT:
|
||||
logger.info(f"Scanning File {file}")
|
||||
try:
|
||||
if directory:
|
||||
try:
|
||||
book = ComicBook.objects.get(file_name=file.name, directory=directory)
|
||||
except ComicBook.MultipleObjectsReturned:
|
||||
logger.error(f'Duplicate Comic {file}')
|
||||
books = ComicBook.objects.filter(file_name=file.name, directory=directory).order_by('id')
|
||||
book = books.first()
|
||||
extra_books = books.exclude(id=book.id)
|
||||
extra_books.delete()
|
||||
if book.version == 0:
|
||||
book.version = 1
|
||||
book.save()
|
||||
if self.VERIFY_PAGES:
|
||||
logger.info(f'Verifing pages: {book}')
|
||||
book.verify_pages()
|
||||
else:
|
||||
book = ComicBook.objects.get(file_name=file.name, directory__isnull=True)
|
||||
if book.version == 0:
|
||||
if directory:
|
||||
book.directory = directory
|
||||
book.version = 1
|
||||
book.save()
|
||||
if self.VERIFY_PAGES:
|
||||
logger.info(f'Verifing pages: {book}')
|
||||
book.verify_pages()
|
||||
except ComicBook.DoesNotExist:
|
||||
book = ComicBook.process_comic_book(file, directory)
|
||||
try:
|
||||
book.generate_thumbnail()
|
||||
except UnidentifiedImageError:
|
||||
book.generate_thumbnail(1)
|
||||
except:
|
||||
pass
|
||||
def scan_directory(self, user: Optional[User] = None, directory: Optional[Directory] = None) -> None:
|
||||
if not user:
|
||||
user_model = get_user_model()
|
||||
user = user_model.objects.first()
|
||||
for item in generate_directory(user, directory):
|
||||
item: Union[Directory, ComicBook]
|
||||
if item.type == 'Directory':
|
||||
logger.info(item)
|
||||
self.scan_directory(user, item)
|
||||
|
||||
18
comic/migrations/0047_comicstatus_updated.py
Normal file
18
comic/migrations/0047_comicstatus_updated.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 4.0.7 on 2022-09-15 09:52
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('comic', '0046_comicbook_one_comic_name_per_directory'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='comicstatus',
|
||||
name='updated',
|
||||
field=models.DateTimeField(auto_now=True),
|
||||
),
|
||||
]
|
||||
18
comic/migrations/0048_comicbook_page_count.py
Normal file
18
comic/migrations/0048_comicbook_page_count.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 4.0.7 on 2022-09-15 09:55
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('comic', '0047_comicstatus_updated'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='comicbook',
|
||||
name='page_count',
|
||||
field=models.IntegerField(default=0),
|
||||
),
|
||||
]
|
||||
22
comic/migrations/0049_populate_pages.py
Normal file
22
comic/migrations/0049_populate_pages.py
Normal file
@@ -0,0 +1,22 @@
|
||||
# Generated by Django 4.0.7 on 2022-09-15 09:59
|
||||
|
||||
from django.db import migrations
|
||||
from django.db.models import Count
|
||||
|
||||
|
||||
def forwards_func(apps, schema_editor):
|
||||
books = apps.get_model("comic", "ComicBook")
|
||||
for book in books.objects.all().annotate(total_pages=Count('comicpage')):
|
||||
book.page_count = book.total_pages
|
||||
book.save()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('comic', '0048_comicbook_page_count'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(forwards_func),
|
||||
]
|
||||
16
comic/migrations/0050_delete_comicpage.py
Normal file
16
comic/migrations/0050_delete_comicpage.py
Normal file
@@ -0,0 +1,16 @@
|
||||
# Generated by Django 4.0.7 on 2022-09-15 15:17
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('comic', '0049_populate_pages'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.DeleteModel(
|
||||
name='ComicPage',
|
||||
),
|
||||
]
|
||||
@@ -3,9 +3,8 @@ import mimetypes
|
||||
import uuid
|
||||
import zipfile
|
||||
from functools import reduce
|
||||
from itertools import zip_longest
|
||||
from pathlib import Path
|
||||
from typing import Optional, List, Union, Tuple, Final
|
||||
from typing import Optional, List, Union, Tuple, Final, IO
|
||||
|
||||
# noinspection PyPackageRequirements
|
||||
import fitz
|
||||
@@ -17,7 +16,6 @@ from django.contrib.auth.models import User
|
||||
from django.core.files.uploadedfile import InMemoryUploadedFile
|
||||
from django.db import models
|
||||
from django.db.models import UniqueConstraint
|
||||
from django.db.transaction import atomic
|
||||
from django_boost.models.fields import AutoOneToOneField
|
||||
from imagekit.models import ProcessedImageField
|
||||
from imagekit.processors import ResizeToFill
|
||||
@@ -93,7 +91,7 @@ class Directory(models.Model):
|
||||
self.parent.get_path_items(path_items)
|
||||
return path_items
|
||||
|
||||
def get_path_objects(self, path_items=None) -> List["Directory"]:
|
||||
def get_path_objects(self, path_items: Optional[List] = None) -> List["Directory"]:
|
||||
if path_items is None:
|
||||
path_items = []
|
||||
path_items.append(self)
|
||||
@@ -114,6 +112,7 @@ class ComicBook(models.Model):
|
||||
options={'quality': 60},
|
||||
null=True)
|
||||
thumbnail_index = models.PositiveIntegerField(default=0)
|
||||
page_count = models.IntegerField(default=0)
|
||||
|
||||
class Meta:
|
||||
constraints = [
|
||||
@@ -131,13 +130,17 @@ class ComicBook(models.Model):
|
||||
def type(self) -> str:
|
||||
return 'ComicBook'
|
||||
|
||||
@property
|
||||
def total(self) -> int:
|
||||
return self.page_count
|
||||
|
||||
def get_pdf(self) -> Path:
|
||||
base_dir = settings.COMIC_BOOK_VOLUME
|
||||
if self.directory:
|
||||
return Path(base_dir, self.directory.get_path(), self.file_name)
|
||||
return Path(base_dir, self.file_name)
|
||||
|
||||
def get_image(self, page: int) -> Union[Tuple[io.BytesIO, Image_type], Tuple[bool, bool]]:
|
||||
def get_image(self, page: int) -> Union[Tuple[IO[bytes], str], Tuple[bool, bool]]:
|
||||
base_dir = settings.COMIC_BOOK_VOLUME
|
||||
if self.directory:
|
||||
archive_path = Path(base_dir, self.directory.path, self.file_name)
|
||||
@@ -151,14 +154,8 @@ class ComicBook(models.Model):
|
||||
except zipfile.BadZipfile:
|
||||
return False, False
|
||||
|
||||
page_obj = ComicPage.objects.get(Comic=self, index=page)
|
||||
try:
|
||||
out = (archive.open(page_obj.page_file_name), page_obj.content_type)
|
||||
except rarfile.NoRarEntry:
|
||||
self.verify_pages()
|
||||
page_obj = ComicPage.objects.get(Comic=self, index=page)
|
||||
out = (archive.open(page_obj.page_file_name), page_obj.content_type)
|
||||
return out
|
||||
file_name, file_mime = self.get_archive_files(archive)[page]
|
||||
return archive.open(file_name), file_mime
|
||||
|
||||
def generate_thumbnail_pdf(self, page_index: int = 0) -> Tuple[io.BytesIO, Image_type, str]:
|
||||
img, pil_data = self._get_pdf_image(page_index if page_index else 0)
|
||||
@@ -171,7 +168,7 @@ class ComicBook(models.Model):
|
||||
img, content_type = self.get_image(page_index)
|
||||
pil_data = Image.open(img)
|
||||
else:
|
||||
for page_number in range(ComicPage.objects.filter(Comic=self).count()):
|
||||
for page_number in range(self.page_count):
|
||||
try:
|
||||
img, content_type = self.get_image(page_number)
|
||||
pil_data = Image.open(img)
|
||||
@@ -211,10 +208,6 @@ class ComicBook(models.Model):
|
||||
img.seek(0)
|
||||
return img, pil_data
|
||||
|
||||
@property
|
||||
def page_count(self) -> int:
|
||||
return ComicPage.objects.filter(Comic=self).count()
|
||||
|
||||
@staticmethod
|
||||
def process_comic_book(comic_file_path: Path, directory: "Directory" = False) -> Union["ComicBook", Path]:
|
||||
try:
|
||||
@@ -234,14 +227,10 @@ class ComicBook(models.Model):
|
||||
return comic_file_path
|
||||
|
||||
if archive_type == 'archive':
|
||||
book.verify_pages()
|
||||
book.page_count = len(book.get_archive_files(archive))
|
||||
elif archive_type == 'pdf':
|
||||
with atomic():
|
||||
for page_index in range(archive.page_count):
|
||||
page = ComicPage(
|
||||
Comic=book, index=page_index, page_file_name=page_index + 1, content_type='application/pdf'
|
||||
)
|
||||
page.save()
|
||||
book.page_count = archive.page_count
|
||||
|
||||
return book
|
||||
|
||||
@property
|
||||
@@ -268,56 +257,20 @@ class ComicBook(models.Model):
|
||||
pass
|
||||
raise NotCompatibleArchive
|
||||
|
||||
def get_page_count(self) -> int:
|
||||
archive, archive_type = self.get_archive()
|
||||
if archive_type == 'archive':
|
||||
return len(self.get_archive_files(archive))
|
||||
elif archive_type == 'pdf':
|
||||
return archive.page_count
|
||||
|
||||
@staticmethod
|
||||
def get_archive_files(archive) -> List[Tuple[str, str]]:
|
||||
def get_archive_files(archive: Union[zipfile.ZipFile, rarfile.RarFile]) -> List[Tuple[str, str]]:
|
||||
return [
|
||||
(x, mimetypes.guess_type(x)[0]) for x in sorted(archive.namelist())
|
||||
if not x.endswith('/') and mimetypes.guess_type(x)[0]
|
||||
if not x.endswith('/') and mimetypes.guess_type(x)[0] and not x.endswith('xml')
|
||||
]
|
||||
|
||||
def verify_pages(self, pages: Optional["ComicPage"] = None) -> None:
|
||||
if not pages:
|
||||
pages = ComicPage.objects.filter(Comic=self)
|
||||
|
||||
archive, archive_type = self.get_archive()
|
||||
if archive_type == 'pdf':
|
||||
return
|
||||
archive_files = self.get_archive_files(archive)
|
||||
index = 0
|
||||
for a_file, db_file in zip_longest(archive_files, pages):
|
||||
if not a_file:
|
||||
db_file.delete()
|
||||
continue
|
||||
if not db_file:
|
||||
ComicPage(
|
||||
Comic=self,
|
||||
page_file_name=a_file[0],
|
||||
index=index,
|
||||
content_type=a_file[1]
|
||||
).save()
|
||||
index += 1
|
||||
continue
|
||||
changed = False
|
||||
if a_file[0] != db_file.page_file_name:
|
||||
db_file.page_file_name = a_file[0]
|
||||
changed = True
|
||||
if a_file[1] != db_file.content_type:
|
||||
db_file.content_type = a_file[1]
|
||||
changed = True
|
||||
if changed:
|
||||
db_file.save()
|
||||
index += 1
|
||||
|
||||
|
||||
class ComicPage(models.Model):
|
||||
Comic = models.ForeignKey(ComicBook, on_delete=models.CASCADE)
|
||||
index = models.IntegerField()
|
||||
page_file_name = models.CharField(max_length=200, unique=False)
|
||||
content_type = models.CharField(max_length=30)
|
||||
|
||||
class Meta:
|
||||
ordering = ['index']
|
||||
|
||||
|
||||
class ComicStatus(models.Model):
|
||||
user = models.ForeignKey(User, unique=False, null=False, on_delete=models.CASCADE)
|
||||
@@ -326,6 +279,7 @@ class ComicStatus(models.Model):
|
||||
last_read_page = models.IntegerField(default=0)
|
||||
unread = models.BooleanField(default=True)
|
||||
finished = models.BooleanField(default=False)
|
||||
updated = models.DateTimeField(auto_now=True)
|
||||
|
||||
class Meta:
|
||||
constraints = [
|
||||
@@ -342,9 +296,6 @@ class ComicStatus(models.Model):
|
||||
)
|
||||
|
||||
|
||||
# TODO: add support to reference items last being read
|
||||
|
||||
|
||||
class UserMisc(models.Model):
|
||||
|
||||
user = AutoOneToOneField(User, on_delete=models.CASCADE, primary_key=True)
|
||||
|
||||
@@ -1,17 +1,19 @@
|
||||
import mimetypes
|
||||
import zipfile
|
||||
from itertools import chain
|
||||
from pathlib import Path
|
||||
from typing import NamedTuple, List
|
||||
from typing import NamedTuple, List, Optional, Union
|
||||
|
||||
import rarfile
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import User
|
||||
from django.db.models import Count, Q, F, Case, When, PositiveSmallIntegerField
|
||||
from django.db.models import Count, Q, F, Case, When, PositiveSmallIntegerField, QuerySet
|
||||
|
||||
from comic import models
|
||||
from comic.errors import NotCompatibleArchive
|
||||
|
||||
|
||||
def generate_directory(user: User, directory=None):
|
||||
def generate_directory(user: User, directory: Optional[models.Directory] = None) -> List[QuerySet]:
|
||||
dir_path = Path(settings.COMIC_BOOK_VOLUME, directory.path) if directory else settings.COMIC_BOOK_VOLUME
|
||||
files = []
|
||||
|
||||
@@ -37,7 +39,6 @@ def generate_directory(user: User, directory=None):
|
||||
models.ComicStatus.objects.bulk_create(new_status)
|
||||
|
||||
file_db_query = file_db_query.annotate(
|
||||
total=Count('comicpage', distinct=True),
|
||||
progress=F('comicstatus__last_read_page') + 1,
|
||||
finished=F('comicstatus__finished'),
|
||||
unread=F('comicstatus__unread'),
|
||||
@@ -60,7 +61,7 @@ def generate_directory(user: User, directory=None):
|
||||
return files
|
||||
|
||||
|
||||
def clean_directories(directories, dir_path, directory=None):
|
||||
def clean_directories(directories: QuerySet, dir_path: Path, directory: Optional[models.Directory] = None) -> None:
|
||||
dir_db_set = set(Path(settings.COMIC_BOOK_VOLUME, x.path) for x in directories)
|
||||
dir_list = set(x for x in sorted(dir_path.glob('*')) if x.is_dir())
|
||||
# Create new directories db instances
|
||||
@@ -72,7 +73,7 @@ def clean_directories(directories, dir_path, directory=None):
|
||||
models.Directory.objects.get(name=stale_directory.name, parent=directory).delete()
|
||||
|
||||
|
||||
def clean_files(files, user, dir_path, directory=None):
|
||||
def clean_files(files: QuerySet, user: User, dir_path: Path, directory: Optional[models.Directory] = None) -> None:
|
||||
file_list = set(x for x in sorted(dir_path.glob('*')) if x.is_file())
|
||||
files_db_set = set(Path(dir_path, x.file_name) for x in files)
|
||||
|
||||
@@ -80,34 +81,23 @@ def clean_files(files, user, dir_path, directory=None):
|
||||
books_to_add = []
|
||||
for new_comic in file_list - files_db_set:
|
||||
if new_comic.suffix.lower() in settings.SUPPORTED_FILES:
|
||||
books_to_add.append(
|
||||
models.ComicBook(file_name=new_comic.name, directory=directory)
|
||||
)
|
||||
new_book = models.ComicBook(file_name=new_comic.name, directory=directory)
|
||||
archive, archive_type = new_book.get_archive()
|
||||
try:
|
||||
if archive_type == 'archive':
|
||||
new_book.page_count = len(get_archive_files(archive))
|
||||
elif archive_type == 'pdf':
|
||||
new_book.page_count = archive.page_count
|
||||
except NotCompatibleArchive:
|
||||
pass
|
||||
books_to_add.append(new_book)
|
||||
models.ComicBook.objects.bulk_create(books_to_add)
|
||||
|
||||
pages_to_add = []
|
||||
status_to_add = []
|
||||
for book in books_to_add:
|
||||
status_to_add.append(models.ComicStatus(user=user, comic=book))
|
||||
try:
|
||||
archive, archive_type = book.get_archive()
|
||||
if archive_type == 'archive':
|
||||
pages_to_add.extend([
|
||||
models.ComicPage(
|
||||
Comic=book, index=idx, page_file_name=page.file_name, content_type=page.mime_type
|
||||
) for idx, page in enumerate(get_archive_files(archive))
|
||||
])
|
||||
elif archive_type == 'pdf':
|
||||
pages_to_add.extend([
|
||||
models.ComicPage(
|
||||
Comic=book, index=idx, page_file_name=idx + 1, content_type='application/pdf'
|
||||
) for idx in range(archive.page_count)
|
||||
])
|
||||
except NotCompatibleArchive:
|
||||
pass
|
||||
|
||||
models.ComicStatus.objects.bulk_create(status_to_add)
|
||||
models.ComicPage.objects.bulk_create(pages_to_add)
|
||||
|
||||
# Remove stale comic instances
|
||||
for stale_comic in files_db_set - file_list:
|
||||
@@ -119,7 +109,7 @@ class ArchiveFile(NamedTuple):
|
||||
mime_type: str
|
||||
|
||||
|
||||
def get_archive_files(archive) -> List[ArchiveFile]:
|
||||
def get_archive_files(archive: Union[zipfile.ZipFile, rarfile.RarFile]) -> List[ArchiveFile]:
|
||||
return [
|
||||
ArchiveFile(x, mimetypes.guess_type(x)[0]) for x in sorted(archive.namelist())
|
||||
if not x.endswith('/') and mimetypes.guess_type(x)[0]
|
||||
|
||||
@@ -6,7 +6,7 @@ from django.conf import settings
|
||||
from django.contrib.auth.models import User
|
||||
from django.contrib.auth.password_validation import validate_password
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.db.models import Count, Case, When, F, PositiveSmallIntegerField, FileField, QuerySet
|
||||
from django.db.models import Case, When, F, PositiveSmallIntegerField, FileField, QuerySet
|
||||
from django.http import FileResponse
|
||||
from drf_yasg.utils import swagger_auto_schema
|
||||
from rest_framework import viewsets, serializers, mixins, permissions, status, renderers
|
||||
@@ -180,12 +180,6 @@ class GenerateThumbnailViewSet(viewsets.ViewSet):
|
||||
)
|
||||
|
||||
|
||||
class PageSerializer(serializers.Serializer):
|
||||
index = serializers.IntegerField()
|
||||
page_file_name = serializers.CharField()
|
||||
content_type = serializers.CharField()
|
||||
|
||||
|
||||
class DirectionSerializer(serializers.Serializer):
|
||||
route = serializers.ChoiceField(choices=['read', 'browse'])
|
||||
selector = serializers.UUIDField(required=False)
|
||||
@@ -197,7 +191,7 @@ class ReadSerializer(serializers.Serializer):
|
||||
last_read_page = serializers.IntegerField()
|
||||
prev_comic = DirectionSerializer()
|
||||
next_comic = DirectionSerializer()
|
||||
pages = PageSerializer(many=True)
|
||||
pages = serializers.IntegerField()
|
||||
|
||||
|
||||
class TypeSerializer(serializers.Serializer):
|
||||
@@ -217,10 +211,13 @@ class ReadViewSet(viewsets.GenericViewSet):
|
||||
def retrieve(self, request: Request, selector: UUID) -> Response:
|
||||
comic = get_object_or_404(models.ComicBook, selector=selector)
|
||||
_, _ = models.UserMisc.objects.get_or_create(user=request.user)
|
||||
pages = models.ComicPage.objects.filter(Comic=comic)
|
||||
comic_status, _ = models.ComicStatus.objects.get_or_create(comic=comic, user=request.user)
|
||||
comic_list = list(models.ComicBook.objects.filter(directory=comic.directory).order_by('file_name'))
|
||||
comic_index = comic_list.index(comic)
|
||||
current_page_count = comic.get_page_count()
|
||||
if comic.page_count != current_page_count:
|
||||
comic.page_count = current_page_count
|
||||
comic.save()
|
||||
try:
|
||||
prev_comic = {'route': 'browse', 'selector': comic.directory.selector} if comic_index == 0 else \
|
||||
{'route': 'read', 'selector': comic_list[comic_index - 1].selector}
|
||||
@@ -238,7 +235,7 @@ class ReadViewSet(viewsets.GenericViewSet):
|
||||
"last_read_page": comic_status.last_read_page,
|
||||
"prev_comic": prev_comic,
|
||||
"next_comic": next_comic,
|
||||
"pages": pages,
|
||||
"pages": comic.page_count,
|
||||
}
|
||||
serializer = self.serializer_class(data)
|
||||
return Response(serializer.data)
|
||||
@@ -269,7 +266,7 @@ class ReadViewSet(viewsets.GenericViewSet):
|
||||
serializer = self.get_serializer(data=request.data)
|
||||
|
||||
if serializer.is_valid():
|
||||
comic_status, _ = models.ComicStatus.objects.annotate(page_count=Count('comic__comicpage')) \
|
||||
comic_status, _ = models.ComicStatus.objects.annotate(page_count=F('comic__page_count')) \
|
||||
.get_or_create(comic_id=selector, user=request.user)
|
||||
comic_status.last_read_page = serializer.data['page']
|
||||
comic_status.unread = False
|
||||
@@ -296,14 +293,14 @@ class PassthroughRenderer(renderers.BaseRenderer): # pylint: disable=too-few-pu
|
||||
|
||||
|
||||
class ImageViewSet(viewsets.ViewSet):
|
||||
queryset = models.ComicPage.objects.all()
|
||||
queryset = models.ComicBook.objects.all()
|
||||
lookup_field = 'page'
|
||||
renderer_classes = [PassthroughRenderer]
|
||||
|
||||
@swagger_auto_schema(responses={status.HTTP_200_OK: "A Binary Image response"})
|
||||
def retrieve(self, _request: Request, parent_lookup_selector: UUID, page: int) -> FileResponse:
|
||||
book = models.ComicBook.objects.get(selector=parent_lookup_selector)
|
||||
img, content = book.get_image(int(page))
|
||||
img, content = book.get_image(int(page) - 1)
|
||||
self.renderer_classes[0].media_type = content
|
||||
return FileResponse(img, content_type=content)
|
||||
|
||||
@@ -315,14 +312,17 @@ class StandardResultsSetPagination(PageNumberPagination):
|
||||
|
||||
|
||||
class RecentComicsSerializer(serializers.ModelSerializer):
|
||||
total_pages = serializers.IntegerField()
|
||||
unread = serializers.BooleanField()
|
||||
finished = serializers.BooleanField()
|
||||
last_read_page = serializers.IntegerField()
|
||||
|
||||
class Meta:
|
||||
model = models.ComicBook
|
||||
fields = ['file_name', 'date_added', 'selector', 'total_pages', 'unread', 'finished', 'last_read_page']
|
||||
fields = ['file_name', 'date_added', 'selector', 'page_count', 'unread', 'finished', 'last_read_page']
|
||||
|
||||
|
||||
class SearchTextQuerySerializer(serializers.Serializer):
|
||||
search_text = serializers.CharField(required=False)
|
||||
|
||||
|
||||
class RecentComicsView(mixins.ListModelMixin, viewsets.GenericViewSet):
|
||||
@@ -339,7 +339,6 @@ class RecentComicsView(mixins.ListModelMixin, viewsets.GenericViewSet):
|
||||
query = models.ComicBook.objects.all()
|
||||
|
||||
query = query.annotate(
|
||||
total_pages=Count('comicpage'),
|
||||
unread=Case(When(comicstatus__user=user, then='comicstatus__unread')),
|
||||
finished=Case(When(comicstatus__user=user, then='comicstatus__finished')),
|
||||
last_read_page=Case(When(comicstatus__user=user, then='comicstatus__last_read_page')) + 1,
|
||||
@@ -353,6 +352,53 @@ class RecentComicsView(mixins.ListModelMixin, viewsets.GenericViewSet):
|
||||
query = query.order_by('-date_added')
|
||||
return query
|
||||
|
||||
@swagger_auto_schema(query_serializer=SearchTextQuerySerializer())
|
||||
def list(self, request: Request, *args, **kwargs) -> Response:
|
||||
return super().list(request, *args, **kwargs)
|
||||
|
||||
|
||||
class HistorySerializer(serializers.ModelSerializer):
|
||||
last_read_time = serializers.DateTimeField()
|
||||
finished = serializers.BooleanField()
|
||||
last_read_page = serializers.IntegerField()
|
||||
|
||||
class Meta:
|
||||
model = models.ComicBook
|
||||
fields = ['file_name', 'selector', 'page_count', 'last_read_time', 'finished', 'last_read_page']
|
||||
|
||||
|
||||
class HistoryViewSet(mixins.ListModelMixin, viewsets.GenericViewSet):
|
||||
queryset = models.ComicBook.objects.all()
|
||||
serializer_class = HistorySerializer
|
||||
pagination_class = StandardResultsSetPagination
|
||||
permission_classes = [permissions.IsAuthenticated]
|
||||
|
||||
def get_queryset(self) -> QuerySet[models.ComicBook]:
|
||||
user = self.request.user
|
||||
if "search_text" in self.request.query_params:
|
||||
query = models.ComicBook.objects.filter(file_name__icontains=self.request.query_params["search_text"])
|
||||
else:
|
||||
query = models.ComicBook.objects.all()
|
||||
|
||||
query = query.annotate(
|
||||
last_read_page=Case(When(comicstatus__user=user, then='comicstatus__last_read_page')) + 1,
|
||||
finished=Case(When(comicstatus__user=user, then='comicstatus__finished')),
|
||||
unread=Case(When(comicstatus__user=user, then='comicstatus__unread')),
|
||||
last_read_time=Case(When(comicstatus__user=user, then='comicstatus__updated')),
|
||||
classification=Case(
|
||||
When(directory__isnull=True, then=models.Directory.Classification.C_18),
|
||||
default=F('directory__classification'),
|
||||
output_field=PositiveSmallIntegerField(choices=models.Directory.Classification.choices)
|
||||
)
|
||||
)
|
||||
query = query.filter(comicstatus__unread=False)
|
||||
query = query.order_by('-comicstatus__updated')
|
||||
return query
|
||||
|
||||
@swagger_auto_schema(query_serializer=SearchTextQuerySerializer())
|
||||
def list(self, request: Request, *args, **kwargs) -> Response:
|
||||
return super().list(request, *args, **kwargs)
|
||||
|
||||
|
||||
class ActionSerializer(serializers.Serializer):
|
||||
selectors = serializers.ListSerializer(child=serializers.UUIDField())
|
||||
@@ -369,7 +415,7 @@ class ActionViewSet(viewsets.GenericViewSet):
|
||||
if serializer.is_valid():
|
||||
comics = self.get_comics(serializer.data['selectors'])
|
||||
comic_status = models.ComicStatus.objects.filter(comic__selector__in=comics, user=request.user)
|
||||
comic_status = comic_status.annotate(total_pages=Count('comic__comicpage'))
|
||||
comic_status = comic_status.annotate(total_pages=F('comic__page_count'))
|
||||
status_to_update = []
|
||||
for c_status in comic_status:
|
||||
c_status.last_read_page = c_status.total_pages - 1
|
||||
@@ -378,8 +424,7 @@ class ActionViewSet(viewsets.GenericViewSet):
|
||||
status_to_update.append(c_status)
|
||||
comics.remove(str(c_status.comic_id))
|
||||
for new_status in comics:
|
||||
comic = models.ComicBook.objects.annotate(
|
||||
total_pages=Count('comicpage')).get(selector=new_status)
|
||||
comic = models.ComicBook.objects.get(selector=new_status)
|
||||
obj, _ = models.ComicStatus.objects.get_or_create(comic=comic, user=request.user)
|
||||
obj.unread = False
|
||||
obj.finished = True
|
||||
|
||||
Reference in New Issue
Block a user