From cf2b9937c6a0dd58141e1c808351a97f11397036 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Wed, 13 Jan 2021 08:10:50 -0800 Subject: [PATCH] Adds fav/boost class view --- bookwyrm/outgoing.py | 97 --------------- bookwyrm/tests/test_outgoing.py | 109 ---------------- bookwyrm/tests/views/test_interaction.py | 152 +++++++++++++++++++++++ bookwyrm/urls.py | 8 +- bookwyrm/view_actions.py | 9 -- bookwyrm/views/__init__.py | 1 + bookwyrm/views/interaction.py | 131 +++++++++++++++++++ bookwyrm/views/status.py | 2 +- 8 files changed, 289 insertions(+), 220 deletions(-) create mode 100644 bookwyrm/tests/views/test_interaction.py create mode 100644 bookwyrm/views/interaction.py diff --git a/bookwyrm/outgoing.py b/bookwyrm/outgoing.py index 1e80852b..546d1201 100644 --- a/bookwyrm/outgoing.py +++ b/bookwyrm/outgoing.py @@ -16,7 +16,6 @@ from bookwyrm.broadcast import broadcast from bookwyrm.sanitize_html import InputHtmlParser from bookwyrm.status import create_notification from bookwyrm.status import create_generated_note -from bookwyrm.status import delete_status from bookwyrm.settings import DOMAIN from bookwyrm.utils import regex @@ -211,99 +210,3 @@ def handle_imported_book(user, item, include_reviews, privacy): # we don't need to send out pure activities because non-bookwyrm # instances don't need this data broadcast(user, review.to_create_activity(user), privacy=privacy) - - -def handle_favorite(user, status): - ''' a user likes a status ''' - try: - favorite = models.Favorite.objects.create( - status=status, - user=user - ) - except IntegrityError: - # you already fav'ed that - return - - fav_activity = favorite.to_activity() - broadcast( - user, fav_activity, privacy='direct', direct_recipients=[status.user]) - if status.user.local: - create_notification( - status.user, - 'FAVORITE', - related_user=user, - related_status=status - ) - - -def handle_unfavorite(user, status): - ''' a user likes a status ''' - try: - favorite = models.Favorite.objects.get( - status=status, - user=user - ) - except models.Favorite.DoesNotExist: - # can't find that status, idk - return - - fav_activity = favorite.to_undo_activity(user) - favorite.delete() - broadcast(user, fav_activity, direct_recipients=[status.user]) - - # check for notification - if status.user.local: - notification = models.Notification.objects.filter( - user=status.user, related_user=user, - related_status=status, notification_type='FAVORITE' - ).first() - if notification: - notification.delete() - - -def handle_boost(user, status): - ''' a user wishes to boost a status ''' - # is it boostable? - if not status.boostable: - return - - if models.Boost.objects.filter( - boosted_status=status, user=user).exists(): - # you already boosted that. - return - boost = models.Boost.objects.create( - boosted_status=status, - privacy=status.privacy, - user=user, - ) - - boost_activity = boost.to_activity() - broadcast(user, boost_activity) - - if status.user.local: - create_notification( - status.user, - 'BOOST', - related_user=user, - related_status=status - ) - - -def handle_unboost(user, status): - ''' a user regrets boosting a status ''' - boost = models.Boost.objects.filter( - boosted_status=status, user=user - ).first() - activity = boost.to_undo_activity(user) - - boost.delete() - broadcast(user, activity) - - # delete related notification - if status.user.local: - notification = models.Notification.objects.filter( - user=status.user, related_user=user, - related_status=status, notification_type='BOOST' - ).first() - if notification: - notification.delete() diff --git a/bookwyrm/tests/test_outgoing.py b/bookwyrm/tests/test_outgoing.py index 956862e0..ec29b48d 100644 --- a/bookwyrm/tests/test_outgoing.py +++ b/bookwyrm/tests/test_outgoing.py @@ -430,112 +430,3 @@ class Outgoing(TestCase): self.assertFalse(models.Review.objects.filter( book=self.book, user=self.local_user ).exists()) - - - def test_handle_delete_status(self): - ''' marks a status as deleted ''' - status = models.Status.objects.create( - user=self.local_user, content='hi') - self.assertFalse(status.deleted) - with patch('bookwyrm.broadcast.broadcast_task.delay'): - outgoing.handle_delete_status(self.local_user, status) - status.refresh_from_db() - self.assertTrue(status.deleted) - - - def test_handle_favorite(self): - ''' create and broadcast faving a status ''' - status = models.Status.objects.create( - user=self.local_user, content='hi') - - with patch('bookwyrm.broadcast.broadcast_task.delay'): - outgoing.handle_favorite(self.remote_user, status) - fav = models.Favorite.objects.get() - self.assertEqual(fav.status, status) - self.assertEqual(fav.user, self.remote_user) - - notification = models.Notification.objects.get() - self.assertEqual(notification.notification_type, 'FAVORITE') - self.assertEqual(notification.user, self.local_user) - self.assertEqual(notification.related_user, self.remote_user) - - - def test_handle_unfavorite(self): - ''' unfav a status ''' - status = models.Status.objects.create( - user=self.local_user, content='hi') - with patch('bookwyrm.broadcast.broadcast_task.delay'): - outgoing.handle_favorite(self.remote_user, status) - - self.assertEqual(models.Favorite.objects.count(), 1) - self.assertEqual(models.Notification.objects.count(), 1) - - with patch('bookwyrm.broadcast.broadcast_task.delay'): - outgoing.handle_unfavorite(self.remote_user, status) - self.assertEqual(models.Favorite.objects.count(), 0) - self.assertEqual(models.Notification.objects.count(), 0) - - - def test_handle_boost(self): - ''' boost a status ''' - status = models.Status.objects.create( - user=self.local_user, content='hi') - - with patch('bookwyrm.broadcast.broadcast_task.delay'): - outgoing.handle_boost(self.remote_user, status) - - boost = models.Boost.objects.get() - self.assertEqual(boost.boosted_status, status) - self.assertEqual(boost.user, self.remote_user) - self.assertEqual(boost.privacy, 'public') - - notification = models.Notification.objects.get() - self.assertEqual(notification.notification_type, 'BOOST') - self.assertEqual(notification.user, self.local_user) - self.assertEqual(notification.related_user, self.remote_user) - self.assertEqual(notification.related_status, status) - - def test_handle_boost_unlisted(self): - ''' boost a status ''' - status = models.Status.objects.create( - user=self.local_user, content='hi', privacy='unlisted') - - with patch('bookwyrm.broadcast.broadcast_task.delay'): - outgoing.handle_boost(self.remote_user, status) - - boost = models.Boost.objects.get() - self.assertEqual(boost.privacy, 'unlisted') - - def test_handle_boost_private(self): - ''' boost a status ''' - status = models.Status.objects.create( - user=self.local_user, content='hi', privacy='followers') - - with patch('bookwyrm.broadcast.broadcast_task.delay'): - outgoing.handle_boost(self.remote_user, status) - self.assertFalse(models.Boost.objects.exists()) - - def test_handle_boost_twice(self): - ''' boost a status ''' - status = models.Status.objects.create( - user=self.local_user, content='hi') - - with patch('bookwyrm.broadcast.broadcast_task.delay'): - outgoing.handle_boost(self.remote_user, status) - outgoing.handle_boost(self.remote_user, status) - self.assertEqual(models.Boost.objects.count(), 1) - - - def test_handle_unboost(self): - ''' undo a boost ''' - status = models.Status.objects.create( - user=self.local_user, content='hi') - with patch('bookwyrm.broadcast.broadcast_task.delay'): - outgoing.handle_boost(self.remote_user, status) - - self.assertEqual(models.Boost.objects.count(), 1) - self.assertEqual(models.Notification.objects.count(), 1) - with patch('bookwyrm.broadcast.broadcast_task.delay'): - outgoing.handle_unboost(self.remote_user, status) - self.assertEqual(models.Boost.objects.count(), 0) - self.assertEqual(models.Notification.objects.count(), 0) diff --git a/bookwyrm/tests/views/test_interaction.py b/bookwyrm/tests/views/test_interaction.py new file mode 100644 index 00000000..c77d6f28 --- /dev/null +++ b/bookwyrm/tests/views/test_interaction.py @@ -0,0 +1,152 @@ +''' test for app action functionality ''' +from unittest.mock import patch +from django.test import TestCase +from django.test.client import RequestFactory + +from bookwyrm import models, views + + +class InteractionViews(TestCase): + ''' viewing and creating statuses ''' + def setUp(self): + ''' we need basic test data and mocks ''' + self.factory = RequestFactory() + self.local_user = models.User.objects.create_user( + 'mouse@local.com', 'mouse@mouse.com', 'mouseword', + local=True, localname='mouse', + remote_id='https://example.com/users/mouse', + ) + with patch('bookwyrm.models.user.set_remote_server'): + self.remote_user = models.User.objects.create_user( + 'rat', 'rat@email.com', 'ratword', + local=False, + remote_id='https://example.com/users/rat', + inbox='https://example.com/users/rat/inbox', + outbox='https://example.com/users/rat/outbox', + ) + + work = models.Work.objects.create(title='Test Work') + self.book = models.Edition.objects.create( + title='Example Edition', + remote_id='https://example.com/book/1', + parent_work=work + ) + + + def test_handle_favorite(self): + ''' create and broadcast faving a status ''' + view = views.Favorite.as_view() + request = self.factory.post('') + request.user = self.local_user + status = models.Status.objects.create( + user=self.local_user, content='hi') + + with patch('bookwyrm.broadcast.broadcast_task.delay'): + view(request, status) + fav = models.Favorite.objects.get() + self.assertEqual(fav.status, status) + self.assertEqual(fav.user, self.remote_user) + + notification = models.Notification.objects.get() + self.assertEqual(notification.notification_type, 'FAVORITE') + self.assertEqual(notification.user, self.local_user) + self.assertEqual(notification.related_user, self.remote_user) + + + def test_handle_unfavorite(self): + ''' unfav a status ''' + view = views.Unfavorite.as_view() + request = self.factory.post('') + request.user = self.local_user + status = models.Status.objects.create( + user=self.local_user, content='hi') + with patch('bookwyrm.broadcast.broadcast_task.delay'): + view(request, status) + + self.assertEqual(models.Favorite.objects.count(), 1) + self.assertEqual(models.Notification.objects.count(), 1) + + with patch('bookwyrm.broadcast.broadcast_task.delay'): + view(request, status) + self.assertEqual(models.Favorite.objects.count(), 0) + self.assertEqual(models.Notification.objects.count(), 0) + + + def test_handle_boost(self): + ''' boost a status ''' + view = views.Boost.as_view() + request = self.factory.post('') + request.user = self.local_user + status = models.Status.objects.create( + user=self.local_user, content='hi') + + with patch('bookwyrm.broadcast.broadcast_task.delay'): + view(request, status) + + boost = models.Boost.objects.get() + self.assertEqual(boost.boosted_status, status) + self.assertEqual(boost.user, self.remote_user) + self.assertEqual(boost.privacy, 'public') + + notification = models.Notification.objects.get() + self.assertEqual(notification.notification_type, 'BOOST') + self.assertEqual(notification.user, self.local_user) + self.assertEqual(notification.related_user, self.remote_user) + self.assertEqual(notification.related_status, status) + + def test_handle_boost_unlisted(self): + ''' boost a status ''' + view = views.Boost.as_view() + request = self.factory.post('') + request.user = self.local_user + status = models.Status.objects.create( + user=self.local_user, content='hi', privacy='unlisted') + + with patch('bookwyrm.broadcast.broadcast_task.delay'): + view(request, status) + + boost = models.Boost.objects.get() + self.assertEqual(boost.privacy, 'unlisted') + + def test_handle_boost_private(self): + ''' boost a status ''' + view = views.Boost.as_view() + request = self.factory.post('') + request.user = self.local_user + status = models.Status.objects.create( + user=self.local_user, content='hi', privacy='followers') + + with patch('bookwyrm.broadcast.broadcast_task.delay'): + view(request, status) + self.assertFalse(models.Boost.objects.exists()) + + def test_handle_boost_twice(self): + ''' boost a status ''' + view = views.Boost.as_view() + request = self.factory.post('') + request.user = self.local_user + status = models.Status.objects.create( + user=self.local_user, content='hi') + + with patch('bookwyrm.broadcast.broadcast_task.delay'): + view(request, status) + view(request, status) + self.assertEqual(models.Boost.objects.count(), 1) + + + def test_handle_unboost(self): + ''' undo a boost ''' + view = views.Unboost.as_view() + request = self.factory.post('') + request.user = self.local_user + status = models.Status.objects.create( + user=self.local_user, content='hi') + with patch('bookwyrm.broadcast.broadcast_task.delay'): + view(request, status) + + self.assertEqual(models.Boost.objects.count(), 1) + self.assertEqual(models.Notification.objects.count(), 1) + with patch('bookwyrm.broadcast.broadcast_task.delay'): + view(request, status) + self.assertEqual(models.Boost.objects.count(), 0) + self.assertEqual(models.Notification.objects.count(), 0) diff --git a/bookwyrm/urls.py b/bookwyrm/urls.py index a6e0cd30..dfd2ff81 100644 --- a/bookwyrm/urls.py +++ b/bookwyrm/urls.py @@ -82,10 +82,10 @@ urlpatterns = [ views.DeleteStatus.as_view()), # interact - re_path(r'^favorite/(?P\d+)/?$', actions.favorite), - re_path(r'^unfavorite/(?P\d+)/?$', actions.unfavorite), - re_path(r'^boost/(?P\d+)/?$', actions.boost), - re_path(r'^unboost/(?P\d+)/?$', actions.unboost), + re_path(r'^favorite/(?P\d+)/?$', views.Favorite.as_view()), + re_path(r'^unfavorite/(?P\d+)/?$', views.Unfavorite.as_view()), + re_path(r'^boost/(?P\d+)/?$', views.Boost.as_view()), + re_path(r'^unboost/(?P\d+)/?$', views.Unboost.as_view()), # books re_path(r'%s(.json)?/?$' % book_path, vviews.book_page), diff --git a/bookwyrm/view_actions.py b/bookwyrm/view_actions.py index 6b818350..1a6edd7f 100644 --- a/bookwyrm/view_actions.py +++ b/bookwyrm/view_actions.py @@ -378,15 +378,6 @@ def untag(request): return redirect('/book/%s' % book_id) -@login_required -@require_POST -def favorite(request, status_id): - ''' like a status ''' - status = models.Status.objects.get(id=status_id) - outgoing.handle_favorite(request.user, status) - return redirect(request.headers.get('Referer', '/')) - - @login_required @require_POST def unfavorite(request, status_id): diff --git a/bookwyrm/views/__init__.py b/bookwyrm/views/__init__.py index 1714c59e..2611184b 100644 --- a/bookwyrm/views/__init__.py +++ b/bookwyrm/views/__init__.py @@ -8,3 +8,4 @@ from .direct_message import DirectMessage from .import_data import Import, ImportStatus from .user import User, EditUser, Followers, Following from .status import Status, Replies, CreateStatus, DeleteStatus +from .interaction import Favorite, Unfavorite, Boost, Unboost diff --git a/bookwyrm/views/interaction.py b/bookwyrm/views/interaction.py new file mode 100644 index 00000000..2c5de375 --- /dev/null +++ b/bookwyrm/views/interaction.py @@ -0,0 +1,131 @@ +''' boosts and favs ''' +from django.db import IntegrityError +from django.contrib.auth.decorators import login_required +from django.http import HttpResponseBadRequest, HttpResponseNotFound +from django.shortcuts import redirect +from django.utils.decorators import method_decorator +from django.views import View + +from bookwyrm import models +from bookwyrm.broadcast import broadcast +from bookwyrm.status import create_notification + + +# pylint: disable= no-self-use +@method_decorator(login_required, name='dispatch') +class Favorite(View): + ''' like a status ''' + def post(self, request, status_id): + ''' create a like ''' + status = models.Status.objects.get(id=status_id) + try: + favorite = models.Favorite.objects.create( + status=status, + user=request.user + ) + except IntegrityError: + # you already fav'ed that + return HttpResponseBadRequest() + + fav_activity = favorite.to_activity() + broadcast( + request.user, fav_activity, privacy='direct', + direct_recipients=[status.user]) + if status.user.local: + create_notification( + status.user, + 'FAVORITE', + related_user=request.user, + related_status=status + ) + return redirect(request.headers.get('Referer', '/')) + + +@method_decorator(login_required, name='dispatch') +class Unfavorite(View): + ''' take back a fav ''' + def post(self, request, status_id): + ''' unlike a status ''' + status = models.Status.objects.get(id=status_id) + try: + favorite = models.Favorite.objects.get( + status=status, + user=request.user + ) + except models.Favorite.DoesNotExist: + # can't find that status, idk + return HttpResponseNotFound() + + fav_activity = favorite.to_undo_activity(request.user) + favorite.delete() + broadcast(request.user, fav_activity, direct_recipients=[status.user]) + + # check for notification + if status.user.local: + notification = models.Notification.objects.filter( + user=status.user, related_user=request.user, + related_status=status, notification_type='FAVORITE' + ).first() + if notification: + notification.delete() + return redirect(request.headers.get('Referer', '/')) + + +@method_decorator(login_required, name='dispatch') +class Boost(View): + ''' boost a status ''' + def post(self, request, status_id): + ''' boost a status ''' + status = models.Status.objects.get(id=status_id) + # is it boostable? + if not status.boostable: + return HttpResponseBadRequest() + + if models.Boost.objects.filter( + boosted_status=status, user=request.user).exists(): + # you already boosted that. + return redirect(request.headers.get('Referer', '/')) + + boost = models.Boost.objects.create( + boosted_status=status, + privacy=status.privacy, + user=request.user, + ) + + boost_activity = boost.to_activity() + broadcast(request.user, boost_activity) + + if status.user.local: + create_notification( + status.user, + 'BOOST', + related_user=request.user, + related_status=status + ) + return redirect(request.headers.get('Referer', '/')) + + + +@method_decorator(login_required, name='dispatch') +class Unboost(View): + ''' boost a status ''' + def post(self, request, status_id): + ''' boost a status ''' + status = models.Status.objects.get(id=status_id) + boost = models.Boost.objects.filter( + boosted_status=status, user=request.user + ).first() + activity = boost.to_undo_activity(request.user) + + boost.delete() + broadcast(request.user, activity) + + # delete related notification + if status.user.local: + notification = models.Notification.objects.filter( + user=status.user, related_user=request.user, + related_status=status, notification_type='BOOST' + ).first() + if notification: + notification.delete() + return redirect(request.headers.get('Referer', '/')) diff --git a/bookwyrm/views/status.py b/bookwyrm/views/status.py index 60a1f814..39cd6077 100644 --- a/bookwyrm/views/status.py +++ b/bookwyrm/views/status.py @@ -1,4 +1,4 @@ -''' non-interactive pages ''' +''' what are we here for if not for posting ''' import re from django.contrib.auth.decorators import login_required from django.http import HttpResponseBadRequest, HttpResponseNotFound