Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Move activity for user migration #2970

Merged
merged 13 commits into from
Nov 2, 2023
1 change: 1 addition & 0 deletions bookwyrm/activitypub/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
from .verbs import Follow, Accept, Reject, Block
from .verbs import Add, Remove
from .verbs import Announce, Like
from .verbs import Move

# this creates a list of all the Activity types that we can serialize,
# so when an Activity comes in from outside, we can check if it's known
Expand Down
25 changes: 25 additions & 0 deletions bookwyrm/activitypub/verbs.py
Original file line number Diff line number Diff line change
Expand Up @@ -231,3 +231,28 @@ class Announce(Verb):
def action(self, allow_external_connections=True):
"""boost"""
self.to_model(allow_external_connections=allow_external_connections)

@dataclass(init=False)
class Move(Verb):
"""a user moving an object"""

# note the spec example for target and origin is an object but
# Mastodon uses a URI string and TBH this makes more sense
# Is there a way we can account for either format?
hughrun marked this conversation as resolved.
Show resolved Hide resolved

object: str
type: str = "Move"
target: str
origin: str

def action(self, allow_external_connections=True):
"""move"""

# we need to work out whether the object is a user or something else.

object_is_user = True # TODO!

if object_is_user:
self.to_model(object_is_user=True allow_external_connections=allow_external_connections)
else:
self.to_model(object_is_user=False allow_external_connections=allow_external_connections)
50 changes: 50 additions & 0 deletions bookwyrm/models/move.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
""" move an object including migrating a user account """
from django.db import models

from bookwyrm import activitypub
from .activitypub_mixin import ActivityMixin
from .base_model import BookWyrmModel
from . import fields
from .status import Status


class Move(ActivityMixin, BookWyrmModel):
"""migrating an activitypub user account"""

user = fields.ForeignKey(
"User", on_delete=models.PROTECT, activitypub_field="actor"
)

# TODO: can we just use the abstract class here?
hughrun marked this conversation as resolved.
Show resolved Hide resolved
activitypub_object = fields.ForeignKey(
"BookWyrmModel", on_delete=models.PROTECT,
activitypub_field="object",
blank=True,
null=True
)

target = fields.CharField(
max_length=255, blank=True, null=True, deduplication_field=True
)

origin = fields.CharField(
max_length=255, blank=True, null=True, deduplication_field=True
)

activity_serializer = activitypub.Move

# pylint: disable=unused-argument
@classmethod
def ignore_activity(cls, activity, allow_external_connections=True):
"""don't bother with incoming moves of unknown objects"""
# TODO how do we check this for any conceivable object?
pass

def save(self, *args, **kwargs):
"""update user active time"""
self.user.update_active_date()
super().save(*args, **kwargs)

# Ok what else? We can trigger a notification for followers of a user who sends a `Move` for themselves
# What about when a book is merged (i.e. moved from one id into another)? We could use that to send out a message
hughrun marked this conversation as resolved.
Show resolved Hide resolved
# to other Bookwyrm instances to update their remote_id for the book, but ...how do we trigger any action?
hughrun marked this conversation as resolved.
Show resolved Hide resolved
16 changes: 15 additions & 1 deletion bookwyrm/models/notification.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,14 @@ class Notification(BookWyrmModel):
GROUP_NAME = "GROUP_NAME"
GROUP_DESCRIPTION = "GROUP_DESCRIPTION"

# Migrations
MOVE = "MOVE"

# pylint: disable=line-too-long
NotificationType = models.TextChoices(
# there has got be a better way to do this
"NotificationType",
f"{FAVORITE} {REPLY} {MENTION} {TAG} {FOLLOW} {FOLLOW_REQUEST} {BOOST} {IMPORT} {ADD} {REPORT} {LINK_DOMAIN} {INVITE} {ACCEPT} {JOIN} {LEAVE} {REMOVE} {GROUP_PRIVACY} {GROUP_NAME} {GROUP_DESCRIPTION}",
f"{FAVORITE} {REPLY} {MENTION} {TAG} {FOLLOW} {FOLLOW_REQUEST} {BOOST} {IMPORT} {ADD} {REPORT} {LINK_DOMAIN} {INVITE} {ACCEPT} {JOIN} {LEAVE} {REMOVE} {GROUP_PRIVACY} {GROUP_NAME} {GROUP_DESCRIPTION} {MOVE}",
)

user = models.ForeignKey("User", on_delete=models.CASCADE)
Expand Down Expand Up @@ -326,3 +329,14 @@ def notify_user_on_follow(sender, instance, created, *args, **kwargs):
notification_type=Notification.FOLLOW,
read=False,
)

@receiver(models.signals.post_save, sender=Move)
# pylint: disable=unused-argument
def notify_on_move(sender, instance, *args, **kwargs):
"""someone moved something"""
Notification.notify(
instance.status.user,
instance.user,
related_object=instance.object,
notification_type=Notification.MOVE,
)
2 changes: 2 additions & 0 deletions bookwyrm/templates/notifications/item.html
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,6 @@
{% include 'notifications/items/update.html' %}
{% elif notification.notification_type == 'GROUP_DESCRIPTION' %}
{% include 'notifications/items/update.html' %}
{% elif notification.notification_type == 'MOVE' %}
{% include 'notifications/items/move.html' %}
{% endif %}
28 changes: 28 additions & 0 deletions bookwyrm/templates/notifications/items/move.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{% extends 'notifications/items/layout.html' %}

{% load i18n %}
{% load utilities %}

{% block primary_link %}{% spaceless %}
{{ notification.related_object.local_path }}
{% endspaceless %}{% endblock %}

{% block icon %}
<span class="icon icon-local"></span>
{% endblock %}

{% block description %}
<!--
TODO: a user has a 'name' but not everything does, notably a book.
On the other hand, maybe we don't need to notify anyone if a book
hughrun marked this conversation as resolved.
Show resolved Hide resolved
is moved, just update the remote_id?
-->
{% blocktrans trimmed with object_name=notification.related_object.name object_path=notification.related_object.local_path %}
<a href="{{ related_user_link }}">{{ related_user }}</a>
moved {{ object_name }}
"<a href="{{ object_path }}">{{ object_name }}</a>"
{% endblocktrans %}

<!-- TODO maybe put a brief context message here for migrated user accounts? -->

{% endblock %}
Loading