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 self.collection_queryset, **kwargs
).serialize() ).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): class CollectionItemMixin(ActivitypubMixin):
"""for items that are part of an (Ordered)Collection""" """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 %} {% load i18n %}
{% block panel %} {% 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 %}"> <form name="edit-list" method="post" action="{% url 'list' list.id %}">
{% include 'lists/form.html' %} {% include 'lists/form.html' %}
</form> </form>
{% include "lists/delete_list_modal.html" with controls_text="delete_list" controls_uid=list.id %}
{% endblock %} {% endblock %}

View file

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

View file

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

View file

@ -40,14 +40,6 @@ class InboxActivities(TestCase):
remote_id="https://example.com/status/1", 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() models.SiteSettings.objects.create()
def test_delete_status(self): def test_delete_status(self):
@ -137,3 +129,30 @@ class InboxActivities(TestCase):
# nothing happens. # nothing happens.
views.inbox.activity_task(activity) views.inbox.activity_task(activity)
self.assertEqual(models.User.objects.filter(is_active=True).count(), 2) 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 unittest.mock import patch
from django.contrib.auth.models import AnonymousUser from django.contrib.auth.models import AnonymousUser
from django.core.exceptions import PermissionDenied
from django.test import TestCase from django.test import TestCase
from django.test.client import RequestFactory from django.test.client import RequestFactory
@ -66,6 +67,44 @@ class ListActionViews(TestCase):
self.anonymous_user.is_authenticated = False self.anonymous_user.is_authenticated = False
models.SiteSettings.objects.create() 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): def test_curate_approve(self):
"""approve a pending item""" """approve a pending item"""
view = views.Curate.as_view() 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/?$", views.Lists.as_view(), name="lists"),
re_path(r"^list/saved/?$", views.SavedLists.as_view(), name="saved-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/(?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/add-book/?$", views.list.add_book, name="list-add-book"),
re_path( re_path(
r"^list/(?P<list_id>\d+)/remove/?$", r"^list/(?P<list_id>\d+)/remove/?$",

View file

@ -27,6 +27,7 @@ from .isbn import Isbn
from .landing import About, Home, Landing from .landing import About, Home, Landing
from .list import Lists, SavedLists, List, Curate, UserLists from .list import Lists, SavedLists, List, Curate, UserLists
from .list import save_list, unsave_list from .list import save_list, unsave_list
from .list import delete_list
from .notifications import Notifications from .notifications import Notifications
from .outbox import Outbox from .outbox import Outbox
from .reading import edit_readthrough, create_readthrough from .reading import edit_readthrough, create_readthrough

View file

@ -3,6 +3,7 @@ from typing import Optional
from urllib.parse import urlencode from urllib.parse import urlencode
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.core.exceptions import PermissionDenied
from django.core.paginator import Paginator from django.core.paginator import Paginator
from django.db import IntegrityError, transaction from django.db import IntegrityError, transaction
from django.db.models import Avg, Count, DecimalField, Q, Max 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) 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 @require_POST
@login_required @login_required
def add_book(request): def add_book(request):