From b44b42be7e9826ee23433994510888f74ad6e7e8 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 11 Jan 2021 09:18:40 -0800 Subject: [PATCH 1/6] Adds rank field to edition model --- .../migrations/0035_edition_edition_rank.py | 19 +++++++++++++++++++ bookwyrm/models/book.py | 1 + 2 files changed, 20 insertions(+) create mode 100644 bookwyrm/migrations/0035_edition_edition_rank.py diff --git a/bookwyrm/migrations/0035_edition_edition_rank.py b/bookwyrm/migrations/0035_edition_edition_rank.py new file mode 100644 index 000000000..b0bd51c5a --- /dev/null +++ b/bookwyrm/migrations/0035_edition_edition_rank.py @@ -0,0 +1,19 @@ +# Generated by Django 3.0.7 on 2021-01-11 17:18 + +import bookwyrm.models.fields +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('bookwyrm', '0034_importjob_complete'), + ] + + operations = [ + migrations.AddField( + model_name='edition', + name='edition_rank', + field=bookwyrm.models.fields.IntegerField(default=0), + ), + ] diff --git a/bookwyrm/models/book.py b/bookwyrm/models/book.py index 081895109..bfb8f85e2 100644 --- a/bookwyrm/models/book.py +++ b/bookwyrm/models/book.py @@ -164,6 +164,7 @@ class Edition(Book): parent_work = fields.ForeignKey( 'Work', on_delete=models.PROTECT, null=True, related_name='editions', activitypub_field='work') + edition_rank = fields.IntegerField(default=0) activity_serializer = activitypub.Edition name_field = 'title' From 6db64e33e46139deecf7f1c18f033897570d13f5 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 11 Jan 2021 09:29:23 -0800 Subject: [PATCH 2/6] set rank on editions --- .../migrations/0035_edition_edition_rank.py | 7 ++++++ bookwyrm/models/book.py | 23 ++++++++++++++++++- 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/bookwyrm/migrations/0035_edition_edition_rank.py b/bookwyrm/migrations/0035_edition_edition_rank.py index b0bd51c5a..1a75a0974 100644 --- a/bookwyrm/migrations/0035_edition_edition_rank.py +++ b/bookwyrm/migrations/0035_edition_edition_rank.py @@ -4,6 +4,12 @@ import bookwyrm.models.fields from django.db import migrations +def set_rank(app_registry, schema_editor): + db_alias = schema_editor.connection.alias + books = app_registry.get_model('bookwyrm', 'Edition') + for book in books.objects.using(db_alias): + book.save() + class Migration(migrations.Migration): dependencies = [ @@ -16,4 +22,5 @@ class Migration(migrations.Migration): name='edition_rank', field=bookwyrm.models.fields.IntegerField(default=0), ), + migrations.RunPython(set_rank), ] diff --git a/bookwyrm/models/book.py b/bookwyrm/models/book.py index bfb8f85e2..7a9e6ee87 100644 --- a/bookwyrm/models/book.py +++ b/bookwyrm/models/book.py @@ -169,13 +169,34 @@ class Edition(Book): activity_serializer = activitypub.Edition name_field = 'title' + @property + def get_rank(self): + ''' calculate how complete the data is on this edition ''' + if self.parent_work.default_edition == self: + # default edition has the highest rank + return 20 + rank = 0 + rank += int(bool(self.cover)) * 3 + rank += int(bool(self.isbn_13)) + rank += int(bool(self.isbn_10)) + rank += int(bool(self.oclc_number)) + rank += int(bool(self.pages)) + rank += int(bool(self.physical_format)) + rank += int(bool(self.description)) + # max rank is 9 + return rank + def save(self, *args, **kwargs): - ''' calculate isbn 10/13 ''' + ''' set some fields on the edition object ''' + # calculate isbn 10/13 if self.isbn_13 and self.isbn_13[:3] == '978' and not self.isbn_10: self.isbn_10 = isbn_13_to_10(self.isbn_13) if self.isbn_10 and not self.isbn_13: self.isbn_13 = isbn_10_to_13(self.isbn_10) + # set rank + self.edition_rank = self.get_rank + return super().save(*args, **kwargs) From 83852e29eb74408a3d20670b5e958d2321104149 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 11 Jan 2021 09:49:32 -0800 Subject: [PATCH 3/6] Update edition ranks when work is saved --- bookwyrm/activitypub/book.py | 1 + bookwyrm/migrations/0035_edition_edition_rank.py | 1 + bookwyrm/models/book.py | 9 ++++++++- bookwyrm/tests/models/test_book_model.py | 16 ++++++++++++++++ 4 files changed, 26 insertions(+), 1 deletion(-) diff --git a/bookwyrm/activitypub/book.py b/bookwyrm/activitypub/book.py index 6fa80b32c..680365599 100644 --- a/bookwyrm/activitypub/book.py +++ b/bookwyrm/activitypub/book.py @@ -41,6 +41,7 @@ class Edition(Book): pages: int = None physicalFormat: str = '' publishers: List[str] = field(default_factory=lambda: []) + editionRank: int = 0 type: str = 'Edition' diff --git a/bookwyrm/migrations/0035_edition_edition_rank.py b/bookwyrm/migrations/0035_edition_edition_rank.py index 1a75a0974..6ccb2142b 100644 --- a/bookwyrm/migrations/0035_edition_edition_rank.py +++ b/bookwyrm/migrations/0035_edition_edition_rank.py @@ -8,6 +8,7 @@ def set_rank(app_registry, schema_editor): db_alias = schema_editor.connection.alias books = app_registry.get_model('bookwyrm', 'Edition') for book in books.objects.using(db_alias): + book.edition_rank = book.get_rank book.save() class Migration(migrations.Migration): diff --git a/bookwyrm/models/book.py b/bookwyrm/models/book.py index 7a9e6ee87..70d818009 100644 --- a/bookwyrm/models/book.py +++ b/bookwyrm/models/book.py @@ -122,6 +122,13 @@ class Work(OrderedCollectionPageMixin, Book): load_remote=False ) + def save(self, *args, **kwargs): + ''' set some fields on the edition object ''' + # set rank + for edition in self.editions.all(): + edition.save() + return super().save(*args, **kwargs) + def get_default_edition(self): ''' in case the default edition is not set ''' return self.default_edition or self.editions.first() @@ -172,7 +179,7 @@ class Edition(Book): @property def get_rank(self): ''' calculate how complete the data is on this edition ''' - if self.parent_work.default_edition == self: + if self.parent_work and self.parent_work.default_edition == self: # default edition has the highest rank return 20 rank = 0 diff --git a/bookwyrm/tests/models/test_book_model.py b/bookwyrm/tests/models/test_book_model.py index a52133eaf..98d6d446e 100644 --- a/bookwyrm/tests/models/test_book_model.py +++ b/bookwyrm/tests/models/test_book_model.py @@ -82,3 +82,19 @@ class Book(TestCase): self.assertEqual(book.edition_info, 'worm, Glorbish language, 2020') self.assertEqual( book.alt_text, 'Test Edition cover (worm, Glorbish language, 2020)') + + + def test_get_rank(self): + ''' sets the data quality index for the book ''' + # basic rank + self.assertEqual(self.first_edition.edition_rank, 0) + + self.first_edition.description = 'hi' + self.first_edition.save() + self.assertEqual(self.first_edition.edition_rank, 1) + + # default edition + self.work.default_edition = self.first_edition + self.work.save() + self.first_edition.refresh_from_db() + self.assertEqual(self.first_edition.edition_rank, 20) From fe67f6530739592a35dd6c17160244613bcd127f Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 11 Jan 2021 10:25:34 -0800 Subject: [PATCH 4/6] Set default edition on bookwyrm import --- bookwyrm/connectors/bookwyrm_connector.py | 6 +++++- bookwyrm/models/book.py | 6 ++++-- bookwyrm/views.py | 2 +- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/bookwyrm/connectors/bookwyrm_connector.py b/bookwyrm/connectors/bookwyrm_connector.py index 3c6f46145..1f877993d 100644 --- a/bookwyrm/connectors/bookwyrm_connector.py +++ b/bookwyrm/connectors/bookwyrm_connector.py @@ -7,7 +7,11 @@ class Connector(AbstractMinimalConnector): ''' this is basically just for search ''' def get_or_create_book(self, remote_id): - return activitypub.resolve_remote_id(models.Edition, remote_id) + edition = activitypub.resolve_remote_id(models.Edition, remote_id) + work = edition.parent_work + work.default_edition = work.get_default_edition() + work.save() + return edition def parse_search_data(self, data): return data diff --git a/bookwyrm/models/book.py b/bookwyrm/models/book.py index 70d818009..07d1d5dc8 100644 --- a/bookwyrm/models/book.py +++ b/bookwyrm/models/book.py @@ -131,12 +131,14 @@ class Work(OrderedCollectionPageMixin, Book): def get_default_edition(self): ''' in case the default edition is not set ''' - return self.default_edition or self.editions.first() + return self.default_edition or self.editions.order_by( + '-edition_rank' + ).first() def to_edition_list(self, **kwargs): ''' an ordered collection of editions ''' return self.to_ordered_collection( - self.editions.order_by('-updated_date').all(), + self.editions.order_by('-edition_rank').all(), remote_id='%s/editions' % self.remote_id, **kwargs ) diff --git a/bookwyrm/views.py b/bookwyrm/views.py index cc45bfb27..faacd2ff8 100644 --- a/bookwyrm/views.py +++ b/bookwyrm/views.py @@ -736,7 +736,7 @@ def editions_page(request, book_id): data = { 'title': 'Editions of %s' % work.title, - 'editions': work.editions.all(), + 'editions': work.editions.order_by('-edition_rank').all(), 'work': work, } return TemplateResponse(request, 'editions.html', data) From 5b7f7aa0e311b9252af16f506a9672b198f62746 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 11 Jan 2021 11:16:10 -0800 Subject: [PATCH 5/6] sort edition list by rank --- bookwyrm/models/base_model.py | 6 ++++-- bookwyrm/models/book.py | 2 +- bookwyrm/models/status.py | 2 +- bookwyrm/models/user.py | 2 +- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/bookwyrm/models/base_model.py b/bookwyrm/models/base_model.py index b212d6939..60b04d186 100644 --- a/bookwyrm/models/base_model.py +++ b/bookwyrm/models/base_model.py @@ -145,9 +145,11 @@ class ActivitypubMixin: if hasattr(self, 'serialize_reverse_fields'): # for example, editions of a work - for model_field_name, activity_field_name in \ + for model_field_name, activity_field_name, sort_field in \ self.serialize_reverse_fields: - related_field = getattr(self, model_field_name) + related_field = getattr( + self, model_field_name + ).order_by(sort_field) activity[activity_field_name] = \ unfurl_related_field(related_field) diff --git a/bookwyrm/models/book.py b/bookwyrm/models/book.py index 07d1d5dc8..49b9f0a91 100644 --- a/bookwyrm/models/book.py +++ b/bookwyrm/models/book.py @@ -144,7 +144,7 @@ class Work(OrderedCollectionPageMixin, Book): ) activity_serializer = activitypub.Work - serialize_reverse_fields = [('editions', 'editions')] + serialize_reverse_fields = [('editions', 'editions', '-edition_rank')] deserialize_reverse_fields = [('editions', 'editions')] diff --git a/bookwyrm/models/status.py b/bookwyrm/models/status.py index 2494c4582..dad659741 100644 --- a/bookwyrm/models/status.py +++ b/bookwyrm/models/status.py @@ -47,7 +47,7 @@ class Status(OrderedCollectionPageMixin, BookWyrmModel): objects = InheritanceManager() activity_serializer = activitypub.Note - serialize_reverse_fields = [('attachments', 'attachment')] + serialize_reverse_fields = [('attachments', 'attachment', 'id')] deserialize_reverse_fields = [('attachments', 'attachment')] @classmethod diff --git a/bookwyrm/models/user.py b/bookwyrm/models/user.py index 4cbe387f4..ef68f9928 100644 --- a/bookwyrm/models/user.py +++ b/bookwyrm/models/user.py @@ -200,7 +200,7 @@ class KeyPair(ActivitypubMixin, BookWyrmModel): blank=True, null=True, activitypub_field='publicKeyPem') activity_serializer = activitypub.PublicKey - serialize_reverse_fields = [('owner', 'owner')] + serialize_reverse_fields = [('owner', 'owner', 'id')] def get_remote_id(self): # self.owner is set by the OneToOneField on User From a6eb444a68f5f598b1c6b68954c293c54c91c1bd Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 11 Jan 2021 14:05:08 -0800 Subject: [PATCH 6/6] Don't error out user page --- bookwyrm/models/base_model.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/bookwyrm/models/base_model.py b/bookwyrm/models/base_model.py index 60b04d186..430097c09 100644 --- a/bookwyrm/models/base_model.py +++ b/bookwyrm/models/base_model.py @@ -52,10 +52,11 @@ def execute_after_save(sender, instance, created, *args, **kwargs): instance.save() -def unfurl_related_field(related_field): +def unfurl_related_field(related_field, sort_field=None): ''' load reverse lookups (like public key owner or Status attachment ''' if hasattr(related_field, 'all'): - return [unfurl_related_field(i) for i in related_field.all()] + return [unfurl_related_field(i) for i in related_field.order_by( + sort_field).all()] if related_field.reverse_unfurl: return related_field.field_to_activity() return related_field.remote_id @@ -147,11 +148,9 @@ class ActivitypubMixin: # for example, editions of a work for model_field_name, activity_field_name, sort_field in \ self.serialize_reverse_fields: - related_field = getattr( - self, model_field_name - ).order_by(sort_field) + related_field = getattr(self, model_field_name) activity[activity_field_name] = \ - unfurl_related_field(related_field) + unfurl_related_field(related_field, sort_field) if not activity.get('id'): activity['id'] = self.get_remote_id()