diff --git a/bookwyrm/models/activitypub_mixin.py b/bookwyrm/models/activitypub_mixin.py index 4e313723..f287b752 100644 --- a/bookwyrm/models/activitypub_mixin.py +++ b/bookwyrm/models/activitypub_mixin.py @@ -362,6 +362,13 @@ class OrderedCollectionMixin(OrderedCollectionPageMixin): self.collection_queryset, **kwargs ).serialize() + def delete(self, *args, broadcast=True, **kwargs): + """Delete the object""" + activity = self.to_delete_activity(self.user) + super().delete(*args, **kwargs) + if self.user.local and broadcast: + self.broadcast(activity, self.user) + class CollectionItemMixin(ActivitypubMixin): """for items that are part of an (Ordered)Collection""" diff --git a/bookwyrm/templates/lists/curate.html b/bookwyrm/templates/lists/curate.html index 661410e8..638d86b8 100644 --- a/bookwyrm/templates/lists/curate.html +++ b/bookwyrm/templates/lists/curate.html @@ -1,4 +1,4 @@ -{% extends 'lists/list_layout.html' %} +{% extends 'lists/layout.html' %} {% load i18n %} {% block panel %} diff --git a/bookwyrm/templates/lists/delete_list_modal.html b/bookwyrm/templates/lists/delete_list_modal.html new file mode 100644 index 00000000..ee7a3393 --- /dev/null +++ b/bookwyrm/templates/lists/delete_list_modal.html @@ -0,0 +1,21 @@ +{% extends 'components/modal.html' %} +{% load i18n %} + +{% block modal-title %}{% trans "Delete this list?" %}{% endblock %} + +{% block modal-body %} +{% trans "This action cannot be un-done" %} +{% endblock %} + +{% block modal-footer %} +
+ {% csrf_token %} + + + {% trans "Cancel" as button_text %} + {% include 'snippets/toggle/toggle_button.html' with text=button_text controls_text="delete_list" controls_uid=list.id %} +
+{% endblock %} + diff --git a/bookwyrm/templates/lists/edit_form.html b/bookwyrm/templates/lists/edit_form.html index 917e5e2a..def47f06 100644 --- a/bookwyrm/templates/lists/edit_form.html +++ b/bookwyrm/templates/lists/edit_form.html @@ -9,4 +9,5 @@
{% include 'lists/form.html' %}
+{% include "lists/delete_list_modal.html" with controls_text="delete_list" controls_uid=list.id %} {% endblock %} diff --git a/bookwyrm/templates/lists/form.html b/bookwyrm/templates/lists/form.html index e5eb9c37..4252d709 100644 --- a/bookwyrm/templates/lists/form.html +++ b/bookwyrm/templates/lists/form.html @@ -3,7 +3,7 @@
-
+
{{ list_form.name }} @@ -34,12 +34,19 @@
-
-
- {% include 'snippets/privacy_select.html' with current=list.privacy %} +
+
+
+
+ {% include 'snippets/privacy_select.html' with current=list.privacy %} +
+
+ +
+
-
- +
+ {% trans "Delete list" as button_text %} + {% include 'snippets/toggle/toggle_button.html' with class="is-danger" text=button_text icon_with_text="x" controls_text="delete_list" controls_uid=list.id focus="modal_title_delete_list" %}
- diff --git a/bookwyrm/templates/lists/list_layout.html b/bookwyrm/templates/lists/layout.html similarity index 100% rename from bookwyrm/templates/lists/list_layout.html rename to bookwyrm/templates/lists/layout.html diff --git a/bookwyrm/templates/lists/list.html b/bookwyrm/templates/lists/list.html index 37f7079b..35014a7b 100644 --- a/bookwyrm/templates/lists/list.html +++ b/bookwyrm/templates/lists/list.html @@ -1,4 +1,4 @@ -{% extends 'lists/list_layout.html' %} +{% extends 'lists/layout.html' %} {% load i18n %} {% load bookwyrm_tags %} {% load markdown %} diff --git a/bookwyrm/tests/views/inbox/test_inbox_delete.py b/bookwyrm/tests/views/inbox/test_inbox_delete.py index 1566c05a..f47d8737 100644 --- a/bookwyrm/tests/views/inbox/test_inbox_delete.py +++ b/bookwyrm/tests/views/inbox/test_inbox_delete.py @@ -40,14 +40,6 @@ class InboxActivities(TestCase): remote_id="https://example.com/status/1", ) - self.create_json = { - "id": "hi", - "type": "Create", - "actor": "hi", - "to": ["https://www.w3.org/ns/activitystreams#public"], - "cc": ["https://example.com/user/mouse/followers"], - "object": {}, - } models.SiteSettings.objects.create() def test_delete_status(self): @@ -137,3 +129,30 @@ class InboxActivities(TestCase): # nothing happens. views.inbox.activity_task(activity) self.assertEqual(models.User.objects.filter(is_active=True).count(), 2) + + def test_delete_list(self): + """delete a list""" + book_list = models.List.objects.create( + name="test list", + user=self.remote_user, + remote_id="https://example.com/list/1", + ) + activity = { + "@context": "https://www.w3.org/ns/activitystreams", + "id": "https://example.com/users/test-user#delete", + "type": "Delete", + "actor": "https://example.com/users/test-user", + "to": ["https://www.w3.org/ns/activitystreams#Public"], + "object": { + "id": book_list.remote_id, + "owner": self.remote_user.remote_id, + "type": "BookList", + "totalItems": 0, + "first": "", + "name": "test list", + "to": [], + "cc": [], + }, + } + views.inbox.activity_task(activity) + self.assertFalse(models.List.objects.exists()) diff --git a/bookwyrm/tests/views/test_list_actions.py b/bookwyrm/tests/views/test_list_actions.py index 2339427c..84c5540c 100644 --- a/bookwyrm/tests/views/test_list_actions.py +++ b/bookwyrm/tests/views/test_list_actions.py @@ -3,6 +3,7 @@ import json from unittest.mock import patch from django.contrib.auth.models import AnonymousUser +from django.core.exceptions import PermissionDenied from django.test import TestCase from django.test.client import RequestFactory @@ -66,6 +67,44 @@ class ListActionViews(TestCase): self.anonymous_user.is_authenticated = False models.SiteSettings.objects.create() + def test_delete_list(self): + """delete an entire list""" + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): + models.ListItem.objects.create( + book_list=self.list, + user=self.local_user, + book=self.book, + approved=True, + order=1, + ) + models.ListItem.objects.create( + book_list=self.list, + user=self.local_user, + book=self.book_two, + approved=False, + order=2, + ) + request = self.factory.post("") + request.user = self.local_user + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay") as mock: + views.delete_list(request, self.list.id) + activity = json.loads(mock.call_args[0][1]) + self.assertEqual(activity["type"], "Delete") + self.assertEqual(activity["actor"], self.local_user.remote_id) + self.assertEqual(activity["object"]["id"], self.list.remote_id) + self.assertEqual(activity["object"]["type"], "BookList") + + self.assertEqual(mock.call_count, 1) + self.assertFalse(models.List.objects.exists()) + self.assertFalse(models.ListItem.objects.exists()) + + def test_delete_list_permission_denied(self): + """delete an entire list""" + request = self.factory.post("") + request.user = self.rat + with self.assertRaises(PermissionDenied): + views.delete_list(request, self.list.id) + def test_curate_approve(self): """approve a pending item""" view = views.Curate.as_view() diff --git a/bookwyrm/urls.py b/bookwyrm/urls.py index 1dbd67a7..991114fa 100644 --- a/bookwyrm/urls.py +++ b/bookwyrm/urls.py @@ -220,6 +220,7 @@ urlpatterns = [ re_path(r"^list/?$", views.Lists.as_view(), name="lists"), re_path(r"^list/saved/?$", views.SavedLists.as_view(), name="saved-lists"), re_path(r"^list/(?P\d+)(.json)?/?$", views.List.as_view(), name="list"), + re_path(r"^list/delete/(?P\d+)/?$", views.delete_list, name="delete-list"), re_path(r"^list/add-book/?$", views.list.add_book, name="list-add-book"), re_path( r"^list/(?P\d+)/remove/?$", diff --git a/bookwyrm/views/__init__.py b/bookwyrm/views/__init__.py index f4204925..ca52800c 100644 --- a/bookwyrm/views/__init__.py +++ b/bookwyrm/views/__init__.py @@ -27,6 +27,7 @@ from .isbn import Isbn from .landing import About, Home, Landing from .list import Lists, SavedLists, List, Curate, UserLists from .list import save_list, unsave_list +from .list import delete_list from .notifications import Notifications from .outbox import Outbox from .reading import edit_readthrough, create_readthrough diff --git a/bookwyrm/views/list.py b/bookwyrm/views/list.py index e6ef52ba..af99e9f5 100644 --- a/bookwyrm/views/list.py +++ b/bookwyrm/views/list.py @@ -3,6 +3,7 @@ from typing import Optional from urllib.parse import urlencode from django.contrib.auth.decorators import login_required +from django.core.exceptions import PermissionDenied from django.core.paginator import Paginator from django.db import IntegrityError, transaction from django.db.models import Avg, Count, DecimalField, Q, Max @@ -260,6 +261,20 @@ def unsave_list(request, list_id): return redirect("list", list_id) +@require_POST +@login_required +def delete_list(request, list_id): + """delete a list""" + book_list = get_object_or_404(models.List, id=list_id) + + # only the owner or a moderator can delete a list + if book_list.user != request.user and not request.user.has_perm("moderate_post"): + raise PermissionDenied + + book_list.delete() + return redirect("lists") + + @require_POST @login_required def add_book(request):