Merge pull request #239 from mouse-reeve/deletion

Deletion
This commit is contained in:
Mouse Reeve 2020-10-16 17:03:34 -07:00 committed by GitHub
commit 1672c699e5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 155 additions and 5 deletions

View file

@ -4,11 +4,12 @@ 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
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

View file

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

View file

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

View file

@ -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': {
@ -237,6 +238,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 '''

View file

@ -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),
),
]

View file

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

View file

@ -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()
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(
'<a href="%s">"%s"</a>' % (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)

View file

@ -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
@ -202,6 +203,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

View file

@ -1,4 +1,5 @@
''' Handle user activity '''
from datetime import datetime
from django.db import IntegrityError
from bookwyrm import models
@ -6,6 +7,12 @@ 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.deleted_date = datetime.now()
status.save()
def create_rating(user, book, rating):
''' a review that's just a rating '''
if not rating or rating < 1 or rating > 5:

View file

@ -1,6 +1,7 @@
{% load humanize %}
{% load fr_display %}
{% if not status.deleted %}
<div class="card">
<header class="card-header">
{% include 'snippets/status_header.html' with status=status %}
@ -25,7 +26,29 @@
<span class="icon icon-public">
<span class="is-sr-only">Public post</span>
</span>
{% if status.user == request.user %}
<form name="delete-{{status.id}}" action="/delete-status" method="post">
{% csrf_token %}
<input type="hidden" name="status" value="{{ status.id }}">
<button type="submit">
<span class="icon icon-cancel">
<span class="is-sr-only">Delete post</span>
</span>
</button>
</form>
{% endif %}
<a href="{{ status.remote_id }}">{{ status.published_date | naturaltime }}</a>
</div>
</footer>
</div>
{% else %}
<div class="card">
<header class="card-header">
<p>
{% include 'snippets/avatar.html' with user=status.user %}
{% include 'snippets/username.html' with user=status.user %}
deleted this status
</p>
</header>
</div>
{% endif %}

View file

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

View file

@ -11,7 +11,7 @@ localname_regex = r'(?P<username>[\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<status_id>\d+)' % \
(local_user_path, '|'.join(status_types))
@ -107,6 +107,8 @@ urlpatterns = [
re_path(r'^unfavorite/(?P<status_id>\d+)/?$', actions.unfavorite),
re_path(r'^boost/(?P<status_id>\d+)/?$', actions.boost),
re_path(r'^delete-status/?$', actions.delete_status),
re_path(r'^shelve/?$', actions.shelve),
re_path(r'^follow/?$', actions.follow),

View file

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

View file

@ -118,7 +118,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'