diff --git a/cbreader/urls.py b/cbreader/urls.py index 011bf4c..630a8d8 100644 --- a/cbreader/urls.py +++ b/cbreader/urls.py @@ -46,6 +46,7 @@ router.register(r'generate_thumbnail', rest.GenerateThumbnailViewSet, basename=' router.register(r'read', rest.ReadViewSet, basename='read')\ .register(r'image', rest.ImageViewSet, basename='image', parents_query_lookups=['selector']) router.register(r'recent', rest.RecentComicsView, basename="recent") +router.register(r'history', rest.HistoryViewSet, basename='history') router.register(r'action', rest.ActionViewSet, basename='action') router.register(r'account', rest.AccountViewSet, basename='account') router.register(r'directory', rest.DirectoryViewSet, basename='directory') diff --git a/comic/admin.py b/comic/admin.py index a8d00a8..8c88ea4 100644 --- a/comic/admin.py +++ b/comic/admin.py @@ -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 = ( diff --git a/comic/management/commands/scan_comics.py b/comic/management/commands/scan_comics.py index 557cd2a..16f7bb9 100644 --- a/comic/management/commands/scan_comics.py +++ b/comic/management/commands/scan_comics.py @@ -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) diff --git a/comic/migrations/0047_comicstatus_updated.py b/comic/migrations/0047_comicstatus_updated.py new file mode 100644 index 0000000..6cf8dd3 --- /dev/null +++ b/comic/migrations/0047_comicstatus_updated.py @@ -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), + ), + ] diff --git a/comic/migrations/0048_comicbook_page_count.py b/comic/migrations/0048_comicbook_page_count.py new file mode 100644 index 0000000..095be2c --- /dev/null +++ b/comic/migrations/0048_comicbook_page_count.py @@ -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), + ), + ] diff --git a/comic/migrations/0049_populate_pages.py b/comic/migrations/0049_populate_pages.py new file mode 100644 index 0000000..abb0930 --- /dev/null +++ b/comic/migrations/0049_populate_pages.py @@ -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), + ] diff --git a/comic/migrations/0050_delete_comicpage.py b/comic/migrations/0050_delete_comicpage.py new file mode 100644 index 0000000..59fc415 --- /dev/null +++ b/comic/migrations/0050_delete_comicpage.py @@ -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', + ), + ] diff --git a/comic/models.py b/comic/models.py index 51413b9..29ce4e0 100644 --- a/comic/models.py +++ b/comic/models.py @@ -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) diff --git a/comic/processing.py b/comic/processing.py index 3c77fd2..0c7e94d 100644 --- a/comic/processing.py +++ b/comic/processing.py @@ -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] diff --git a/comic/rest.py b/comic/rest.py index e6a2e15..ab1f02e 100644 --- a/comic/rest.py +++ b/comic/rest.py @@ -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 diff --git a/frontend/src/components/ComicCard.vue b/frontend/src/components/ComicCard.vue index 2deb040..a0f1d87 100644 --- a/frontend/src/components/ComicCard.vue +++ b/frontend/src/components/ComicCard.vue @@ -25,7 +25,7 @@
  • Mark Un-read
  • Mark read
  • Mark previous comics read
  • -
  • Edit comic
  • +
  • Edit comic
  • @@ -33,7 +33,7 @@ -