lib.itmens/social/models.py

185 lines
6.4 KiB
Python
Raw Normal View History

2022-12-13 06:44:29 +00:00
"""
Models for Social app
DataSignalManager captures create/update/(soft/hard)delete from Journal app, and generate Activity objects,
ActivityManager generates chronological view for user and, in future, ActivityStreams
"""
from django.db import models
from users.models import User
from catalog.common.models import Item
from journal.models import *
import logging
from functools import cached_property
from django.db.models.signals import post_save, post_delete, pre_delete
from django.db.models import Q
2022-12-17 16:18:16 -05:00
from django.conf import settings
2022-12-13 06:44:29 +00:00
_logger = logging.getLogger(__name__)
class ActionType(models.TextChoices):
Create = 'create'
Delete = 'delete'
Update = 'update'
2022-12-13 16:33:58 -05:00
Add = 'add'
Remove = 'remove'
2022-12-13 06:44:29 +00:00
Like = 'like'
Undo_Like = 'undo_like'
Announce = 'announce'
Undo_Announce = 'undo_announce'
Follow = 'follow'
Undo_Follow = 'undo_follow'
Flag = 'flag'
Move = 'move'
Accept = 'accept'
Reject = 'reject'
Block = 'block'
Undo_Block = 'undo_block'
class ActivityManager:
def __init__(self, user):
self.owner = user
def get_viewable_activities(self, before_time=None):
q = Q(owner_id__in=self.owner.following, visibility__lt=2) | Q(owner=self.owner)
2022-12-13 18:12:43 +00:00
q = q & Q(is_viewable=True)
2022-12-13 06:44:29 +00:00
if before_time:
q = q & Q(created_time__lt=before_time)
return Activity.objects.filter(q)
@staticmethod
def get_manager_for_user(user):
return ActivityManager(user)
User.activity_manager = cached_property(ActivityManager.get_manager_for_user)
User.activity_manager.__set_name__(User, 'activity_manager')
class Activity(models.Model, UserOwnedObjectMixin):
owner = models.ForeignKey(User, on_delete=models.PROTECT)
visibility = models.PositiveSmallIntegerField(default=0) # 0: Public / 1: Follower only / 2: Self only
action_type = models.CharField(blank=False, choices=ActionType.choices, max_length=50)
action_object = models.ForeignKey(Piece, on_delete=models.SET_NULL, null=True)
is_viewable = models.BooleanField(default=True) # if viewable in local time line, otherwise it's event only for s2s
# action_uid = TODO
@property
def target(self):
return get_attself.action_object
@property
def action_class(self):
return self.action_object.__class__.__name__
2022-12-13 18:12:43 +00:00
def __str__(self):
return f'{self.id}:{self.action_type}:{self.action_object}:{self.is_viewable}'
2022-12-13 06:44:29 +00:00
class DefaultSignalProcessor():
def __init__(self, action_object):
self.action_object = action_object
def activity_viewable(self, action_type):
2022-12-13 18:12:43 +00:00
return action_type == ActionType.Create and bool(getattr(self.action_object, 'attached_to', None) is None)
2022-12-13 06:44:29 +00:00
def created(self):
2022-12-13 18:12:43 +00:00
activity = Activity.objects.create(owner=self.action_object.owner, visibility=self.action_object.visibility, action_object=self.action_object, action_type=ActionType.Create, is_viewable=self.activity_viewable(ActionType.Create))
return activity
2022-12-13 06:44:29 +00:00
def updated(self):
create_activity = Activity.objects.filter(owner=self.action_object.owner, action_object=self.action_object, action_type=ActionType.Create).first()
action_type = ActionType.Update if create_activity else ActionType.Create
is_viewable = self.activity_viewable(action_type)
return Activity.objects.create(owner=self.action_object.owner, visibility=self.action_object.visibility, action_object=self.action_object, action_type=action_type, is_viewable=is_viewable)
def deleted(self):
create_activity = Activity.objects.filter(owner=self.action_object.owner, action_object=self.action_object, action_type=ActionType.Create).first()
if create_activity:
2022-12-13 18:12:43 +00:00
create_activity.is_viewable = False
2022-12-13 06:44:29 +00:00
create_activity.save()
2022-12-13 18:12:43 +00:00
else:
_logger.warning(f'unable to find create activity for {self.action_object}')
2022-12-13 06:44:29 +00:00
# FIXME action_object=self.action_object causing issues in test when hard delete, the bare minimum is to save id of the actual object that ActivityPub requires
return Activity.objects.create(owner=self.action_object.owner, visibility=self.action_object.visibility, action_object=None, action_type=ActionType.Delete, is_viewable=self.activity_viewable(ActionType.Delete))
class UnhandledSignalProcessor(DefaultSignalProcessor):
def created(self):
_logger.warning(f'unhandled created signal for {self.action_object}')
def updated(self):
_logger.warning(f'unhandled updated signal for {self.action_object}')
def deleted(self):
_logger.warning(f'unhandled deleted signal for {self.action_object}')
class DataSignalManager:
processors = {}
@staticmethod
def save_handler(sender, instance, created, **kwargs):
processor_class = DataSignalManager.processors.get(instance.__class__)
if not processor_class:
processor_class = GenericSignalProcessor
processor = processor_class(instance)
if created:
processor.created()
elif getattr(instance, 'is_deleted', False):
processor.deleted()
else:
processor.updated()
@staticmethod
def delete_handler(sender, instance, **kwargs):
processor_class = DataSignalManager.processors.get(instance.__class__)
if not processor_class:
processor_class = GenericSignalProcessor
processor = processor_class(instance)
processor.deleted()
@staticmethod
def add_handler_for_model(model):
2022-12-17 16:18:16 -05:00
if not settings.DISABLE_SOCIAL:
post_save.connect(DataSignalManager.save_handler, sender=model)
pre_delete.connect(DataSignalManager.delete_handler, sender=model)
2022-12-13 06:44:29 +00:00
@staticmethod
def register(processor):
DataSignalManager.add_handler_for_model(processor.model)
DataSignalManager.processors[processor.model] = processor
return processor
@DataSignalManager.register
class MarkProcessor(DefaultSignalProcessor):
2022-12-13 18:12:43 +00:00
model = ShelfMember
2022-12-13 06:44:29 +00:00
# @DataSignalManager.register
# class ReplyProcessor(DefaultSignalProcessor):
# model = Reply
# def activity_viewable(self):
# return False
# @DataSignalManager.register
# class RatingProcessor(DefaultSignalProcessor):
# model = Rating
@DataSignalManager.register
class ReviewProcessor(DefaultSignalProcessor):
model = Review
@DataSignalManager.register
class CollectionProcessor(DefaultSignalProcessor):
model = Collection