mirror of
https://github.com/bookwyrm-social/bookwyrm.git
synced 2024-10-31 22:19:00 +00:00
Merge branch 'main' into production
This commit is contained in:
commit
8a1bfc5ffc
10 changed files with 97 additions and 18 deletions
|
@ -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
|
||||||
|
|
11
.github/workflows/lint-frontend.yaml
vendored
11
.github/workflows/lint-frontend.yaml
vendored
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 """
|
||||||
obj = self.object.to_model(save=False, allow_create=False)
|
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)
|
||||||
|
|
||||||
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)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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 """
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Reference in a new issue