Merge branch 'main' into production

This commit is contained in:
Mouse Reeve 2021-04-17 15:29:52 -07:00
commit 8a1bfc5ffc
10 changed files with 97 additions and 18 deletions

View file

@ -23,6 +23,7 @@ max_line_length = off
[*.{csv,json,html,md,po,py,svg,tsv}] [*.{csv,json,html,md,po,py,svg,tsv}]
max_line_length = off max_line_length = off
# ` ` at the end of a line is a line-break in markdown
[*.{md,markdown}] [*.{md,markdown}]
trim_trailing_whitespace = false trim_trailing_whitespace = false
@ -30,7 +31,9 @@ trim_trailing_whitespace = false
indent_size = 2 indent_size = 2
max_line_length = off max_line_length = off
[{package.json,yarn.lock}] # Computer generated files
[{package.json,*.lock,*.mo}]
indent_size = unset indent_size = unset
indent_style = unset indent_style = unset
max_line_length = unset max_line_length = unset
insert_final_newline = unset

View file

@ -18,7 +18,7 @@ jobs:
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
steps: steps:
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it.
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Install modules - name: Install modules
@ -26,8 +26,13 @@ jobs:
# See .stylelintignore for files that are not linted. # See .stylelintignore for files that are not linted.
- name: Run stylelint - name: Run stylelint
run: yarn stylelint bookwyrm/static/**/*.css --report-needless-disables --report-invalid-scope-disables run: >
yarn stylelint bookwyrm/static/**/*.css \
--report-needless-disables \
--report-invalid-scope-disables
# See .eslintignore for files that are not linted. # See .eslintignore for files that are not linted.
- name: Run ESLint - name: Run ESLint
run: yarn eslint bookwyrm/static --ext .js,.jsx,.ts,.tsx run: >
yarn eslint bookwyrm/static \
--ext .js,.jsx,.ts,.tsx

View file

@ -5,7 +5,7 @@ import sys
from .base_activity import ActivityEncoder, Signature, naive_parse from .base_activity import ActivityEncoder, Signature, naive_parse
from .base_activity import Link, Mention from .base_activity import Link, Mention
from .base_activity import ActivitySerializerError, resolve_remote_id from .base_activity import ActivitySerializerError, resolve_remote_id
from .image import Document from .image import Document, Image
from .note import Note, GeneratedNote, Article, Comment, Quotation from .note import Note, GeneratedNote, Article, Comment, Quotation
from .note import Review, Rating from .note import Review, Rating
from .note import Tombstone from .note import Tombstone

View file

@ -5,9 +5,16 @@ from .base_activity import ActivityObject
@dataclass(init=False) @dataclass(init=False)
class Document(ActivityObject): class Document(ActivityObject):
""" image block """ """ a document """
url: str url: str
name: str = "" name: str = ""
type: str = "Document" type: str = "Document"
id: str = None id: str = None
@dataclass(init=False)
class Image(Document):
""" an image """
type: str = "Image"

View file

@ -3,7 +3,7 @@ from dataclasses import dataclass, field
from typing import Dict from typing import Dict
from .base_activity import ActivityObject from .base_activity import ActivityObject
from .image import Document from .image import Image
@dataclass(init=False) @dataclass(init=False)
@ -28,7 +28,7 @@ class Person(ActivityObject):
endpoints: Dict = None endpoints: Dict = None
name: str = None name: str = None
summary: str = None summary: str = None
icon: Document = field(default_factory=lambda: {}) icon: Image = field(default_factory=lambda: {})
bookwyrmUser: bool = False bookwyrmUser: bool = False
manuallyApprovesFollowers: str = False manuallyApprovesFollowers: str = False
discoverable: str = False discoverable: str = False

View file

@ -16,11 +16,10 @@ class Verb(ActivityObject):
def action(self): def action(self):
""" usually we just want to update and save """ """ usually we just want to update and save """
obj = self.object # self.object may return None if the object is invalid in an expected way
# it may return None if the object is invalid in an expected way
# ie, Question type # ie, Question type
if obj: if self.object:
obj.to_model() self.object.to_model()
@dataclass(init=False) @dataclass(init=False)
@ -43,7 +42,16 @@ class Delete(Verb):
def action(self): def action(self):
""" find and delete the activity object """ """ find and delete the activity object """
if not self.object:
return
if isinstance(self.object, str):
# Deleted users are passed as strings. Not wild about this fix
model = apps.get_model("bookwyrm.User")
obj = model.find_existing_by_remote_id(self.object)
else:
obj = self.object.to_model(save=False, allow_create=False) obj = self.object.to_model(save=False, allow_create=False)
if obj: if obj:
obj.delete() obj.delete()
# if we can't find it, we don't need to delete it because we don't have it # if we can't find it, we don't need to delete it because we don't have it
@ -58,8 +66,7 @@ class Update(Verb):
def action(self): def action(self):
""" update a model instance from the dataclass """ """ update a model instance from the dataclass """
obj = self.object if self.object:
if obj:
self.object.to_model(allow_create=False) self.object.to_model(allow_create=False)

View file

@ -241,7 +241,9 @@ class ObjectMixin(ActivitypubMixin):
return return
# is this a deletion? # is this a deletion?
if hasattr(self, "deleted") and self.deleted: if (hasattr(self, "deleted") and self.deleted) or (
hasattr(self, "is_active") and not self.is_active
):
activity = self.to_delete_activity(user) activity = self.to_delete_activity(user)
else: else:
activity = self.to_update_activity(user) activity = self.to_update_activity(user)

View file

@ -205,6 +205,9 @@ class User(OrderedCollectionPageMixin, AbstractUser):
def to_activity(self, **kwargs): def to_activity(self, **kwargs):
"""override default AP serializer to add context object """override default AP serializer to add context object
idk if this is the best way to go about this""" idk if this is the best way to go about this"""
if not self.is_active:
return self.remote_id
activity_object = super().to_activity(**kwargs) activity_object = super().to_activity(**kwargs)
activity_object["@context"] = [ activity_object["@context"] = [
"https://www.w3.org/ns/activitystreams", "https://www.w3.org/ns/activitystreams",
@ -283,6 +286,12 @@ class User(OrderedCollectionPageMixin, AbstractUser):
editable=False, editable=False,
).save(broadcast=False) ).save(broadcast=False)
def delete(self, *args, **kwargs):
""" deactivate rather than delete a user """
self.is_active = False
# skip the logic in this class's save()
super().save(*args, **kwargs)
@property @property
def local_path(self): def local_path(self):
""" this model doesn't inherit bookwyrm model, so here we are """ """ this model doesn't inherit bookwyrm model, so here we are """

View file

@ -1,4 +1,5 @@
""" testing models """ """ testing models """
import json
from unittest.mock import patch from unittest.mock import patch
from django.test import TestCase from django.test import TestCase
import responses import responses
@ -152,3 +153,17 @@ class User(TestCase):
self.assertEqual(server.server_name, DOMAIN) self.assertEqual(server.server_name, DOMAIN)
self.assertIsNone(server.application_type) self.assertIsNone(server.application_type)
self.assertIsNone(server.application_version) self.assertIsNone(server.application_version)
def test_delete_user(self):
""" deactivate a user """
self.assertTrue(self.user.is_active)
with patch(
"bookwyrm.models.activitypub_mixin.broadcast_task.delay"
) as broadcast_mock:
self.user.delete()
self.assertEqual(broadcast_mock.call_count, 1)
activity = json.loads(broadcast_mock.call_args[0][1])
self.assertEqual(activity["type"], "Delete")
self.assertEqual(activity["object"], self.user.remote_id)
self.assertFalse(self.user.is_active)

View file

@ -49,7 +49,7 @@ class InboxActivities(TestCase):
} }
models.SiteSettings.objects.create() models.SiteSettings.objects.create()
def test_handle_delete_status(self): def test_delete_status(self):
""" remove a status """ """ remove a status """
self.assertFalse(self.status.deleted) self.assertFalse(self.status.deleted)
activity = { activity = {
@ -70,7 +70,7 @@ class InboxActivities(TestCase):
self.assertTrue(status.deleted) self.assertTrue(status.deleted)
self.assertIsInstance(status.deleted_date, datetime) self.assertIsInstance(status.deleted_date, datetime)
def test_handle_delete_status_notifications(self): def test_delete_status_notifications(self):
""" remove a status with related notifications """ """ remove a status with related notifications """
models.Notification.objects.create( models.Notification.objects.create(
related_status=self.status, related_status=self.status,
@ -104,3 +104,34 @@ class InboxActivities(TestCase):
# notifications should be truly deleted # notifications should be truly deleted
self.assertEqual(models.Notification.objects.count(), 1) self.assertEqual(models.Notification.objects.count(), 1)
self.assertEqual(models.Notification.objects.get(), notif) self.assertEqual(models.Notification.objects.get(), notif)
def test_delete_user(self):
""" delete a user """
self.assertTrue(models.User.objects.get(username="rat@example.com").is_active)
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": self.remote_user.remote_id,
}
views.inbox.activity_task(activity)
self.assertFalse(models.User.objects.get(username="rat@example.com").is_active)
def test_delete_user_unknown(self):
""" don't worry about it if we don't know the user """
self.assertEqual(models.User.objects.filter(is_active=True).count(), 2)
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": "https://example.com/users/test-user",
}
# nothing happens.
views.inbox.activity_task(activity)
self.assertEqual(models.User.objects.filter(is_active=True).count(), 2)