Merge pull request #1369 from bookwyrm-social/delete-lists

Delete lists
This commit is contained in:
Mouse Reeve 2021-09-06 13:16:56 -07:00 committed by GitHub
commit aa856b0155
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 128 additions and 17 deletions

View file

@ -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"""

View file

@ -1,4 +1,4 @@
{% extends 'lists/list_layout.html' %}
{% extends 'lists/layout.html' %}
{% load i18n %}
{% block panel %}

View file

@ -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 %}
<form name="delete-list-{{ list.id }}" action="{% url 'delete-list' list.id %}" method="POST">
{% csrf_token %}
<input type="hidden" name="id" value="{{ list.id }}">
<button class="button is-danger" type="submit">
{% trans "Delete" %}
</button>
{% trans "Cancel" as button_text %}
{% include 'snippets/toggle/toggle_button.html' with text=button_text controls_text="delete_list" controls_uid=list.id %}
</form>
{% endblock %}

View file

@ -9,4 +9,5 @@
<form name="edit-list" method="post" action="{% url 'list' list.id %}">
{% include 'lists/form.html' %}
</form>
{% include "lists/delete_list_modal.html" with controls_text="delete_list" controls_uid=list.id %}
{% endblock %}

View file

@ -3,7 +3,7 @@
<input type="hidden" name="user" value="{{ request.user.id }}">
<div class="columns">
<div class="column">
<div class="column is-two-thirds">
<div class="field">
<label class="label" for="id_name">{% trans "Name:" %}</label>
{{ list_form.name }}
@ -34,12 +34,19 @@
</fieldset>
</div>
</div>
<div class="field has-addons">
<div class="control">
{% include 'snippets/privacy_select.html' with current=list.privacy %}
<div class="columns is-mobile">
<div class="column">
<div class="field has-addons">
<div class="control">
{% include 'snippets/privacy_select.html' with current=list.privacy %}
</div>
<div class="control">
<button type="submit" class="button is-primary">{% trans "Save" %}</button>
</div>
</div>
</div>
<div class="control">
<button type="submit" class="button is-primary">{% trans "Save" %}</button>
<div class="column is-narrow">
{% 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" %}
</div>
</div>

View file

@ -1,4 +1,4 @@
{% extends 'lists/list_layout.html' %}
{% extends 'lists/layout.html' %}
{% load i18n %}
{% load bookwyrm_tags %}
{% load markdown %}

View file

@ -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())

View file

@ -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()

View file

@ -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<list_id>\d+)(.json)?/?$", views.List.as_view(), name="list"),
re_path(r"^list/delete/(?P<list_id>\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<list_id>\d+)/remove/?$",

View file

@ -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

View file

@ -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):