diff --git a/bookwyrm/management/commands/repair_editions.py b/bookwyrm/management/commands/repair_editions.py new file mode 100644 index 000000000..304cd5e51 --- /dev/null +++ b/bookwyrm/management/commands/repair_editions.py @@ -0,0 +1,21 @@ +""" Repair editions with missing works """ +from django.core.management.base import BaseCommand +from bookwyrm import models + + +class Command(BaseCommand): + """command-line options""" + + help = "Repairs an edition that is in a broken state" + + # pylint: disable=unused-argument + def handle(self, *args, **options): + """Find and repair broken editions""" + # Find broken editions + editions = models.Edition.objects.filter(parent_work__isnull=True) + self.stdout.write(f"Repairing {editions.count()} edition(s):") + + # Do repair + for edition in editions: + edition.repair() + self.stdout.write(".", ending="") diff --git a/bookwyrm/models/book.py b/bookwyrm/models/book.py index f4a984903..13de59a8c 100644 --- a/bookwyrm/models/book.py +++ b/bookwyrm/models/book.py @@ -381,6 +381,19 @@ class Edition(Book): return super().save(*args, **kwargs) + @transaction.atomic + def repair(self): + """If an edition is in a bad state (missing a work), let's fix that""" + # made sure it actually NEEDS reapir + if self.parent_work: + return + + new_work = Work.objects.create(title=self.title) + new_work.authors.set(self.authors.all()) + + self.parent_work = new_work + self.save(update_fields=["parent_work"], broadcast=False) + @classmethod def viewer_aware_objects(cls, viewer): """annotate a book query with metadata related to the user""" diff --git a/bookwyrm/templatetags/rating_tags.py b/bookwyrm/templatetags/rating_tags.py index cc23d3308..367463a8f 100644 --- a/bookwyrm/templatetags/rating_tags.py +++ b/bookwyrm/templatetags/rating_tags.py @@ -12,6 +12,10 @@ register = template.Library() @register.filter(name="rating") def get_rating(book, user): """get the overall rating of a book""" + # this shouldn't happen, but it CAN + if not book.parent_work: + return None + return cache.get_or_set( f"book-rating-{book.parent_work.id}", lambda u, b: models.Review.objects.filter( diff --git a/bookwyrm/tests/models/test_book_model.py b/bookwyrm/tests/models/test_book_model.py index 825f42b87..590b7b873 100644 --- a/bookwyrm/tests/models/test_book_model.py +++ b/bookwyrm/tests/models/test_book_model.py @@ -24,8 +24,7 @@ class Book(TestCase): title="Example Work", remote_id="https://example.com/book/1" ) self.first_edition = models.Edition.objects.create( - title="Example Edition", - parent_work=self.work, + title="Example Edition", parent_work=self.work ) self.second_edition = models.Edition.objects.create( title="Another Example Edition", @@ -143,3 +142,15 @@ class Book(TestCase): for article in articles ) self.assertTrue(all(book.sort_title == "test edition" for book in books)) + + def test_repair_edition(self): + """Fix editions with no works""" + edition = models.Edition.objects.create(title="test") + edition.authors.set([models.Author.objects.create(name="Author Name")]) + self.assertIsNone(edition.parent_work) + + edition.repair() + edition.refresh_from_db() + + self.assertEqual(edition.parent_work.title, "test") + self.assertEqual(edition.parent_work.authors.count(), 1) diff --git a/bookwyrm/tests/templatetags/test_rating_tags.py b/bookwyrm/tests/templatetags/test_rating_tags.py index 5abfa471a..8c07eeb8f 100644 --- a/bookwyrm/tests/templatetags/test_rating_tags.py +++ b/bookwyrm/tests/templatetags/test_rating_tags.py @@ -71,6 +71,12 @@ class RatingTags(TestCase): ) self.assertEqual(rating_tags.get_rating(self.book, self.local_user), 5) + def test_get_rating_broken_edition(self, *_): + """Don't have a server error if an edition is missing a work""" + broken_book = models.Edition.objects.create(title="Test") + broken_book.parent_work = None + self.assertIsNone(rating_tags.get_rating(broken_book, self.local_user)) + def test_get_user_rating(self, *_): """get a user's most recent rating of a book""" with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):