diff --git a/vrobbler/apps/books/models.py b/vrobbler/apps/books/models.py index 28d9175..9322b93 100644 --- a/vrobbler/apps/books/models.py +++ b/vrobbler/apps/books/models.py @@ -96,30 +96,42 @@ class Book(LongPlayScrobblableMixin): def get_absolute_url(self): return reverse("books:book_detail", kwargs={"slug": self.uuid}) - def fix_metadata(self, force_update=False): + def fix_metadata(self, data: dict = {}, force_update=False): if not self.openlibrary_id or force_update: author_name = "" if self.author: author_name = self.author.name - book_dict = lookup_book_from_openlibrary(self.title, author_name) - if not book_dict: + + if not data: + data = lookup_book_from_openlibrary( + str(self.title), author_name + ) + + if not data: logger.warn(f"Book not found in OL {self.title}") return - cover_url = book_dict.pop("cover_url", "") - ol_author_id = book_dict.pop("ol_author_id", "") - ol_author_name = book_dict.pop("ol_author_name", "") - if book_dict.get("pages") == None: - book_dict.pop("pages") + # We can discard the author name from OL for now, we'll lookup details below + data.pop("ol_author_name", "") + self.fix_authors_metadata(data.pop("ol_author_id", "")) - ol_title = book_dict.get("title", "") + ol_title = data.get("title", "") - if ol_title.lower() != self.title.lower(): + # Kick out a little warning if we're about to change KoReader's title + if ol_title.lower() != str(self.title).lower(): logger.warn( f"OL and KoReader disagree on this book title {self.title} != {ol_title}" ) - Book.objects.filter(pk=self.id).update(**book_dict) + # If we don't know pages, don't overwrite existing with None + if data.get("pages") == None: + data.pop("pages") + + # Pop this, so we can look it up later + cover_url = data.pop("cover_url", "") + + # Fun trick for updating all fields at once + Book.objects.filter(pk=self.id).update(**data) self.refresh_from_db() if cover_url: @@ -129,12 +141,31 @@ class Book(LongPlayScrobblableMixin): self.cover.save(fname, ContentFile(r.content), save=True) if self.pages: - self.run_time_seconds = self.pages * int( + self.run_time_seconds = int(self.pages) * int( self.AVG_PAGE_READING_SECONDS ) self.save() + def fix_authors_metadata(self, openlibrary_author_id): + author = Author.objects.filter( + openlibrary_id=openlibrary_author_id + ).first() + if not author: + data = lookup_author_from_openlibrary(openlibrary_author_id) + author_image_url = data.pop("author_headshot_url", None) + + author = Author.objects.create(**data) + + if author_image_url: + r = requests.get(author_image_url) + if r.status_code == 200: + fname = f"{author.name}_{author.uuid}.jpg" + author.headshot.save( + fname, ContentFile(r.content), save=True + ) + self.authors.add(author) + @property def author(self): return self.authors.first() @@ -161,10 +192,14 @@ class Book(LongPlayScrobblableMixin): return progress @classmethod - def find_or_create(cls, lookup_id: str, author: str = "") -> "Game": - from books.utils import update_or_create_book + def find_or_create(cls, lookup_id: str, author: str = "") -> "Book": + data = lookup_book_from_openlibrary(lookup_id, author) - return update_or_create_book(lookup_id, author) + book, book_created = cls.objects.get_or_create(isbn=data["isbn"]) + if book_created: + book.fix_metadata(data=data) + + return book class Page(TimeStampedModel): diff --git a/vrobbler/apps/books/openlibrary.py b/vrobbler/apps/books/openlibrary.py index 5a2ddef..371278e 100644 --- a/vrobbler/apps/books/openlibrary.py +++ b/vrobbler/apps/books/openlibrary.py @@ -1,5 +1,6 @@ import json import logging +from typing import Optional import urllib import requests @@ -74,7 +75,9 @@ def lookup_author_from_openlibrary(olid: str) -> dict: } -def lookup_book_from_openlibrary(title: str, author: str = None) -> dict: +def lookup_book_from_openlibrary( + title: str, author: Optional[str] = None +) -> dict: title_quoted = urllib.parse.quote(title) author_quoted = "" if author: diff --git a/vrobbler/apps/books/utils.py b/vrobbler/apps/books/utils.py deleted file mode 100644 index 684cf5e..0000000 --- a/vrobbler/apps/books/utils.py +++ /dev/null @@ -1,52 +0,0 @@ -from django.core.files.base import ContentFile -import requests -from typing import Optional -from books.openlibrary import ( - lookup_author_from_openlibrary, - lookup_book_from_openlibrary, -) -from books.models import Author, Book - - -def update_or_create_book( - title: str, author: Optional[str] = None, force_update=False -) -> Book: - book_dict = lookup_book_from_openlibrary(title, author) - - book, book_created = Book.objects.get_or_create( - isbn=book_dict.get("isbn"), - ) - - if book_created or force_update: - cover_url = book_dict.pop("cover_url") - ol_author_id = book_dict.pop("ol_author_id") - - Book.objects.filter(pk=book.id).update(**book_dict) - book.refresh_from_db() - - # Process authors - author_dict = lookup_author_from_openlibrary(ol_author_id) - author = Author.objects.filter(openlibrary_id=ol_author_id).first() - if not author: - author_image_url = author_dict.pop("author_headshot_url", None) - - author = Author.objects.create(**author_dict) - - if author_image_url: - r = requests.get(author_image_url) - if r.status_code == 200: - fname = f"{author.name}_{author.uuid}.jpg" - author.headshot.save( - fname, ContentFile(r.content), save=True - ) - book.authors.add(author) - - # Process cover URL - r = requests.get(cover_url) - if r.status_code == 200: - fname = f"{book.title}_{book.uuid}.jpg" - book.cover.save(fname, ContentFile(r.content), save=True) - - book.fix_metadata() - - return book