From baba2e2057a4e675a178cc21cc32882c1a12bbbb Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Wed, 20 Oct 2021 13:08:33 -0700 Subject: [PATCH 1/9] Move shelf views into directory --- bookwyrm/views/__init__.py | 8 +++++--- bookwyrm/views/shelf/__init__.py | 0 bookwyrm/views/{ => shelf}/shelf.py | 0 3 files changed, 5 insertions(+), 3 deletions(-) create mode 100644 bookwyrm/views/shelf/__init__.py rename bookwyrm/views/{ => shelf}/shelf.py (100%) diff --git a/bookwyrm/views/__init__.py b/bookwyrm/views/__init__.py index b502bd8d5..ba83b5320 100644 --- a/bookwyrm/views/__init__.py +++ b/bookwyrm/views/__init__.py @@ -38,6 +38,11 @@ from .landing.login import Login, Logout from .landing.register import Register, ConfirmEmail, ConfirmEmailCode, resend_link from .landing.password import PasswordResetRequest, PasswordReset +# shelves +from .shelf.shelf import Shelf +from .shelf.shelf import create_shelf, delete_shelf +from .shelf.shelf import shelve, unshelve + # misc views from .author import Author, EditAuthor from .directory import Directory @@ -69,9 +74,6 @@ from .reading import create_readthrough, delete_readthrough, delete_progressupda from .reading import ReadingStatus from .rss_feed import RssFeed from .search import Search -from .shelf import Shelf -from .shelf import create_shelf, delete_shelf -from .shelf import shelve, unshelve from .status import CreateStatus, EditStatus, DeleteStatus, update_progress from .status import edit_readthrough from .updates import get_notification_count, get_unread_status_count diff --git a/bookwyrm/views/shelf/__init__.py b/bookwyrm/views/shelf/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/bookwyrm/views/shelf.py b/bookwyrm/views/shelf/shelf.py similarity index 100% rename from bookwyrm/views/shelf.py rename to bookwyrm/views/shelf/shelf.py From 6a2f962f8db0314849e8b9b3908d8347e24116a3 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Wed, 20 Oct 2021 13:15:43 -0700 Subject: [PATCH 2/9] Split shelf view into multiple files --- bookwyrm/views/__init__.py | 4 +- bookwyrm/views/shelf/shelf.py | 100 +------------------------ bookwyrm/views/shelf/shelf_actions.py | 103 ++++++++++++++++++++++++++ 3 files changed, 106 insertions(+), 101 deletions(-) create mode 100644 bookwyrm/views/shelf/shelf_actions.py diff --git a/bookwyrm/views/__init__.py b/bookwyrm/views/__init__.py index ba83b5320..e1dd83557 100644 --- a/bookwyrm/views/__init__.py +++ b/bookwyrm/views/__init__.py @@ -40,8 +40,8 @@ from .landing.password import PasswordResetRequest, PasswordReset # shelves from .shelf.shelf import Shelf -from .shelf.shelf import create_shelf, delete_shelf -from .shelf.shelf import shelve, unshelve +from .shelf.shelf_actions import create_shelf, delete_shelf +from .shelf.shelf_actions import shelve, unshelve # misc views from .author import Author, EditAuthor diff --git a/bookwyrm/views/shelf/shelf.py b/bookwyrm/views/shelf/shelf.py index 0b830d906..f8cffe93f 100644 --- a/bookwyrm/views/shelf/shelf.py +++ b/bookwyrm/views/shelf/shelf.py @@ -1,7 +1,6 @@ """ shelf views """ from collections import namedtuple -from django.db import IntegrityError, transaction from django.db.models import OuterRef, Subquery, F from django.contrib.auth.decorators import login_required from django.core.paginator import Paginator @@ -11,12 +10,11 @@ from django.template.response import TemplateResponse from django.utils.decorators import method_decorator from django.utils.translation import gettext_lazy as _ from django.views import View -from django.views.decorators.http import require_POST from bookwyrm import forms, models from bookwyrm.activitypub import ActivitypubResponse from bookwyrm.settings import PAGE_LENGTH -from .helpers import is_api_request, get_user_from_username +from bookwyrm.views.helpers import is_api_request, get_user_from_username # pylint: disable=no-self-use @@ -128,102 +126,6 @@ class Shelf(View): return redirect(shelf.local_path) -@login_required -@require_POST -def create_shelf(request): - """user generated shelves""" - form = forms.ShelfForm(request.POST) - if not form.is_valid(): - return redirect(request.headers.get("Referer", "/")) - - shelf = form.save() - return redirect(shelf.local_path) - - -@login_required -@require_POST -def delete_shelf(request, shelf_id): - """user generated shelves""" - shelf = get_object_or_404(models.Shelf, id=shelf_id) - shelf.raise_not_deletable(request.user) - - shelf.delete() - return redirect("user-shelves", request.user.localname) - - -@login_required -@require_POST -@transaction.atomic -def shelve(request): - """put a book on a user's shelf""" - book = get_object_or_404(models.Edition, id=request.POST.get("book")) - desired_shelf = get_object_or_404( - request.user.shelf_set, identifier=request.POST.get("shelf") - ) - - # first we need to remove from the specified shelf - change_from_current_identifier = request.POST.get("change-shelf-from") - if change_from_current_identifier: - # find the shelfbook obj and delete it - get_object_or_404( - models.ShelfBook, - book=book, - user=request.user, - shelf__identifier=change_from_current_identifier, - ).delete() - - # A book can be on multiple shelves, but only on one read status shelf at a time - if desired_shelf.identifier in models.Shelf.READ_STATUS_IDENTIFIERS: - # figure out where state shelf it's currently on (if any) - current_read_status_shelfbook = ( - models.ShelfBook.objects.select_related("shelf") - .filter( - shelf__identifier__in=models.Shelf.READ_STATUS_IDENTIFIERS, - user=request.user, - book=book, - ) - .first() - ) - if current_read_status_shelfbook is not None: - if ( - current_read_status_shelfbook.shelf.identifier - != desired_shelf.identifier - ): - current_read_status_shelfbook.delete() - else: # It is already on the shelf - return redirect(request.headers.get("Referer", "/")) - - # create the new shelf-book entry - models.ShelfBook.objects.create( - book=book, shelf=desired_shelf, user=request.user - ) - else: - # we're putting it on a custom shelf - try: - models.ShelfBook.objects.create( - book=book, shelf=desired_shelf, user=request.user - ) - # The book is already on this shelf. - # Might be good to alert, or reject the action? - except IntegrityError: - pass - return redirect(request.headers.get("Referer", "/")) - - -@login_required -@require_POST -def unshelve(request): - """put a on a user's shelf""" - book = get_object_or_404(models.Edition, id=request.POST.get("book")) - shelf_book = get_object_or_404( - models.ShelfBook, book=book, shelf__id=request.POST["shelf"] - ) - shelf_book.raise_not_deletable(request.user) - - shelf_book.delete() - return redirect(request.headers.get("Referer", "/")) - - def sort_books(books, sort): """Books in shelf sorting""" sort_fields = [ diff --git a/bookwyrm/views/shelf/shelf_actions.py b/bookwyrm/views/shelf/shelf_actions.py new file mode 100644 index 000000000..470b2d3d3 --- /dev/null +++ b/bookwyrm/views/shelf/shelf_actions.py @@ -0,0 +1,103 @@ +""" shelf views """ +from django.db import IntegrityError, transaction +from django.contrib.auth.decorators import login_required +from django.shortcuts import get_object_or_404, redirect +from django.views.decorators.http import require_POST + +from bookwyrm import forms, models + + +@login_required +@require_POST +def create_shelf(request): + """user generated shelves""" + form = forms.ShelfForm(request.POST) + if not form.is_valid(): + return redirect(request.headers.get("Referer", "/")) + + shelf = form.save() + return redirect(shelf.local_path) + + +@login_required +@require_POST +def delete_shelf(request, shelf_id): + """user generated shelves""" + shelf = get_object_or_404(models.Shelf, id=shelf_id) + shelf.raise_not_deletable(request.user) + + shelf.delete() + return redirect("user-shelves", request.user.localname) + + +@login_required +@require_POST +@transaction.atomic +def shelve(request): + """put a book on a user's shelf""" + book = get_object_or_404(models.Edition, id=request.POST.get("book")) + desired_shelf = get_object_or_404( + request.user.shelf_set, identifier=request.POST.get("shelf") + ) + + # first we need to remove from the specified shelf + change_from_current_identifier = request.POST.get("change-shelf-from") + if change_from_current_identifier: + # find the shelfbook obj and delete it + get_object_or_404( + models.ShelfBook, + book=book, + user=request.user, + shelf__identifier=change_from_current_identifier, + ).delete() + + # A book can be on multiple shelves, but only on one read status shelf at a time + if desired_shelf.identifier in models.Shelf.READ_STATUS_IDENTIFIERS: + # figure out where state shelf it's currently on (if any) + current_read_status_shelfbook = ( + models.ShelfBook.objects.select_related("shelf") + .filter( + shelf__identifier__in=models.Shelf.READ_STATUS_IDENTIFIERS, + user=request.user, + book=book, + ) + .first() + ) + if current_read_status_shelfbook is not None: + if ( + current_read_status_shelfbook.shelf.identifier + != desired_shelf.identifier + ): + current_read_status_shelfbook.delete() + else: # It is already on the shelf + return redirect(request.headers.get("Referer", "/")) + + # create the new shelf-book entry + models.ShelfBook.objects.create( + book=book, shelf=desired_shelf, user=request.user + ) + else: + # we're putting it on a custom shelf + try: + models.ShelfBook.objects.create( + book=book, shelf=desired_shelf, user=request.user + ) + # The book is already on this shelf. + # Might be good to alert, or reject the action? + except IntegrityError: + pass + return redirect(request.headers.get("Referer", "/")) + + +@login_required +@require_POST +def unshelve(request): + """put a on a user's shelf""" + book = get_object_or_404(models.Edition, id=request.POST.get("book")) + shelf_book = get_object_or_404( + models.ShelfBook, book=book, shelf__id=request.POST["shelf"] + ) + shelf_book.raise_not_deletable(request.user) + + shelf_book.delete() + return redirect(request.headers.get("Referer", "/")) From d86ffc47a71763f63c4f3de0f2278c4dcbb521bc Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Wed, 20 Oct 2021 13:16:29 -0700 Subject: [PATCH 3/9] Fixes incorrect method comment --- bookwyrm/views/shelf/shelf_actions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bookwyrm/views/shelf/shelf_actions.py b/bookwyrm/views/shelf/shelf_actions.py index 470b2d3d3..702b72c13 100644 --- a/bookwyrm/views/shelf/shelf_actions.py +++ b/bookwyrm/views/shelf/shelf_actions.py @@ -92,7 +92,7 @@ def shelve(request): @login_required @require_POST def unshelve(request): - """put a on a user's shelf""" + """remove a book from a user's shelf""" book = get_object_or_404(models.Edition, id=request.POST.get("book")) shelf_book = get_object_or_404( models.ShelfBook, book=book, shelf__id=request.POST["shelf"] From 87deac17f9f23fef0a95c1417530d9d29dfe9bd2 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Wed, 20 Oct 2021 13:31:18 -0700 Subject: [PATCH 4/9] Make import link a link not a button --- bookwyrm/templates/shelf/shelf.html | 67 +++++++++++++++++------------ 1 file changed, 40 insertions(+), 27 deletions(-) diff --git a/bookwyrm/templates/shelf/shelf.html b/bookwyrm/templates/shelf/shelf.html index fa1e91f98..662d75073 100644 --- a/bookwyrm/templates/shelf/shelf.html +++ b/bookwyrm/templates/shelf/shelf.html @@ -19,45 +19,58 @@ -
- +
{% include 'shelf/create_shelf_form.html' with controls_text='create_shelf_form' %} From b64a616ff94859ae9c5f2071fb4de068c639bb35 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Wed, 20 Oct 2021 13:56:55 -0700 Subject: [PATCH 5/9] Fixes mock in test --- bookwyrm/tests/views/test_shelf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bookwyrm/tests/views/test_shelf.py b/bookwyrm/tests/views/test_shelf.py index b78e241cc..35cc63f21 100644 --- a/bookwyrm/tests/views/test_shelf.py +++ b/bookwyrm/tests/views/test_shelf.py @@ -52,7 +52,7 @@ class ShelfViews(TestCase): shelf = self.local_user.shelf_set.first() request = self.factory.get("") request.user = self.local_user - with patch("bookwyrm.views.shelf.is_api_request") as is_api: + with patch("bookwyrm.views.shelf.shelf.is_api_request") as is_api: is_api.return_value = False result = view(request, self.local_user.username, shelf.identifier) self.assertIsInstance(result, TemplateResponse) From 1bb23a8edf1d916a7f315cf127f8566678df8bd9 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Wed, 20 Oct 2021 14:15:05 -0700 Subject: [PATCH 6/9] Adds more tests of shelf views --- bookwyrm/tests/views/test_shelf.py | 45 ++++++++++++++++++++++++++++-- 1 file changed, 43 insertions(+), 2 deletions(-) diff --git a/bookwyrm/tests/views/test_shelf.py b/bookwyrm/tests/views/test_shelf.py index 35cc63f21..914f73973 100644 --- a/bookwyrm/tests/views/test_shelf.py +++ b/bookwyrm/tests/views/test_shelf.py @@ -2,6 +2,7 @@ import json from unittest.mock import patch +from django.contrib.auth.models import AnonymousUser from django.core.exceptions import PermissionDenied from django.template.response import TemplateResponse from django.test import TestCase @@ -46,6 +47,46 @@ class ShelfViews(TestCase): ) models.SiteSettings.objects.create() + self.anonymous_user = AnonymousUser + self.anonymous_user.is_authenticated = False + + def test_shelf_page_all_books(self, *_): + """there are so many views, this just makes sure it LOADS""" + view = views.Shelf.as_view() + request = self.factory.get("") + request.user = self.local_user + with patch("bookwyrm.views.shelf.shelf.is_api_request") as is_api: + is_api.return_value = False + result = view(request, self.local_user.username) + self.assertIsInstance(result, TemplateResponse) + validate_html(result.render()) + self.assertEqual(result.status_code, 200) + + def test_shelf_page_all_books_anonymous(self, *_): + """there are so many views, this just makes sure it LOADS""" + view = views.Shelf.as_view() + request = self.factory.get("") + request.user = self.anonymous_user + with patch("bookwyrm.views.shelf.shelf.is_api_request") as is_api: + is_api.return_value = False + result = view(request, self.local_user.username) + self.assertIsInstance(result, TemplateResponse) + validate_html(result.render()) + self.assertEqual(result.status_code, 200) + + def test_shelf_page_sorted(self, *_): + """there are so many views, this just makes sure it LOADS""" + view = views.Shelf.as_view() + shelf = self.local_user.shelf_set.first() + request = self.factory.get("", {"sort": "author"}) + request.user = self.local_user + with patch("bookwyrm.views.shelf.shelf.is_api_request") as is_api: + is_api.return_value = False + result = view(request, self.local_user.username, shelf.identifier) + self.assertIsInstance(result, TemplateResponse) + validate_html(result.render()) + self.assertEqual(result.status_code, 200) + def test_shelf_page(self, *_): """there are so many views, this just makes sure it LOADS""" view = views.Shelf.as_view() @@ -59,7 +100,7 @@ class ShelfViews(TestCase): validate_html(result.render()) self.assertEqual(result.status_code, 200) - with patch("bookwyrm.views.shelf.is_api_request") as is_api: + with patch("bookwyrm.views.shelf.shelf.is_api_request") as is_api: is_api.return_value = True result = view(request, self.local_user.username, shelf.identifier) self.assertIsInstance(result, ActivitypubResponse) @@ -67,7 +108,7 @@ class ShelfViews(TestCase): request = self.factory.get("/?page=1") request.user = self.local_user - with patch("bookwyrm.views.shelf.is_api_request") as is_api: + with patch("bookwyrm.views.shelf.shelf.is_api_request") as is_api: is_api.return_value = True result = view(request, self.local_user.username, shelf.identifier) self.assertIsInstance(result, ActivitypubResponse) From 3d92afdf280231a4d07a57bf6770f7c532ca929d Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Wed, 20 Oct 2021 14:16:13 -0700 Subject: [PATCH 7/9] Moves shelf tests into subdirectory --- bookwyrm/tests/views/shelf/__init__.py | 1 + bookwyrm/tests/views/{ => shelf}/test_shelf.py | 0 2 files changed, 1 insertion(+) create mode 100644 bookwyrm/tests/views/shelf/__init__.py rename bookwyrm/tests/views/{ => shelf}/test_shelf.py (100%) diff --git a/bookwyrm/tests/views/shelf/__init__.py b/bookwyrm/tests/views/shelf/__init__.py new file mode 100644 index 000000000..b6e690fd5 --- /dev/null +++ b/bookwyrm/tests/views/shelf/__init__.py @@ -0,0 +1 @@ +from . import * diff --git a/bookwyrm/tests/views/test_shelf.py b/bookwyrm/tests/views/shelf/test_shelf.py similarity index 100% rename from bookwyrm/tests/views/test_shelf.py rename to bookwyrm/tests/views/shelf/test_shelf.py From 5c2d6e651029ca597f4b3a5de9fc3801b9cf46b5 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Wed, 20 Oct 2021 14:30:11 -0700 Subject: [PATCH 8/9] Separate out test files and add more tests --- bookwyrm/tests/views/shelf/test_shelf.py | 153 +------------ .../tests/views/shelf/test_shelf_actions.py | 216 ++++++++++++++++++ 2 files changed, 217 insertions(+), 152 deletions(-) create mode 100644 bookwyrm/tests/views/shelf/test_shelf_actions.py diff --git a/bookwyrm/tests/views/shelf/test_shelf.py b/bookwyrm/tests/views/shelf/test_shelf.py index 914f73973..71df3631f 100644 --- a/bookwyrm/tests/views/shelf/test_shelf.py +++ b/bookwyrm/tests/views/shelf/test_shelf.py @@ -1,14 +1,12 @@ """ test for app action functionality """ -import json from unittest.mock import patch from django.contrib.auth.models import AnonymousUser -from django.core.exceptions import PermissionDenied from django.template.response import TemplateResponse from django.test import TestCase from django.test.client import RequestFactory -from bookwyrm import forms, models, views +from bookwyrm import models, views from bookwyrm.activitypub import ActivitypubResponse from bookwyrm.tests.validate_html import validate_html @@ -165,152 +163,3 @@ class ShelfViews(TestCase): view(request, request.user.username, shelf.identifier) self.assertEqual(shelf.name, "To Read") - - def test_shelve(self, *_): - """shelve a book""" - request = self.factory.post( - "", {"book": self.book.id, "shelf": self.shelf.identifier} - ) - request.user = self.local_user - with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay") as mock: - views.shelve(request) - - self.assertEqual(mock.call_count, 1) - activity = json.loads(mock.call_args[0][1]) - self.assertEqual(activity["type"], "Add") - - item = models.ShelfBook.objects.get() - self.assertEqual(activity["object"]["id"], item.remote_id) - # make sure the book is on the shelf - self.assertEqual(self.shelf.books.get(), self.book) - - def test_shelve_to_read(self, *_): - """special behavior for the to-read shelf""" - shelf = models.Shelf.objects.get(identifier="to-read") - request = self.factory.post( - "", {"book": self.book.id, "shelf": shelf.identifier} - ) - request.user = self.local_user - - with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): - views.shelve(request) - # make sure the book is on the shelf - self.assertEqual(shelf.books.get(), self.book) - - def test_shelve_reading(self, *_): - """special behavior for the reading shelf""" - shelf = models.Shelf.objects.get(identifier="reading") - request = self.factory.post( - "", {"book": self.book.id, "shelf": shelf.identifier} - ) - request.user = self.local_user - - with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): - views.shelve(request) - # make sure the book is on the shelf - self.assertEqual(shelf.books.get(), self.book) - - def test_shelve_read(self, *_): - """special behavior for the read shelf""" - shelf = models.Shelf.objects.get(identifier="read") - request = self.factory.post( - "", {"book": self.book.id, "shelf": shelf.identifier} - ) - request.user = self.local_user - - with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): - views.shelve(request) - # make sure the book is on the shelf - self.assertEqual(shelf.books.get(), self.book) - - def test_unshelve(self, *_): - """remove a book from a shelf""" - with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): - models.ShelfBook.objects.create( - book=self.book, user=self.local_user, shelf=self.shelf - ) - item = models.ShelfBook.objects.get() - - self.shelf.save() - self.assertEqual(self.shelf.books.count(), 1) - request = self.factory.post("", {"book": self.book.id, "shelf": self.shelf.id}) - request.user = self.local_user - with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay") as mock: - views.unshelve(request) - activity = json.loads(mock.call_args[0][1]) - self.assertEqual(activity["type"], "Remove") - self.assertEqual(activity["object"]["id"], item.remote_id) - self.assertEqual(self.shelf.books.count(), 0) - - def test_create_shelf(self, *_): - """a brand new custom shelf""" - form = forms.ShelfForm() - form.data["user"] = self.local_user.id - form.data["name"] = "new shelf name" - form.data["description"] = "desc" - form.data["privacy"] = "unlisted" - request = self.factory.post("", form.data) - request.user = self.local_user - - views.create_shelf(request) - - shelf = models.Shelf.objects.get(name="new shelf name") - self.assertEqual(shelf.privacy, "unlisted") - self.assertEqual(shelf.description, "desc") - self.assertEqual(shelf.user, self.local_user) - - def test_delete_shelf(self, *_): - """delete a brand new custom shelf""" - request = self.factory.post("") - request.user = self.local_user - shelf_id = self.shelf.id - - views.delete_shelf(request, shelf_id) - - self.assertFalse(models.Shelf.objects.filter(id=shelf_id).exists()) - - def test_delete_shelf_unauthorized(self, *_): - """delete a brand new custom shelf""" - with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"), patch( - "bookwyrm.activitystreams.populate_stream_task.delay" - ): - rat = models.User.objects.create_user( - "rat@local.com", - "rat@mouse.mouse", - "password", - local=True, - localname="rat", - ) - request = self.factory.post("") - request.user = rat - - with self.assertRaises(PermissionDenied): - views.delete_shelf(request, self.shelf.id) - - self.assertTrue(models.Shelf.objects.filter(id=self.shelf.id).exists()) - - def test_delete_shelf_has_book(self, *_): - """delete a brand new custom shelf""" - with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): - models.ShelfBook.objects.create( - book=self.book, user=self.local_user, shelf=self.shelf - ) - request = self.factory.post("") - request.user = self.local_user - - with self.assertRaises(PermissionDenied): - views.delete_shelf(request, self.shelf.id) - - self.assertTrue(models.Shelf.objects.filter(id=self.shelf.id).exists()) - - def test_delete_shelf_not_editable(self, *_): - """delete a brand new custom shelf""" - shelf = self.local_user.shelf_set.first() - self.assertFalse(shelf.editable) - request = self.factory.post("") - request.user = self.local_user - - with self.assertRaises(PermissionDenied): - views.delete_shelf(request, shelf.id) - - self.assertTrue(models.Shelf.objects.filter(id=shelf.id).exists()) diff --git a/bookwyrm/tests/views/shelf/test_shelf_actions.py b/bookwyrm/tests/views/shelf/test_shelf_actions.py new file mode 100644 index 000000000..50b319d0e --- /dev/null +++ b/bookwyrm/tests/views/shelf/test_shelf_actions.py @@ -0,0 +1,216 @@ +""" test for app action functionality """ +import json +from unittest.mock import patch + +from django.core.exceptions import PermissionDenied +from django.test import TestCase +from django.test.client import RequestFactory + +from bookwyrm import forms, models, views + + +@patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay") +@patch("bookwyrm.suggested_users.rerank_suggestions_task.delay") +@patch("bookwyrm.activitystreams.populate_stream_task.delay") +@patch("bookwyrm.activitystreams.add_book_statuses_task.delay") +@patch("bookwyrm.activitystreams.remove_book_statuses_task.delay") +class ShelfActionViews(TestCase): + """tag views""" + + def setUp(self): + """we need basic test data and mocks""" + self.factory = RequestFactory() + with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"), patch( + "bookwyrm.activitystreams.populate_stream_task.delay" + ): + 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", + ) + self.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=self.work, + ) + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): + self.shelf = models.Shelf.objects.create( + name="Test Shelf", identifier="test-shelf", user=self.local_user + ) + models.SiteSettings.objects.create() + + def test_shelve(self, *_): + """shelve a book""" + request = self.factory.post( + "", {"book": self.book.id, "shelf": self.shelf.identifier} + ) + request.user = self.local_user + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay") as mock: + views.shelve(request) + + self.assertEqual(mock.call_count, 1) + activity = json.loads(mock.call_args[0][1]) + self.assertEqual(activity["type"], "Add") + + item = models.ShelfBook.objects.get() + self.assertEqual(activity["object"]["id"], item.remote_id) + # make sure the book is on the shelf + self.assertEqual(self.shelf.books.get(), self.book) + + def test_shelve_to_read(self, *_): + """special behavior for the to-read shelf""" + shelf = models.Shelf.objects.get(identifier="to-read") + request = self.factory.post( + "", {"book": self.book.id, "shelf": shelf.identifier} + ) + request.user = self.local_user + + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): + views.shelve(request) + # make sure the book is on the shelf + self.assertEqual(shelf.books.get(), self.book) + + def test_shelve_reading(self, *_): + """special behavior for the reading shelf""" + shelf = models.Shelf.objects.get(identifier="reading") + request = self.factory.post( + "", {"book": self.book.id, "shelf": shelf.identifier} + ) + request.user = self.local_user + + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): + views.shelve(request) + # make sure the book is on the shelf + self.assertEqual(shelf.books.get(), self.book) + + def test_shelve_read(self, *_): + """special behavior for the read shelf""" + shelf = models.Shelf.objects.get(identifier="read") + request = self.factory.post( + "", {"book": self.book.id, "shelf": shelf.identifier} + ) + request.user = self.local_user + + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): + views.shelve(request) + # make sure the book is on the shelf + self.assertEqual(shelf.books.get(), self.book) + + def test_shelve_read_with_change_shelf(self, *_): + """special behavior for the read shelf""" + previous_shelf = models.Shelf.objects.get(identifier="reading") + models.ShelfBook.objects.create( + shelf=previous_shelf, user=self.local_user, book=self.book + ) + shelf = models.Shelf.objects.get(identifier="read") + + request = self.factory.post( + "", { + "book": self.book.id, + "shelf": shelf.identifier, + "change-shelf-from": previous_shelf.identifier, + } + ) + request.user = self.local_user + + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): + views.shelve(request) + # make sure the book is on the shelf + self.assertEqual(shelf.books.get(), self.book) + self.assertEqual(list(previous_shelf.books.all()), []) + + def test_unshelve(self, *_): + """remove a book from a shelf""" + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): + models.ShelfBook.objects.create( + book=self.book, user=self.local_user, shelf=self.shelf + ) + item = models.ShelfBook.objects.get() + + self.shelf.save() + self.assertEqual(self.shelf.books.count(), 1) + request = self.factory.post("", {"book": self.book.id, "shelf": self.shelf.id}) + request.user = self.local_user + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay") as mock: + views.unshelve(request) + activity = json.loads(mock.call_args[0][1]) + self.assertEqual(activity["type"], "Remove") + self.assertEqual(activity["object"]["id"], item.remote_id) + self.assertEqual(self.shelf.books.count(), 0) + + def test_create_shelf(self, *_): + """a brand new custom shelf""" + form = forms.ShelfForm() + form.data["user"] = self.local_user.id + form.data["name"] = "new shelf name" + form.data["description"] = "desc" + form.data["privacy"] = "unlisted" + request = self.factory.post("", form.data) + request.user = self.local_user + + views.create_shelf(request) + + shelf = models.Shelf.objects.get(name="new shelf name") + self.assertEqual(shelf.privacy, "unlisted") + self.assertEqual(shelf.description, "desc") + self.assertEqual(shelf.user, self.local_user) + + def test_delete_shelf(self, *_): + """delete a brand new custom shelf""" + request = self.factory.post("") + request.user = self.local_user + shelf_id = self.shelf.id + + views.delete_shelf(request, shelf_id) + + self.assertFalse(models.Shelf.objects.filter(id=shelf_id).exists()) + + def test_delete_shelf_unauthorized(self, *_): + """delete a brand new custom shelf""" + with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"), patch( + "bookwyrm.activitystreams.populate_stream_task.delay" + ): + rat = models.User.objects.create_user( + "rat@local.com", + "rat@mouse.mouse", + "password", + local=True, + localname="rat", + ) + request = self.factory.post("") + request.user = rat + + with self.assertRaises(PermissionDenied): + views.delete_shelf(request, self.shelf.id) + + self.assertTrue(models.Shelf.objects.filter(id=self.shelf.id).exists()) + + def test_delete_shelf_has_book(self, *_): + """delete a brand new custom shelf""" + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): + models.ShelfBook.objects.create( + book=self.book, user=self.local_user, shelf=self.shelf + ) + request = self.factory.post("") + request.user = self.local_user + + with self.assertRaises(PermissionDenied): + views.delete_shelf(request, self.shelf.id) + + self.assertTrue(models.Shelf.objects.filter(id=self.shelf.id).exists()) + + def test_delete_shelf_not_editable(self, *_): + """delete a brand new custom shelf""" + shelf = self.local_user.shelf_set.first() + self.assertFalse(shelf.editable) + request = self.factory.post("") + request.user = self.local_user + + with self.assertRaises(PermissionDenied): + views.delete_shelf(request, shelf.id) + + self.assertTrue(models.Shelf.objects.filter(id=shelf.id).exists()) From f65a54eb4abe8b5f0b089f1801fb598faa96e2ed Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Wed, 20 Oct 2021 14:34:42 -0700 Subject: [PATCH 9/9] Python formatting --- bookwyrm/tests/views/shelf/test_shelf_actions.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/bookwyrm/tests/views/shelf/test_shelf_actions.py b/bookwyrm/tests/views/shelf/test_shelf_actions.py index 50b319d0e..3efae0f45 100644 --- a/bookwyrm/tests/views/shelf/test_shelf_actions.py +++ b/bookwyrm/tests/views/shelf/test_shelf_actions.py @@ -109,11 +109,12 @@ class ShelfActionViews(TestCase): shelf = models.Shelf.objects.get(identifier="read") request = self.factory.post( - "", { + "", + { "book": self.book.id, "shelf": shelf.identifier, "change-shelf-from": previous_shelf.identifier, - } + }, ) request.user = self.local_user