Merge pull request #460 from mouse-reeve/review-rate

Review rate
This commit is contained in:
Mouse Reeve 2021-03-08 11:50:42 -08:00 committed by GitHub
commit bf255bd51d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 144 additions and 16 deletions

View file

@ -6,7 +6,8 @@ from .base_activity import ActivityEncoder, Signature, naive_parse
from .base_activity import Link, Mention from .base_activity import Link, Mention
from .base_activity import ActivitySerializerError, resolve_remote_id from .base_activity import ActivitySerializerError, resolve_remote_id
from .image import Image 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 .note import Tombstone
from .ordered_collection import OrderedCollection, OrderedCollectionPage from .ordered_collection import OrderedCollection, OrderedCollectionPage
from .ordered_collection import BookList, Shelf from .ordered_collection import BookList, Shelf

View file

@ -13,7 +13,7 @@ class Tombstone(ActivityObject):
type: str = "Tombstone" 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 """ """ this should never really get serialized, just searched for """
model = apps.get_model("bookwyrm.Status") model = apps.get_model("bookwyrm.Status")
return model.find_existing_by_remote_id(self.id) return model.find_existing_by_remote_id(self.id)
@ -60,6 +60,14 @@ class Comment(Note):
type: str = "Comment" type: str = "Comment"
@dataclass(init=False)
class Quotation(Comment):
""" a quote and commentary on a book """
quote: str
type: str = "Quotation"
@dataclass(init=False) @dataclass(init=False)
class Review(Comment): class Review(Comment):
""" a full book review """ """ a full book review """
@ -70,8 +78,9 @@ class Review(Comment):
@dataclass(init=False) @dataclass(init=False)
class Quotation(Comment): class Rating(Comment):
""" a quote and commentary on a book """ """ just a star rating """
quote: str rating: int
type: str = "Quotation" content: str = None
type: str = "Rating"

View file

@ -54,8 +54,8 @@ class RegisterForm(CustomForm):
class RatingForm(CustomForm): class RatingForm(CustomForm):
class Meta: class Meta:
model = models.Review model = models.ReviewRating
fields = ["user", "book", "content", "rating", "privacy"] fields = ["user", "book", "rating", "privacy"]
class ReviewForm(CustomForm): class ReviewForm(CustomForm):

View file

@ -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),
]

View file

@ -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 = []

View file

@ -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 = []

View file

@ -9,7 +9,8 @@ from .connector import Connector
from .shelf import Shelf, ShelfBook from .shelf import Shelf, ShelfBook
from .list import List, ListItem 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 .status import Boost
from .attachment import Image from .attachment import Image
from .favorite import Favorite from .favorite import Favorite

View file

@ -278,13 +278,12 @@ class Review(Status):
def pure_name(self): def pure_name(self):
""" clarify review names for mastodon serialization """ """ clarify review names for mastodon serialization """
if self.rating: if self.rating:
# pylint: disable=bad-string-format-type return 'Review of "{}" ({:d} stars): {}'.format(
return 'Review of "%s" (%d stars): %s' % (
self.book.title, self.book.title,
self.rating, self.rating,
self.name, self.name,
) )
return 'Review of "%s": %s' % (self.book.title, self.name) return 'Review of "{}": {}'.format(self.book.title, self.name)
@property @property
def pure_content(self): def pure_content(self):
@ -295,6 +294,22 @@ class Review(Status):
pure_type = "Article" 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): class Boost(ActivityMixin, Status):
""" boost'ing a post """ """ boost'ing a post """

View file

@ -1,7 +1,7 @@
{% load bookwyrm_tags %} {% load bookwyrm_tags %}
{% load i18n %} {% load i18n %}
<div class="block"> <div class="block">
{% if status.status_type == 'Review' %} {% if status.status_type == 'Review' or status.status_type == 'Rating' %}
<div> <div>
{% if status.name %} {% if status.name %}
<h3 class="title is-5 has-subtitle" dir="auto"> <h3 class="title is-5 has-subtitle" dir="auto">

View file

@ -7,7 +7,7 @@
{% if status.status_type == 'GeneratedNote' %} {% if status.status_type == 'GeneratedNote' %}
{{ status.content | safe }} {{ status.content | safe }}
{% elif status.status_type == 'Review' and not status.name and not status.content%} {% elif status.status_type == 'Rating' %}
{% trans "rated" %} {% trans "rated" %}
{% elif status.status_type == 'Review' %} {% elif status.status_type == 'Review' %}
{% trans "reviewed" %} {% trans "reviewed" %}

View file

@ -10,7 +10,15 @@ from bookwyrm.utils import regex
user_path = r"^user/(?P<username>%s)" % regex.username user_path = r"^user/(?P<username>%s)" % regex.username
local_user_path = r"^user/(?P<username>%s)" % regex.localname local_user_path = r"^user/(?P<username>%s)" % regex.localname
status_types = ["status", "review", "comment", "quotation", "boost", "generatednote"] status_types = [
"status",
"review",
"reviewrating",
"comment",
"quotation",
"boost",
"generatednote",
]
status_path = r"%s/(%s)/(?P<status_id>\d+)" % (user_path, "|".join(status_types)) status_path = r"%s/(%s)/(?P<status_id>\d+)" % (user_path, "|".join(status_types))
book_path = r"^book/(?P<book_id>\d+)" book_path = r"^book/(?P<book_id>\d+)"

View file

@ -57,7 +57,7 @@ class CreateStatus(View):
status.mention_users.set(set(status.mention_users.all())) status.mention_users.set(set(status.mention_users.all()))
# don't apply formatting to generated notes # don't apply formatting to generated notes
if not isinstance(status, models.GeneratedNote): if not isinstance(status, models.GeneratedNote) and content:
status.content = to_markdown(content) status.content = to_markdown(content)
# do apply formatting to quotes # do apply formatting to quotes
if hasattr(status, "quote"): if hasattr(status, "quote"):
@ -85,6 +85,8 @@ class DeleteStatus(View):
def find_mentions(content): def find_mentions(content):
""" detect @mentions in raw status content """ """ detect @mentions in raw status content """
if not content:
return
for match in re.finditer(regex.strict_username, content): for match in re.finditer(regex.strict_username, content):
username = match.group().strip().split("@")[1:] username = match.group().strip().split("@")[1:]
if len(username) == 1: if len(username) == 1: