From 704e1092c42b5905e6cb8631274ed20a5d58b538 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Thu, 8 Oct 2020 12:32:45 -0700 Subject: [PATCH 1/7] Delete statuses --- bookwyrm/activitypub/__init__.py | 1 + bookwyrm/activitypub/note.py | 8 ++++++++ bookwyrm/models/status.py | 16 +++++++++++++++- bookwyrm/outgoing.py | 7 +++++++ bookwyrm/status.py | 5 +++++ bookwyrm/templates/snippets/status.html | 11 +++++++++++ bookwyrm/urls.py | 4 +++- bookwyrm/view_actions.py | 21 +++++++++++++++++++++ 8 files changed, 71 insertions(+), 2 deletions(-) diff --git a/bookwyrm/activitypub/__init__.py b/bookwyrm/activitypub/__init__.py index 03c714a6..45cd42a5 100644 --- a/bookwyrm/activitypub/__init__.py +++ b/bookwyrm/activitypub/__init__.py @@ -4,6 +4,7 @@ import sys from .base_activity import ActivityEncoder, Image, PublicKey, Signature from .note import Note, GeneratedNote, Article, Comment, Review, Quotation +from .note import Tombstone from .interaction import Boost, Like from .ordered_collection import OrderedCollection, OrderedCollectionPage from .person import Person diff --git a/bookwyrm/activitypub/note.py b/bookwyrm/activitypub/note.py index 63ac8a6e..54730fb6 100644 --- a/bookwyrm/activitypub/note.py +++ b/bookwyrm/activitypub/note.py @@ -4,6 +4,14 @@ from typing import Dict, List from .base_activity import ActivityObject, Image +@dataclass(init=False) +class Tombstone(ActivityObject): + url: str + published: str + deleted: str + type: str = 'Tombstone' + + @dataclass(init=False) class Note(ActivityObject): ''' Note activity ''' diff --git a/bookwyrm/models/status.py b/bookwyrm/models/status.py index 6c3369f2..f9f90467 100644 --- a/bookwyrm/models/status.py +++ b/bookwyrm/models/status.py @@ -22,6 +22,8 @@ class Status(OrderedCollectionPageMixin, BookWyrmModel): sensitive = models.BooleanField(default=False) # the created date can't be this, because of receiving federated posts published_date = models.DateTimeField(default=timezone.now) + deleted = models.BooleanField(default=False) + deleted_date = models.DateTimeField(default=timezone.now) favorites = models.ManyToManyField( 'User', symmetrical=False, @@ -104,6 +106,18 @@ class Status(OrderedCollectionPageMixin, BookWyrmModel): **kwargs ) + def to_activity(self, **kwargs): + ''' return tombstone if the status is deleted ''' + if self.deleted: + return activitypub.Tombstone( + id=self.remote_id, + url=self.remote_id, + deleted=http_date(self.deleted_date.timestamp()), + published=http_date(self.deleted_date.timestamp()), + ).serialize() + return ActivitypubMixin.to_activity(self, **kwargs) + + class GeneratedStatus(Status): ''' these are app-generated messages about user activity ''' @property @@ -112,7 +126,7 @@ class GeneratedStatus(Status): message = self.content books = ', '.join( '"%s"' % (self.book.local_id, self.book.title) \ - for book in self.mention_books + for book in self.mention_books.all() ) return '%s %s' % (message, books) diff --git a/bookwyrm/outgoing.py b/bookwyrm/outgoing.py index 25a61c46..92187ffa 100644 --- a/bookwyrm/outgoing.py +++ b/bookwyrm/outgoing.py @@ -13,6 +13,7 @@ from bookwyrm.status import create_review, create_status from bookwyrm.status import create_quotation, create_comment from bookwyrm.status import create_tag, create_notification, create_rating from bookwyrm.status import create_generated_note +from bookwyrm.status import delete_status from bookwyrm.remote_user import get_or_create_remote_user @@ -197,6 +198,12 @@ def handle_import_books(user, items): return None +def handle_delete_status(user, status): + ''' delete a status and broadcast deletion to other servers ''' + delete_status(status) + broadcast(user, status.to_activity()) + + def handle_rate(user, book, rating): ''' a review that's just a rating ''' builder = create_rating diff --git a/bookwyrm/status.py b/bookwyrm/status.py index 0c13638e..190f5dd7 100644 --- a/bookwyrm/status.py +++ b/bookwyrm/status.py @@ -6,6 +6,11 @@ from bookwyrm.books_manager import get_or_create_book from bookwyrm.sanitize_html import InputHtmlParser +def delete_status(status): + ''' replace the status with a tombstone ''' + status.deleted = True + status.save() + def create_rating(user, book, rating): ''' a review that's just a rating ''' if not rating or rating < 1 or rating > 5: diff --git a/bookwyrm/templates/snippets/status.html b/bookwyrm/templates/snippets/status.html index f1fab742..809cbe9e 100644 --- a/bookwyrm/templates/snippets/status.html +++ b/bookwyrm/templates/snippets/status.html @@ -25,6 +25,17 @@ Public post + {% if status.user == request.user %} +
+ {% csrf_token %} + + +
+ {% endif %} {{ status.published_date | naturaltime }} diff --git a/bookwyrm/urls.py b/bookwyrm/urls.py index f1b33877..331efee5 100644 --- a/bookwyrm/urls.py +++ b/bookwyrm/urls.py @@ -11,7 +11,7 @@ localname_regex = r'(?P[\w\-_]+)' user_path = r'^user/%s' % username_regex local_user_path = r'^user/%s' % localname_regex -status_types = ['status', 'review', 'comment', 'quotation', 'boost'] +status_types = ['status', 'review', 'comment', 'quotation', 'boost', 'generatedstatus'] status_path = r'%s/(%s)/(?P\d+)' % \ (local_user_path, '|'.join(status_types)) @@ -107,6 +107,8 @@ urlpatterns = [ re_path(r'^unfavorite/(?P\d+)/?$', actions.unfavorite), re_path(r'^boost/(?P\d+)/?$', actions.boost), + re_path(r'^delete-status/?$', actions.delete_status), + re_path(r'^shelve/?$', actions.shelve), re_path(r'^follow/?$', actions.follow), diff --git a/bookwyrm/view_actions.py b/bookwyrm/view_actions.py index 992a270d..e7674bb9 100644 --- a/bookwyrm/view_actions.py +++ b/bookwyrm/view_actions.py @@ -418,6 +418,27 @@ def boost(request, status_id): outgoing.handle_boost(request.user, status) return redirect(request.headers.get('Referer', '/')) + +@login_required +def delete_status(request): + ''' delete and tombstone a status ''' + status_id = request.POST.get('status') + if not status_id: + return HttpResponseBadRequest() + try: + status = models.Status.objects.get(id=status_id) + except models.Status.DoesNotExist: + return HttpResponseBadRequest() + + # don't let people delete other people's statuses + if status.user != request.user: + return HttpResponseBadRequest() + + # perform deletion + outgoing.handle_delete_status(request.user, status) + return redirect(request.headers.get('Referer', '/')) + + @login_required def follow(request): ''' follow another user, here or abroad ''' From 48df06aea79a8b2718eec9ff6cb45348510550a9 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Thu, 8 Oct 2020 12:35:27 -0700 Subject: [PATCH 2/7] Filter out deleted statuses in feed --- bookwyrm/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bookwyrm/views.py b/bookwyrm/views.py index 2bc840c0..f020d287 100644 --- a/bookwyrm/views.py +++ b/bookwyrm/views.py @@ -117,7 +117,7 @@ def get_activity_feed(user, filter_level, model=models.Status): activities = model if hasattr(model, 'objects'): - activities = model.objects + activities = model.objects.filter(deleted=False) activities = activities.order_by( '-created_date' From 0d614c7ebb394c5233a57223796ce51fb1b23309 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Thu, 8 Oct 2020 12:38:06 -0700 Subject: [PATCH 3/7] Don't show deleted statuses --- bookwyrm/templates/snippets/status.html | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/bookwyrm/templates/snippets/status.html b/bookwyrm/templates/snippets/status.html index 809cbe9e..5c570e0d 100644 --- a/bookwyrm/templates/snippets/status.html +++ b/bookwyrm/templates/snippets/status.html @@ -1,6 +1,7 @@ {% load humanize %} {% load fr_display %} +{% if not status.deleted %}
{% include 'snippets/status_header.html' with status=status %} @@ -40,3 +41,14 @@
+{% else %} +
+
+

+ {% include 'snippets/avatar.html' with user=status.user %} + {% include 'snippets/username.html' with user=status.user %} + deleted this status +

+
+
+{% endif %} From 10a0a6ac3776fb34015a3235ba61fcea97a37a2d Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Thu, 8 Oct 2020 12:40:47 -0700 Subject: [PATCH 4/7] hide deleted statuses from threads --- bookwyrm/templatetags/fr_display.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bookwyrm/templatetags/fr_display.py b/bookwyrm/templatetags/fr_display.py index 818eae2a..cb4ee419 100644 --- a/bookwyrm/templatetags/fr_display.py +++ b/bookwyrm/templatetags/fr_display.py @@ -42,7 +42,8 @@ def get_replies(status): ''' get all direct replies to a status ''' #TODO: this limit could cause problems return models.Status.objects.filter( - reply_parent=status + reply_parent=status, + deleted=False, ).select_subclasses().all()[:10] From d689b6e7c44fe0eb3733d4071a5b619503077b6f Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Wed, 14 Oct 2020 08:38:51 -0700 Subject: [PATCH 5/7] Adds Delete verb --- bookwyrm/activitypub/__init__.py | 2 +- bookwyrm/activitypub/verbs.py | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/bookwyrm/activitypub/__init__.py b/bookwyrm/activitypub/__init__.py index 45cd42a5..446455fa 100644 --- a/bookwyrm/activitypub/__init__.py +++ b/bookwyrm/activitypub/__init__.py @@ -9,7 +9,7 @@ from .interaction import Boost, Like from .ordered_collection import OrderedCollection, OrderedCollectionPage from .person import Person from .book import Edition, Work, Author -from .verbs import Create, Undo, Update +from .verbs import Create, Delete, Undo, Update from .verbs import Follow, Accept, Reject from .verbs import Add, Remove diff --git a/bookwyrm/activitypub/verbs.py b/bookwyrm/activitypub/verbs.py index 1ae106b0..bd6d882d 100644 --- a/bookwyrm/activitypub/verbs.py +++ b/bookwyrm/activitypub/verbs.py @@ -21,6 +21,15 @@ class Create(Verb): type: str = 'Create' +@dataclass(init=False) +class Delete(Verb): + ''' Create activity ''' + to: List + cc: List + signature: Signature + type: str = 'Delete' + + @dataclass(init=False) class Update(Verb): ''' Update activity ''' From 22410e3f479f8f4b61256344f56e4c47171f8e07 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Wed, 14 Oct 2020 09:20:36 -0700 Subject: [PATCH 6/7] Adds deleted database fields to Status --- .../migrations/0053_auto_20201006_2020.py | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 bookwyrm/migrations/0053_auto_20201006_2020.py diff --git a/bookwyrm/migrations/0053_auto_20201006_2020.py b/bookwyrm/migrations/0053_auto_20201006_2020.py new file mode 100644 index 00000000..515fc446 --- /dev/null +++ b/bookwyrm/migrations/0053_auto_20201006_2020.py @@ -0,0 +1,24 @@ +# Generated by Django 3.0.7 on 2020-10-06 20:20 + +from django.db import migrations, models +import django.utils.timezone + + +class Migration(migrations.Migration): + + dependencies = [ + ('bookwyrm', '0052_auto_20201005_2145'), + ] + + operations = [ + migrations.AddField( + model_name='status', + name='deleted', + field=models.BooleanField(default=False), + ), + migrations.AddField( + model_name='status', + name='deleted_date', + field=models.DateTimeField(default=django.utils.timezone.now), + ), + ] From 7f579ffefa94e38968788042c5e2df376d9d7787 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Fri, 16 Oct 2020 17:00:10 -0700 Subject: [PATCH 7/7] Read incoming deletion activities --- bookwyrm/incoming.py | 15 +++++++++++++++ bookwyrm/migrations/0054_auto_20201016_2359.py | 18 ++++++++++++++++++ bookwyrm/models/status.py | 2 +- bookwyrm/status.py | 2 ++ 4 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 bookwyrm/migrations/0054_auto_20201016_2359.py diff --git a/bookwyrm/incoming.py b/bookwyrm/incoming.py index 54e2fb24..d5cfc36b 100644 --- a/bookwyrm/incoming.py +++ b/bookwyrm/incoming.py @@ -57,6 +57,7 @@ def shared_inbox(request): 'Accept': handle_follow_accept, 'Reject': handle_follow_reject, 'Create': handle_create, + 'Delete': handle_delete_status, 'Like': handle_favorite, 'Announce': handle_boost, 'Add': { @@ -229,6 +230,20 @@ def handle_create(activity): ) +@app.task +def handle_delete_status(activity): + ''' remove a status ''' + status_id = activity['object']['id'] + try: + status = models.Status.objects.select_subclasses().get( + remote_id=status_id + ) + except models.Status.DoesNotExist: + return + status_builder.delete_status(status) + + + @app.task def handle_favorite(activity): ''' approval of your good good post ''' diff --git a/bookwyrm/migrations/0054_auto_20201016_2359.py b/bookwyrm/migrations/0054_auto_20201016_2359.py new file mode 100644 index 00000000..c8ab3480 --- /dev/null +++ b/bookwyrm/migrations/0054_auto_20201016_2359.py @@ -0,0 +1,18 @@ +# Generated by Django 3.0.7 on 2020-10-16 23:59 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('bookwyrm', '0053_auto_20201006_2020'), + ] + + operations = [ + migrations.AlterField( + model_name='status', + name='deleted_date', + field=models.DateTimeField(), + ), + ] diff --git a/bookwyrm/models/status.py b/bookwyrm/models/status.py index f9f90467..0a70eb77 100644 --- a/bookwyrm/models/status.py +++ b/bookwyrm/models/status.py @@ -23,7 +23,7 @@ class Status(OrderedCollectionPageMixin, BookWyrmModel): # the created date can't be this, because of receiving federated posts published_date = models.DateTimeField(default=timezone.now) deleted = models.BooleanField(default=False) - deleted_date = models.DateTimeField(default=timezone.now) + deleted_date = models.DateTimeField() favorites = models.ManyToManyField( 'User', symmetrical=False, diff --git a/bookwyrm/status.py b/bookwyrm/status.py index 190f5dd7..25619839 100644 --- a/bookwyrm/status.py +++ b/bookwyrm/status.py @@ -1,4 +1,5 @@ ''' Handle user activity ''' +from datetime import datetime from django.db import IntegrityError from bookwyrm import models @@ -9,6 +10,7 @@ from bookwyrm.sanitize_html import InputHtmlParser def delete_status(status): ''' replace the status with a tombstone ''' status.deleted = True + status.deleted_date = datetime.now() status.save() def create_rating(user, book, rating):