lib.itmens/social/models.py

203 lines
6.2 KiB
Python

"""
Models for Social app
DataSignalManager captures create/update/(soft/hard)delete/add/remove from Journal app, and generate Activity objects,
ActivityManager generates chronological view for user and, in future, ActivityStreams
"""
from typing import Type
from django.conf import settings
from django.db import models
from django.db.models import Q
from django.db.models.signals import post_save, pre_delete
from django.utils import timezone
from loguru import logger
from journal.models import (
Collection,
Comment,
FeaturedCollection,
Like,
Piece,
Review,
ShelfMember,
UserOwnedObjectMixin,
)
from users.models import APIdentity
class ActivityTemplate(models.TextChoices):
MarkItem = "mark_item"
ReviewItem = "review_item"
CreateCollection = "create_collection"
LikeCollection = "like_collection"
FeatureCollection = "feature_collection"
CommentChildItem = "comment_child_item"
class LocalActivity(models.Model, UserOwnedObjectMixin):
owner = models.ForeignKey(APIdentity, on_delete=models.CASCADE)
visibility = models.PositiveSmallIntegerField(default=0) # type: ignore
template = models.CharField(
blank=False, choices=ActivityTemplate.choices, max_length=50
)
action_object = models.ForeignKey(Piece, on_delete=models.CASCADE)
created_time = models.DateTimeField(default=timezone.now, db_index=True)
class Meta:
index_together = [
["owner", "created_time"],
]
def __str__(self):
return f"Activity [{self.owner}:{self.template}:{self.action_object}]"
class ActivityManager:
def __init__(self, owner: APIdentity):
self.owner = owner
def get_timeline(self, before_time=None):
following = [x for x in self.owner.following if x not in self.owner.muting]
q = Q(owner_id__in=following, visibility__lt=2) | Q(owner=self.owner)
if before_time:
q = q & Q(created_time__lt=before_time)
return (
LocalActivity.objects.filter(q)
.order_by("-created_time")
.prefetch_related("action_object", "owner")
) # .select_related() https://github.com/django-polymorphic/django-polymorphic/pull/531
@staticmethod
def get_manager_for_user(user):
return ActivityManager(user)
class DataSignalManager:
processors = {}
@staticmethod
def save_handler(sender, instance, created, **kwargs):
processor_class = DataSignalManager.processors.get(instance.__class__)
if processor_class:
processor = processor_class(instance)
if created:
if hasattr(processor, "created"):
processor.created()
elif hasattr(processor, "updated"):
processor.updated()
@staticmethod
def delete_handler(sender, instance, **kwargs):
processor_class = DataSignalManager.processors.get(instance.__class__)
if processor_class:
processor = processor_class(instance)
if hasattr(processor, "deleted"):
processor.deleted()
@staticmethod
def add_handler_for_model(model):
if settings.DISABLE_MODEL_SIGNAL:
logger.warning(
f"{model.__name__} are not being indexed with DISABLE_MODEL_SIGNAL configuration"
)
return
post_save.connect(DataSignalManager.save_handler, sender=model)
pre_delete.connect(DataSignalManager.delete_handler, sender=model)
@staticmethod
def register(processor):
DataSignalManager.add_handler_for_model(processor.model)
DataSignalManager.processors[processor.model] = processor
return processor
class DefaultActivityProcessor:
model: Type[Piece]
template: ActivityTemplate
def __init__(self, action_object):
self.action_object = action_object
def created(self):
params = {
"owner": self.action_object.owner,
"visibility": self.action_object.visibility,
"template": self.template,
"action_object": self.action_object,
"created_time": self.action_object.created_time,
}
LocalActivity.objects.create(**params)
def updated(self):
activity = LocalActivity.objects.filter(
action_object=self.action_object
).first()
if not activity:
self.created()
elif (
activity.visibility != self.action_object.visibility
or activity.created_time != activity.action_object.created_time
):
activity.visibility = self.action_object.visibility
activity.created_time = activity.action_object.created_time
activity.save()
@DataSignalManager.register
class MarkProcessor(DefaultActivityProcessor):
model = ShelfMember
template = ActivityTemplate.MarkItem
@DataSignalManager.register
class ReviewProcessor(DefaultActivityProcessor):
model = Review
template = ActivityTemplate.ReviewItem
@DataSignalManager.register
class CollectionProcessor(DefaultActivityProcessor):
model = Collection
template = ActivityTemplate.CreateCollection
@DataSignalManager.register
class LikeCollectionProcessor(DefaultActivityProcessor):
model = Like
template = ActivityTemplate.LikeCollection
def created(self):
if isinstance(self.action_object.target, Collection):
super().created()
def updated(self):
if isinstance(self.action_object.target, Collection):
super().updated()
@DataSignalManager.register
class FeaturedCollectionProcessor(DefaultActivityProcessor):
model = FeaturedCollection
template = ActivityTemplate.FeatureCollection
@DataSignalManager.register
class CommentChildItemProcessor(DefaultActivityProcessor):
model = Comment
template = ActivityTemplate.CommentChildItem
def created(self):
if self.action_object.item.class_name in ["podcastepisode", "tvepisode"]:
super().created()
def updated(self):
if self.action_object.item.class_name in ["podcastepisode", "tvepisode"]:
super().updated()
def reset_social_visibility_for_user(owner: APIdentity, visibility: int):
LocalActivity.objects.filter(owner=owner).update(visibility=visibility)