bookwyrm/bookwyrm/models/relationship.py

171 lines
5.7 KiB
Python
Raw Normal View History

2020-09-17 20:09:11 +00:00
''' defines relationships between users '''
2021-02-11 00:06:50 +00:00
from django.apps import apps
from django.db import models, transaction
2021-01-25 01:07:19 +00:00
from django.db.models import Q
from django.dispatch import receiver
2020-09-17 20:09:11 +00:00
from bookwyrm import activitypub
from .activitypub_mixin import ActivitypubMixin, ActivityMixin
from .base_model import BookWyrmModel
from . import fields
2020-09-17 20:09:11 +00:00
class UserRelationship(BookWyrmModel):
2020-09-17 20:09:11 +00:00
''' many-to-many through table for followers '''
user_subject = fields.ForeignKey(
2020-09-17 20:09:11 +00:00
'User',
on_delete=models.PROTECT,
related_name='%(class)s_user_subject',
activitypub_field='actor',
2020-09-17 20:09:11 +00:00
)
user_object = fields.ForeignKey(
2020-09-17 20:09:11 +00:00
'User',
on_delete=models.PROTECT,
related_name='%(class)s_user_object',
activitypub_field='object',
2020-09-17 20:09:11 +00:00
)
@property
def privacy(self):
''' all relationships are handled directly with the participants '''
return 'direct'
@property
def recipients(self):
''' the remote user needs to recieve direct broadcasts '''
return [u for u in [self.user_subject, self.user_object] if not u.local]
2020-09-17 20:09:11 +00:00
class Meta:
''' relationships should be unique '''
abstract = True
constraints = [
models.UniqueConstraint(
fields=['user_subject', 'user_object'],
name='%(class)s_unique'
),
models.CheckConstraint(
check=~models.Q(user_subject=models.F('user_object')),
name='%(class)s_no_self'
)
]
2020-12-13 02:06:48 +00:00
def get_remote_id(self, status=None):# pylint: disable=arguments-differ
2020-09-17 20:09:11 +00:00
''' use shelf identifier in remote_id '''
status = status or 'follows'
2020-09-17 20:09:11 +00:00
base_path = self.user_subject.remote_id
return '%s#%s/%d' % (base_path, status, self.id)
2020-09-17 20:09:11 +00:00
2021-02-17 00:35:28 +00:00
class UserFollows(ActivitypubMixin, UserRelationship):
2020-09-17 20:09:11 +00:00
''' Following a user '''
status = 'follows'
@classmethod
def from_request(cls, follow_request):
''' converts a follow request into a follow relationship '''
return cls.objects.create(
2020-09-17 20:09:11 +00:00
user_subject=follow_request.user_subject,
user_object=follow_request.user_object,
remote_id=follow_request.remote_id,
2020-09-17 20:09:11 +00:00
)
class UserFollowRequest(ActivitypubMixin, UserRelationship):
2020-09-17 20:09:11 +00:00
''' following a user requires manual or automatic confirmation '''
status = 'follow_request'
activity_serializer = activitypub.Follow
2020-09-17 20:09:11 +00:00
def save(self, *args, broadcast=True, **kwargs):
''' make sure the follow or block relationship doesn't already exist '''
try:
UserFollows.objects.get(
user_subject=self.user_subject,
user_object=self.user_object,
)
# blocking in either direction is a no-go
UserBlocks.objects.get(
user_subject=self.user_subject,
user_object=self.user_object,
)
UserBlocks.objects.get(
user_subject=self.user_object,
user_object=self.user_subject,
)
2021-02-17 19:28:54 +00:00
return
except (UserFollows.DoesNotExist, UserBlocks.DoesNotExist):
2021-02-17 19:28:54 +00:00
pass
# this was calling itself which is not my idea of "super" ...
if not self.id:
super().save(*args, **kwargs)
2021-02-17 19:28:54 +00:00
return
2021-02-11 00:06:50 +00:00
if broadcast and self.user_subject.local and not self.user_object.local:
self.broadcast(self.to_activity(), self.user_subject)
2021-02-11 00:06:50 +00:00
if self.user_object.local:
2021-02-16 04:49:23 +00:00
manually_approves = self.user_object.manually_approves_followers
if not manually_approves:
2021-02-16 04:49:23 +00:00
self.accept()
2021-02-11 00:06:50 +00:00
model = apps.get_model('bookwyrm.Notification', require_ready=True)
2021-02-16 04:49:23 +00:00
notification_type = 'FOLLOW_REQUEST' if \
manually_approves else 'FOLLOW'
2021-02-11 00:06:50 +00:00
model.objects.create(
user=self.user_object,
related_user=self.user_subject,
notification_type=notification_type,
)
def accept(self):
''' turn this request into the real deal'''
user = self.user_object
2021-02-17 19:28:54 +00:00
if not self.user_subject.local:
activity = activitypub.Accept(
id=self.get_remote_id(status='accepts'),
actor=self.user_object.remote_id,
object=self.to_activity()
).serialize()
self.broadcast(activity, user)
with transaction.atomic():
UserFollows.from_request(self)
self.delete()
def reject(self):
''' generate a Reject for this follow request '''
user = self.user_object
activity = activitypub.Reject(
id=self.get_remote_id(status='rejects'),
actor=self.user_object.remote_id,
object=self.to_activity()
).serialize()
self.delete()
self.broadcast(activity, user)
2020-09-17 20:09:11 +00:00
class UserBlocks(ActivityMixin, UserRelationship):
2020-09-17 20:09:11 +00:00
''' prevent another user from following you and seeing your posts '''
status = 'blocks'
2021-01-23 19:03:10 +00:00
activity_serializer = activitypub.Block
2021-01-25 01:07:19 +00:00
@receiver(models.signals.post_save, sender=UserBlocks)
#pylint: disable=unused-argument
def execute_after_save(sender, instance, created, *args, **kwargs):
''' remove follow or follow request rels after a block is created '''
UserFollows.objects.filter(
Q(user_subject=instance.user_subject,
user_object=instance.user_object) | \
Q(user_subject=instance.user_object,
user_object=instance.user_subject)
).delete()
UserFollowRequest.objects.filter(
Q(user_subject=instance.user_subject,
user_object=instance.user_object) | \
Q(user_subject=instance.user_object,
user_object=instance.user_subject)
).delete()