diff --git a/fedireads/forms.py b/fedireads/forms.py index eea57c931..ffbf6cd18 100644 --- a/fedireads/forms.py +++ b/fedireads/forms.py @@ -26,14 +26,19 @@ class RegisterForm(ModelForm): } +class RatingForm(ModelForm): + class Meta: + model = models.Review + fields = ['rating'] + + class ReviewForm(ModelForm): class Meta: model = models.Review - fields = ['name', 'rating', 'content'] + fields = ['name', 'content'] help_texts = {f: None for f in fields} labels = { 'name': 'Title', - 'rating': 'Rating (out of 5)', 'content': 'Review', } diff --git a/fedireads/migrations/0029_auto_20200403_1835.py b/fedireads/migrations/0029_auto_20200403_1835.py new file mode 100644 index 000000000..a2e5427c8 --- /dev/null +++ b/fedireads/migrations/0029_auto_20200403_1835.py @@ -0,0 +1,18 @@ +# Generated by Django 3.0.3 on 2020-04-03 18:35 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('fedireads', '0028_auto_20200401_1824'), + ] + + operations = [ + migrations.AlterField( + model_name='review', + name='name', + field=models.CharField(max_length=255, null=True), + ), + ] diff --git a/fedireads/models/status.py b/fedireads/models/status.py index 91ec0ab5f..bec5a211e 100644 --- a/fedireads/models/status.py +++ b/fedireads/models/status.py @@ -58,7 +58,7 @@ class Comment(Status): class Review(Status): ''' a book review ''' - name = models.CharField(max_length=255) + name = models.CharField(max_length=255, null=True) book = models.ForeignKey('Edition', on_delete=models.PROTECT) rating = models.IntegerField( default=None, diff --git a/fedireads/outgoing.py b/fedireads/outgoing.py index dd8ea1b38..641512d29 100644 --- a/fedireads/outgoing.py +++ b/fedireads/outgoing.py @@ -10,7 +10,7 @@ from fedireads import activitypub from fedireads import models from fedireads.broadcast import get_recipients, broadcast from fedireads.status import create_review, create_status, create_comment -from fedireads.status import create_tag, create_notification +from fedireads.status import create_tag, create_notification, create_rating from fedireads.remote_user import get_or_create_remote_user @@ -188,6 +188,12 @@ def handle_import_books(user, items): broadcast(user, create_activity, recipients) +def handle_rate(user, book, rating): + ''' a review that's just a rating ''' + review = create_rating(user, book, rating) + # TODO: serialize and broadcast + + def handle_review(user, book, name, content, rating): ''' post a review ''' # validated and saves the review in the database so it has an id diff --git a/fedireads/static/format.css b/fedireads/static/format.css index 0c1395c20..5696ab459 100644 --- a/fedireads/static/format.css +++ b/fedireads/static/format.css @@ -314,6 +314,62 @@ button .icon { } +/* star ratings */ +.stars { + letter-spacing: -0.15em; + display: inline-block; +} +.rate-stars .icon { + cursor: pointer; + color: goldenrod; +} +.rate-stars label.icon { + color: black; +} +.rate-stars form { + display: inline; + width: min-content; +} +.rate-stars button.icon { + background: none; + border: none; + padding: 0; + margin: 0; +} +.rate-stars:hover .icon:before { + content: '\e9d9'; +} +.rate-stars form:hover ~ form .icon:before{ + content: '\e9d7'; +} + +.review-form .rate-stars:hover .icon:before { + content: '\e9d9'; +} +.review-form .rate-stars label { + display: inline; +} +.review-form .rate-stars input + .icon:before { + content: '\e9d9'; +} +.review-form .rate-stars input:checked + .icon:before { + content: '\e9d9'; +} +.review-form .rate-stars input:checked + * ~ .icon:before { + content: '\e9d7'; +} +.review-form .rate-stars:hover label.icon:before { + content: '\e9d9'; +} +.review-form .rate-stars label.icon:hover:before { + content: '\e9d9'; + } +.review-form .rate-stars label.icon:hover ~ label.icon:before{ + content: '\e9d7'; +} +.review-form .rate-stars input[type="radio"] { + display: none; +} /* re-usable tab styles */ .tabs { @@ -406,6 +462,20 @@ button .icon { margin-bottom: 1em; } + +dl { + font-size: 0.9em; + margin-top: 0.5em; +} +dt { + float: left; + margin-right: 0.5em; +} +dd { + margin-bottom: 0.25em; +} + + .all-shelves { display: flex; flex-direction: row; diff --git a/fedireads/static/js/shared.js b/fedireads/static/js/shared.js index 1859c4af9..60e2615c1 100644 --- a/fedireads/static/js/shared.js +++ b/fedireads/static/js/shared.js @@ -37,6 +37,17 @@ function reply(e) { return true; } +function rate_stars(e) { + e.preventDefault(); + ajaxPost(e.target); + rating = e.target.rating.value; + var stars = e.target.parentElement.getElementsByClassName('icon'); + for (var i = 0; i < stars.length ; i++) { + stars[i].className = rating > i ? 'icon icon-star-full' : 'icon icon-star-empty'; + } + return true; +} + function tabChange(e) { e.preventDefault(); var target = e.target.parentElement; diff --git a/fedireads/status.py b/fedireads/status.py index fa06997ad..8b31a1f65 100644 --- a/fedireads/status.py +++ b/fedireads/status.py @@ -25,6 +25,17 @@ def create_review_from_activity(author, activity): return review +def create_rating(user, book, rating): + ''' a review that's just a rating ''' + if not rating or rating < 1 or rating > 5: + raise ValueError('Invalid rating') + return models.Review.objects.create( + user=user, + book=book, + rating=rating, + ) + + def create_review(user, book, name, content, rating): ''' a book review has been added ''' name = sanitize(name) diff --git a/fedireads/templates/book.html b/fedireads/templates/book.html index 137d996b3..ee832d863 100644 --- a/fedireads/templates/book.html +++ b/fedireads/templates/book.html @@ -40,7 +40,8 @@
{{ status.content | safe }}{% endif %} + {% if not status.content and status.book and not hide_book and status.status_type != 'Boost' %} + {% include 'snippets/book_description.html' with book=status.book %} + {% endif %} + {% if status.status_type == 'Boost' %} {% include 'snippets/status_content.html' with status=status|boosted_status %} {% endif %} diff --git a/fedireads/templates/snippets/status_header.html b/fedireads/templates/snippets/status_header.html index 9650a034f..6efd0a574 100644 --- a/fedireads/templates/snippets/status_header.html +++ b/fedireads/templates/snippets/status_header.html @@ -5,10 +5,12 @@ {% if status.status_type == 'Update' %} {{ status.content | safe }} +{% elif status.status_type == 'Review' and not status.name and not status.content%} + rated {{ status.book.title }} {% elif status.status_type == 'Review' %} - reviewed {{ status.book.title }} + reviewed {{ status.book.title }} {% elif status.status_type == 'Comment' %} - commented on {{ status.book.title }} + commented on {{ status.book.title }} {% elif status.status_type == 'Boost' %} boosted {% elif status.reply_parent %} diff --git a/fedireads/templatetags/fr_display.py b/fedireads/templatetags/fr_display.py index 76b1efafc..8a81776a2 100644 --- a/fedireads/templatetags/fr_display.py +++ b/fedireads/templatetags/fr_display.py @@ -12,16 +12,17 @@ def dict_key(d, k): return d.get(k) or 0 -@register.filter(name='stars') -def stars(number): - ''' turn integers into stars ''' - try: - number = int(number) - except (ValueError, TypeError): - number = 0 - if not number: - return '' - return ('★' * number) + '☆' * (5 - number) +@register.filter(name='rating') +def get_rating(book, user): + ''' get a user's rating of a book ''' + rating = models.Review.objects.filter( + user=user, + book=book, + rating__isnull=False, + ).order_by('-published_date').first() + if rating: + return rating.rating + return 0 @register.filter(name='description') diff --git a/fedireads/urls.py b/fedireads/urls.py index a839220bf..d9897a1a7 100644 --- a/fedireads/urls.py +++ b/fedireads/urls.py @@ -78,6 +78,7 @@ urlpatterns = [ re_path(r'^edit_book/(?P