diff --git a/bookwyrm/activitypub/__init__.py b/bookwyrm/activitypub/__init__.py index 1a156cae6..35b786f71 100644 --- a/bookwyrm/activitypub/__init__.py +++ b/bookwyrm/activitypub/__init__.py @@ -6,7 +6,8 @@ from .base_activity import ActivityEncoder, Signature, naive_parse from .base_activity import Link, Mention from .base_activity import ActivitySerializerError, resolve_remote_id from .image import Image -from .note import Note, GeneratedNote, Article, Comment, Review, Quotation +from .note import Note, GeneratedNote, Article, Comment, Quotation +from .note import Review, Rating from .note import Tombstone from .ordered_collection import OrderedCollection, OrderedCollectionPage from .ordered_collection import BookList, Shelf diff --git a/bookwyrm/activitypub/note.py b/bookwyrm/activitypub/note.py index f38fea5b0..a739eafa1 100644 --- a/bookwyrm/activitypub/note.py +++ b/bookwyrm/activitypub/note.py @@ -13,7 +13,7 @@ class Tombstone(ActivityObject): type: str = "Tombstone" - def to_model(self, *args, **kwargs): + def to_model(self, *args, **kwargs): # pylint: disable=unused-argument """ this should never really get serialized, just searched for """ model = apps.get_model("bookwyrm.Status") return model.find_existing_by_remote_id(self.id) @@ -60,6 +60,14 @@ class Comment(Note): type: str = "Comment" +@dataclass(init=False) +class Quotation(Comment): + """ a quote and commentary on a book """ + + quote: str + type: str = "Quotation" + + @dataclass(init=False) class Review(Comment): """ a full book review """ @@ -70,8 +78,9 @@ class Review(Comment): @dataclass(init=False) -class Quotation(Comment): - """ a quote and commentary on a book """ +class Rating(Comment): + """ just a star rating """ - quote: str - type: str = "Quotation" + rating: int + content: str = None + type: str = "Rating" diff --git a/bookwyrm/forms.py b/bookwyrm/forms.py index 3d8839efa..380e701fa 100644 --- a/bookwyrm/forms.py +++ b/bookwyrm/forms.py @@ -54,8 +54,8 @@ class RegisterForm(CustomForm): class RatingForm(CustomForm): class Meta: - model = models.Review - fields = ["user", "book", "content", "rating", "privacy"] + model = models.ReviewRating + fields = ["user", "book", "rating", "privacy"] class ReviewForm(CustomForm): diff --git a/bookwyrm/migrations/0046_reviewrating.py b/bookwyrm/migrations/0046_reviewrating.py new file mode 100644 index 000000000..8d1490042 --- /dev/null +++ b/bookwyrm/migrations/0046_reviewrating.py @@ -0,0 +1,66 @@ +# Generated by Django 3.0.7 on 2021-02-25 18:36 + +from django.db import migrations, models +from django.db import connection +from django.db.models import Q +import django.db.models.deletion +from psycopg2.extras import execute_values + + +def convert_review_rating(app_registry, schema_editor): + """ take rating type Reviews and convert them to ReviewRatings """ + db_alias = schema_editor.connection.alias + + reviews = ( + app_registry.get_model("bookwyrm", "Review") + .objects.using(db_alias) + .filter(Q(content__isnull=True) | Q(content="")) + ) + + with connection.cursor() as cursor: + values = [(r.id,) for r in reviews] + execute_values( + cursor, + """ +INSERT INTO bookwyrm_reviewrating(review_ptr_id) +VALUES %s""", + values, + ) + + +def unconvert_review_rating(app_registry, schema_editor): + """ undo the conversion from ratings back to reviews""" + # All we need to do to revert this is drop the table, which Django will do + # on its own, as long as we have a valid reverse function. So, this is a + # no-op function so Django will do its thing + + +class Migration(migrations.Migration): + + dependencies = [ + ("bookwyrm", "0045_auto_20210210_2114"), + ] + + operations = [ + migrations.CreateModel( + name="ReviewRating", + fields=[ + ( + "review_ptr", + models.OneToOneField( + auto_created=True, + on_delete=django.db.models.deletion.CASCADE, + parent_link=True, + primary_key=True, + serialize=False, + to="bookwyrm.Review", + ), + ), + ], + options={ + "abstract": False, + }, + bases=("bookwyrm.review",), + ), + migrations.RunPython(convert_review_rating, unconvert_review_rating), + ] diff --git a/bookwyrm/migrations/0047_merge_20210228_1839.py b/bookwyrm/migrations/0047_merge_20210228_1839.py new file mode 100644 index 000000000..4be39e56f --- /dev/null +++ b/bookwyrm/migrations/0047_merge_20210228_1839.py @@ -0,0 +1,13 @@ +# Generated by Django 3.0.7 on 2021-02-28 18:39 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("bookwyrm", "0046_reviewrating"), + ("bookwyrm", "0046_sitesettings_privacy_policy"), + ] + + operations = [] diff --git a/bookwyrm/migrations/0048_merge_20210308_1754.py b/bookwyrm/migrations/0048_merge_20210308_1754.py new file mode 100644 index 000000000..47fa9e771 --- /dev/null +++ b/bookwyrm/migrations/0048_merge_20210308_1754.py @@ -0,0 +1,13 @@ +# Generated by Django 3.0.7 on 2021-03-08 17:54 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("bookwyrm", "0047_connector_isbn_search_url"), + ("bookwyrm", "0047_merge_20210228_1839"), + ] + + operations = [] diff --git a/bookwyrm/models/__init__.py b/bookwyrm/models/__init__.py index bef9debea..67ee16d3d 100644 --- a/bookwyrm/models/__init__.py +++ b/bookwyrm/models/__init__.py @@ -9,7 +9,8 @@ from .connector import Connector from .shelf import Shelf, ShelfBook from .list import List, ListItem -from .status import Status, GeneratedNote, Review, Comment, Quotation +from .status import Status, GeneratedNote, Comment, Quotation +from .status import Review, ReviewRating from .status import Boost from .attachment import Image from .favorite import Favorite diff --git a/bookwyrm/models/status.py b/bookwyrm/models/status.py index c18afd1bf..80f2b593a 100644 --- a/bookwyrm/models/status.py +++ b/bookwyrm/models/status.py @@ -278,13 +278,12 @@ class Review(Status): def pure_name(self): """ clarify review names for mastodon serialization """ if self.rating: - # pylint: disable=bad-string-format-type - return 'Review of "%s" (%d stars): %s' % ( + return 'Review of "{}" ({:d} stars): {}'.format( self.book.title, self.rating, self.name, ) - return 'Review of "%s": %s' % (self.book.title, self.name) + return 'Review of "{}": {}'.format(self.book.title, self.name) @property def pure_content(self): @@ -295,6 +294,22 @@ class Review(Status): pure_type = "Article" +class ReviewRating(Review): + """ a subtype of review that only contains a rating """ + + def save(self, *args, **kwargs): + if not self.rating: + raise ValueError("ReviewRating object must include a numerical rating") + return super().save(*args, **kwargs) + + @property + def pure_content(self): + return 'Rated "{}": {:d} stars'.format(self.book.title, self.rating) + + activity_serializer = activitypub.Rating + pure_type = "Note" + + class Boost(ActivityMixin, Status): """ boost'ing a post """ diff --git a/bookwyrm/templates/snippets/status/status_content.html b/bookwyrm/templates/snippets/status/status_content.html index 5c59a716b..616e1e638 100644 --- a/bookwyrm/templates/snippets/status/status_content.html +++ b/bookwyrm/templates/snippets/status/status_content.html @@ -1,7 +1,7 @@ {% load bookwyrm_tags %} {% load i18n %}