Merge branch 'main' into frontend-book-cover

This commit is contained in:
Fabien Basmaison 2021-04-25 11:24:21 +02:00
commit 23985e4357
18 changed files with 1568 additions and 2069 deletions

View File

@ -204,7 +204,9 @@ class ObjectMixin(ActivitypubMixin):
created = created or not bool(self.id) created = created or not bool(self.id)
# first off, we want to save normally no matter what # first off, we want to save normally no matter what
super().save(*args, **kwargs) super().save(*args, **kwargs)
if not broadcast: if not broadcast or (
hasattr(self, "status_type") and self.status_type == "Announce"
):
return return
# this will work for objects owned by a user (lists, shelves) # this will work for objects owned by a user (lists, shelves)

View File

@ -351,6 +351,16 @@ class Boost(ActivityMixin, Status):
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
""" save and notify """ """ save and notify """
# This constraint can't work as it would cross tables.
# class Meta:
# unique_together = ('user', 'boosted_status')
if (
Boost.objects.filter(boosted_status=self.boosted_status, user=self.user)
.exclude(id=self.id)
.exists()
):
return
super().save(*args, **kwargs) super().save(*args, **kwargs)
if not self.boosted_status.user.local or self.boosted_status.user == self.user: if not self.boosted_status.user.local or self.boosted_status.user == self.user:
return return

View File

@ -153,7 +153,7 @@ LANGUAGES = [
("de-de", _("German")), ("de-de", _("German")),
("es", _("Spanish")), ("es", _("Spanish")),
("fr-fr", _("French")), ("fr-fr", _("French")),
("zh-cn", _("Simplified Chinese")), ("zh-hans", _("Simplified Chinese")),
] ]

View File

@ -85,6 +85,13 @@ body {
} }
} }
/** Stars
******************************************************************************/
.stars {
white-space: nowrap;
}
/** Stars in a review form /** Stars in a review form
* *
* Specificity makes hovering taking over checked inputs. * Specificity makes hovering taking over checked inputs.

View File

@ -51,7 +51,7 @@
{% for status in report.statuses.select_subclasses.all %} {% for status in report.statuses.select_subclasses.all %}
<li> <li>
{% if status.deleted %} {% if status.deleted %}
<em>{% trans "Statuses has been deleted" %}</em> <em>{% trans "Status has been deleted" %}</em>
{% else %} {% else %}
{% include 'snippets/status/status.html' with status=status moderation_mode=True %} {% include 'snippets/status/status.html' with status=status moderation_mode=True %}
{% endif %} {% endif %}

View File

@ -1,7 +1,8 @@
{% load i18n %} {% load i18n %}
{% load bookwyrm_tags %}
{% if book.authors %} {% if book.authors %}
{% blocktrans with path=book.local_path title=book.title %}<a href="{{ path }}">{{ title }}</a> by {% endblocktrans %}{% include 'snippets/authors.html' with book=book %} {% blocktrans with path=book.local_path title=book|title %}<a href="{{ path }}">{{ title }}</a> by {% endblocktrans %}{% include 'snippets/authors.html' with book=book %}
{% else %} {% else %}
<a href="{{ book.local_path }}">{{ book.title }}</a> <a href="{{ book.local_path }}">{{ book|title }}</a>
{% endif %} {% endif %}

View File

@ -1,10 +1,10 @@
{% load i18n %} {% load i18n %}
{% if rating %} {% if rating %}
{% blocktrans with book_title=book.title display_rating=rating|floatformat:"0" review_title=name count counter=rating %}Review of "{{ book_title }}" ({{ display_rating }} star): {{ review_title }}{% plural %}Review of "{{ book_title }}" ({{ display_rating }} stars): {{ review_title }}{% endblocktrans %} {% blocktrans with book_title=book.title|safe display_rating=rating|floatformat:"0" review_title=name|safe count counter=rating %}Review of "{{ book_title }}" ({{ display_rating }} star): {{ review_title }}{% plural %}Review of "{{ book_title }}" ({{ display_rating }} stars): {{ review_title }}{% endblocktrans %}
{% else %} {% else %}
{% blocktrans with book_title=book.title review_title=name %}Review of "{{ book_title }}": {{ review_title }}{% endblocktrans %} {% blocktrans with book_title=book.title|safe review_title=name|safe %}Review of "{{ book_title }}": {{ review_title }}{% endblocktrans %}
{% endif %} {% endif %}

View File

@ -43,7 +43,7 @@
{% if status.book %} {% if status.book %}
{% if status.status_type == 'GeneratedNote' or status.status_type == 'Rating' %} {% if status.status_type == 'GeneratedNote' or status.status_type == 'Rating' %}
<a href="/book/{{ status.book.id }}">{{ status.book.title }}</a>{% if status.status_type == 'Rating' %}: <a href="/book/{{ status.book.id }}">{{ status.book|title }}</a>{% if status.status_type == 'Rating' %}:
<span <span
itemprop="reviewRating" itemprop="reviewRating"
itemscope itemscope
@ -62,7 +62,7 @@
{% include 'snippets/book_titleby.html' with book=status.book %} {% include 'snippets/book_titleby.html' with book=status.book %}
{% endif %} {% endif %}
{% elif status.mention_books %} {% elif status.mention_books %}
<a href="/book/{{ status.mention_books.first.id }}">{{ status.mention_books.first.title }}</a> <a href="/book/{{ status.mention_books.first.id }}">{{ status.mention_books.first|title }}</a>
{% endif %} {% endif %}
{% if status.progress %} {% if status.progress %}

View File

@ -168,6 +168,17 @@ def get_next_shelf(current_shelf):
return "to-read" return "to-read"
@register.filter(name="title")
def get_title(book):
""" display the subtitle if the title is short """
if not book:
return ""
title = book.title
if len(title) < 6 and book.subtitle:
title = "{:s}: {:s}".format(title, book.subtitle)
return title
@register.simple_tag(takes_context=False) @register.simple_tag(takes_context=False)
def related_status(notification): def related_status(notification):
""" for notifications """ """ for notifications """

View File

@ -269,7 +269,7 @@ class Status(TestCase):
def test_review_to_pure_activity(self, *_): def test_review_to_pure_activity(self, *_):
""" subclass of the base model version with a "pure" serializer """ """ subclass of the base model version with a "pure" serializer """
status = models.Review.objects.create( status = models.Review.objects.create(
name="Review name", name="Review's name",
content="test content", content="test content",
rating=3.0, rating=3.0,
user=self.local_user, user=self.local_user,
@ -280,7 +280,7 @@ class Status(TestCase):
self.assertEqual(activity["type"], "Article") self.assertEqual(activity["type"], "Article")
self.assertEqual( self.assertEqual(
activity["name"], activity["name"],
'Review of "%s" (3 stars): Review name' % self.book.title, 'Review of "%s" (3 stars): Review\'s name' % self.book.title,
) )
self.assertEqual(activity["content"], "test content") self.assertEqual(activity["content"], "test content")
self.assertEqual(activity["attachment"][0].type, "Document") self.assertEqual(activity["attachment"][0].type, "Document")

View File

@ -51,7 +51,7 @@ class InboxActivities(TestCase):
models.SiteSettings.objects.create() models.SiteSettings.objects.create()
@patch("bookwyrm.activitystreams.ActivityStream.add_status") @patch("bookwyrm.activitystreams.ActivityStream.add_status")
def test_handle_boost(self, _): def test_boost(self, redis_mock):
""" boost a status """ """ boost a status """
self.assertEqual(models.Notification.objects.count(), 0) self.assertEqual(models.Notification.objects.count(), 0)
activity = { activity = {
@ -66,16 +66,23 @@ class InboxActivities(TestCase):
with patch("bookwyrm.models.status.Status.ignore_activity") as discarder: with patch("bookwyrm.models.status.Status.ignore_activity") as discarder:
discarder.return_value = False discarder.return_value = False
views.inbox.activity_task(activity) views.inbox.activity_task(activity)
# boost added to redis activitystreams
self.assertTrue(redis_mock.called)
# boost created of correct status
boost = models.Boost.objects.get() boost = models.Boost.objects.get()
self.assertEqual(boost.boosted_status, self.status) self.assertEqual(boost.boosted_status, self.status)
# notification sent to original poster
notification = models.Notification.objects.get() notification = models.Notification.objects.get()
self.assertEqual(notification.user, self.local_user) self.assertEqual(notification.user, self.local_user)
self.assertEqual(notification.related_status, self.status) self.assertEqual(notification.related_status, self.status)
@responses.activate @responses.activate
@patch("bookwyrm.activitystreams.ActivityStream.add_status") @patch("bookwyrm.activitystreams.ActivityStream.add_status")
def test_handle_boost_remote_status(self, redis_mock): def test_boost_remote_status(self, redis_mock):
""" boost a status """ """ boost a status from a remote server """
work = models.Work.objects.create(title="work title") work = models.Work.objects.create(title="work title")
book = models.Edition.objects.create( book = models.Edition.objects.create(
title="Test", title="Test",
@ -123,7 +130,7 @@ class InboxActivities(TestCase):
self.assertEqual(boost.boosted_status.comment.book, book) self.assertEqual(boost.boosted_status.comment.book, book)
@responses.activate @responses.activate
def test_handle_discarded_boost(self): def test_discarded_boost(self):
""" test a boost of a mastodon status that will be discarded """ """ test a boost of a mastodon status that will be discarded """
status = models.Status( status = models.Status(
content="hi", content="hi",
@ -146,7 +153,7 @@ class InboxActivities(TestCase):
views.inbox.activity_task(activity) views.inbox.activity_task(activity)
self.assertEqual(models.Boost.objects.count(), 0) self.assertEqual(models.Boost.objects.count(), 0)
def test_handle_unboost(self): def test_unboost(self):
""" undo a boost """ """ undo a boost """
with patch("bookwyrm.activitystreams.ActivityStream.add_status"): with patch("bookwyrm.activitystreams.ActivityStream.add_status"):
boost = models.Boost.objects.create( boost = models.Boost.objects.create(
@ -175,7 +182,7 @@ class InboxActivities(TestCase):
self.assertTrue(redis_mock.called) self.assertTrue(redis_mock.called)
self.assertFalse(models.Boost.objects.exists()) self.assertFalse(models.Boost.objects.exists())
def test_handle_unboost_unknown_boost(self): def test_unboost_unknown_boost(self):
""" undo a boost """ """ undo a boost """
activity = { activity = {
"type": "Undo", "type": "Undo",

View File

@ -1,4 +1,5 @@
""" test for app action functionality """ """ test for app action functionality """
import json
from unittest.mock import patch from unittest.mock import patch
from django.test import TestCase from django.test import TestCase
from django.test.client import RequestFactory from django.test.client import RequestFactory
@ -39,7 +40,7 @@ class InteractionViews(TestCase):
parent_work=work, parent_work=work,
) )
def test_handle_favorite(self, _): def test_favorite(self, _):
""" create and broadcast faving a status """ """ create and broadcast faving a status """
view = views.Favorite.as_view() view = views.Favorite.as_view()
request = self.factory.post("") request = self.factory.post("")
@ -57,7 +58,7 @@ class InteractionViews(TestCase):
self.assertEqual(notification.user, self.local_user) self.assertEqual(notification.user, self.local_user)
self.assertEqual(notification.related_user, self.remote_user) self.assertEqual(notification.related_user, self.remote_user)
def test_handle_unfavorite(self, _): def test_unfavorite(self, _):
""" unfav a status """ """ unfav a status """
view = views.Unfavorite.as_view() view = views.Unfavorite.as_view()
request = self.factory.post("") request = self.factory.post("")
@ -74,7 +75,7 @@ class InteractionViews(TestCase):
self.assertEqual(models.Favorite.objects.count(), 0) self.assertEqual(models.Favorite.objects.count(), 0)
self.assertEqual(models.Notification.objects.count(), 0) self.assertEqual(models.Notification.objects.count(), 0)
def test_handle_boost(self, _): def test_boost(self, _):
""" boost a status """ """ boost a status """
view = views.Boost.as_view() view = views.Boost.as_view()
request = self.factory.post("") request = self.factory.post("")
@ -85,6 +86,7 @@ class InteractionViews(TestCase):
view(request, status.id) view(request, status.id)
boost = models.Boost.objects.get() boost = models.Boost.objects.get()
self.assertEqual(boost.boosted_status, status) self.assertEqual(boost.boosted_status, status)
self.assertEqual(boost.user, self.remote_user) self.assertEqual(boost.user, self.remote_user)
self.assertEqual(boost.privacy, "public") self.assertEqual(boost.privacy, "public")
@ -95,7 +97,7 @@ class InteractionViews(TestCase):
self.assertEqual(notification.related_user, self.remote_user) self.assertEqual(notification.related_user, self.remote_user)
self.assertEqual(notification.related_status, status) self.assertEqual(notification.related_status, status)
def test_handle_self_boost(self, _): def test_self_boost(self, _):
""" boost your own status """ """ boost your own status """
view = views.Boost.as_view() view = views.Boost.as_view()
request = self.factory.post("") request = self.factory.post("")
@ -103,7 +105,14 @@ class InteractionViews(TestCase):
with patch("bookwyrm.activitystreams.ActivityStream.add_status"): with patch("bookwyrm.activitystreams.ActivityStream.add_status"):
status = models.Status.objects.create(user=self.local_user, content="hi") status = models.Status.objects.create(user=self.local_user, content="hi")
view(request, status.id) with patch(
"bookwyrm.models.activitypub_mixin.broadcast_task.delay"
) as broadcast_mock:
view(request, status.id)
self.assertEqual(broadcast_mock.call_count, 1)
activity = json.loads(broadcast_mock.call_args[0][1])
self.assertEqual(activity["type"], "Announce")
boost = models.Boost.objects.get() boost = models.Boost.objects.get()
self.assertEqual(boost.boosted_status, status) self.assertEqual(boost.boosted_status, status)
@ -112,7 +121,7 @@ class InteractionViews(TestCase):
self.assertFalse(models.Notification.objects.exists()) self.assertFalse(models.Notification.objects.exists())
def test_handle_boost_unlisted(self, _): def test_boost_unlisted(self, _):
""" boost a status """ """ boost a status """
view = views.Boost.as_view() view = views.Boost.as_view()
request = self.factory.post("") request = self.factory.post("")
@ -127,7 +136,7 @@ class InteractionViews(TestCase):
boost = models.Boost.objects.get() boost = models.Boost.objects.get()
self.assertEqual(boost.privacy, "unlisted") self.assertEqual(boost.privacy, "unlisted")
def test_handle_boost_private(self, _): def test_boost_private(self, _):
""" boost a status """ """ boost a status """
view = views.Boost.as_view() view = views.Boost.as_view()
request = self.factory.post("") request = self.factory.post("")
@ -140,7 +149,7 @@ class InteractionViews(TestCase):
view(request, status.id) view(request, status.id)
self.assertFalse(models.Boost.objects.exists()) self.assertFalse(models.Boost.objects.exists())
def test_handle_boost_twice(self, _): def test_boost_twice(self, _):
""" boost a status """ """ boost a status """
view = views.Boost.as_view() view = views.Boost.as_view()
request = self.factory.post("") request = self.factory.post("")
@ -152,7 +161,7 @@ class InteractionViews(TestCase):
view(request, status.id) view(request, status.id)
self.assertEqual(models.Boost.objects.count(), 1) self.assertEqual(models.Boost.objects.count(), 1)
def test_handle_unboost(self, _): def test_unboost(self, _):
""" undo a boost """ """ undo a boost """
view = views.Unboost.as_view() view = views.Unboost.as_view()
request = self.factory.post("") request = self.factory.post("")

4
bw-dev
View File

@ -90,10 +90,10 @@ case "$CMD" in
runweb python manage.py collectstatic --no-input runweb python manage.py collectstatic --no-input
;; ;;
makemessages) makemessages)
runweb django-admin makemessages --no-wrap --ignore=venv3 $@ runweb django-admin makemessages --no-wrap --ignore=venv $@
;; ;;
compilemessages) compilemessages)
runweb django-admin compilemessages --ignore venv3 $@ runweb django-admin compilemessages --ignore venv $@
;; ;;
build) build)
docker-compose build docker-compose build

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.