new data model: social
This commit is contained in:
parent
3511ac16a0
commit
ad696d8377
9 changed files with 294 additions and 54 deletions
|
@ -155,8 +155,33 @@ class LookupIdDescriptor(object): # TODO make it mixin of Field
|
||||||
# return sid[0] in IdType.values()
|
# return sid[0] in IdType.values()
|
||||||
|
|
||||||
|
|
||||||
class Item(PolymorphicModel):
|
class SoftDeleteMixin:
|
||||||
URL_PATH = None # subclass must specify this
|
"""
|
||||||
|
SoftDeleteMixin
|
||||||
|
|
||||||
|
Model must add this:
|
||||||
|
is_deleted = models.BooleanField(default=False, db_index=True)
|
||||||
|
|
||||||
|
Model may override this:
|
||||||
|
def clear(self):
|
||||||
|
pass
|
||||||
|
"""
|
||||||
|
|
||||||
|
def clear(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def delete(self, using=None, soft=True, *args, **kwargs):
|
||||||
|
print('SOFT')
|
||||||
|
if soft:
|
||||||
|
self.clear()
|
||||||
|
self.is_deleted = True
|
||||||
|
self.save(using=using)
|
||||||
|
else:
|
||||||
|
return super().delete(using=using, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class Item(PolymorphicModel, SoftDeleteMixin):
|
||||||
|
url_path = None # subclass must specify this
|
||||||
category = None # subclass must specify this
|
category = None # subclass must specify this
|
||||||
uid = models.UUIDField(default=uuid.uuid4, editable=False, db_index=True)
|
uid = models.UUIDField(default=uuid.uuid4, editable=False, db_index=True)
|
||||||
# item_type = models.CharField(_("类型"), choices=ItemType.choices, blank=False, max_length=50)
|
# item_type = models.CharField(_("类型"), choices=ItemType.choices, blank=False, max_length=50)
|
||||||
|
@ -174,23 +199,13 @@ class Item(PolymorphicModel):
|
||||||
is_deleted = models.BooleanField(default=False, db_index=True)
|
is_deleted = models.BooleanField(default=False, db_index=True)
|
||||||
history = HistoricalRecords()
|
history = HistoricalRecords()
|
||||||
merged_to_item = models.ForeignKey('Item', null=True, on_delete=models.SET_NULL, default=None, related_name="merged_from_items")
|
merged_to_item = models.ForeignKey('Item', null=True, on_delete=models.SET_NULL, default=None, related_name="merged_from_items")
|
||||||
# parent_item = models.ForeignKey('Item', null=True, on_delete=models.SET_NULL, related_name='child_items')
|
|
||||||
# identical_item = models.ForeignKey('Item', null=True, on_delete=models.SET_NULL, related_name='identical_items')
|
|
||||||
# def get_lookup_id(self, id_type: str) -> str:
|
|
||||||
# prefix = id_type.strip().lower() + ':'
|
|
||||||
# return next((x[len(prefix):] for x in self.lookup_ids if x.startswith(prefix)), None)
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
unique_together = [['polymorphic_ctype_id', 'primary_lookup_id_type', 'primary_lookup_id_value']]
|
unique_together = [['polymorphic_ctype_id', 'primary_lookup_id_type', 'primary_lookup_id_value']]
|
||||||
|
|
||||||
def delete(self, using=None, soft=True, *args, **kwargs):
|
def clear(self):
|
||||||
if soft:
|
|
||||||
self.primary_lookup_id_value = None
|
self.primary_lookup_id_value = None
|
||||||
self.primary_lookup_id_type = None
|
self.primary_lookup_id_type = None
|
||||||
self.is_deleted = True
|
|
||||||
self.save(using=using)
|
|
||||||
else:
|
|
||||||
return super().delete(using=using, *args, **kwargs)
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"{self.id}{' ' + self.primary_lookup_id_type + ':' + self.primary_lookup_id_value if self.primary_lookup_id_value else ''} ({self.title})"
|
return f"{self.id}{' ' + self.primary_lookup_id_type + ':' + self.primary_lookup_id_value if self.primary_lookup_id_value else ''} ({self.title})"
|
||||||
|
@ -221,13 +236,17 @@ class Item(PolymorphicModel):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def url(self):
|
def url(self):
|
||||||
return f'/{self.URL_PATH}/{base62.encode(self.uid.int)}'
|
return f'/{self.url_path}/{base62.encode(self.uid.int)}'
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_by_url(cls, url_or_b62):
|
def get_by_url(cls, url_or_b62):
|
||||||
b62 = url_or_b62.split('/')[-1]
|
b62 = url_or_b62.split('/')[-1]
|
||||||
return cls.objects.get(uid=uuid.UUID(int=base62.decode(b62)))
|
return cls.objects.get(uid=uuid.UUID(int=base62.decode(b62)))
|
||||||
|
|
||||||
|
# def get_lookup_id(self, id_type: str) -> str:
|
||||||
|
# prefix = id_type.strip().lower() + ':'
|
||||||
|
# return next((x[len(prefix):] for x in self.lookup_ids if x.startswith(prefix)), None)
|
||||||
|
|
||||||
def update_lookup_ids(self, lookup_ids):
|
def update_lookup_ids(self, lookup_ids):
|
||||||
# TODO
|
# TODO
|
||||||
# ll = set(lookup_ids)
|
# ll = set(lookup_ids)
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from polymorphic.models import PolymorphicModel
|
from polymorphic.models import PolymorphicModel
|
||||||
from users.models import User
|
from users.models import User
|
||||||
from catalog.common.models import Item, ItemCategory
|
from catalog.common.models import Item, ItemCategory, SoftDeleteMixin
|
||||||
from catalog.collection.models import Collection as CatalogCollection
|
from catalog.collection.models import Collection as CatalogCollection
|
||||||
from decimal import *
|
from decimal import *
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
|
@ -15,24 +15,14 @@ from functools import cached_property
|
||||||
from django.db.models import Count
|
from django.db.models import Count
|
||||||
|
|
||||||
|
|
||||||
class Piece(PolymorphicModel):
|
class UserOwnedObjectMixin:
|
||||||
|
"""
|
||||||
|
UserOwnedObjectMixin
|
||||||
|
|
||||||
|
Models must add these:
|
||||||
owner = models.ForeignKey(User, on_delete=models.PROTECT)
|
owner = models.ForeignKey(User, on_delete=models.PROTECT)
|
||||||
visibility = models.PositiveSmallIntegerField(default=0) # 0: Public / 1: Follower only / 2: Self only
|
visibility = models.PositiveSmallIntegerField(default=0)
|
||||||
metadata = models.JSONField(default=dict)
|
"""
|
||||||
created_time = models.DateTimeField(auto_now_add=True)
|
|
||||||
edited_time = models.DateTimeField(auto_now=True)
|
|
||||||
is_deleted = models.BooleanField(default=False, db_index=True)
|
|
||||||
|
|
||||||
def clear(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def delete(self, using=None, soft=True, *args, **kwargs):
|
|
||||||
if soft:
|
|
||||||
self.clear()
|
|
||||||
self.is_deleted = True
|
|
||||||
self.save(using=using)
|
|
||||||
else:
|
|
||||||
return super().delete(using=using, *args, **kwargs)
|
|
||||||
|
|
||||||
def is_visible_to(self, viewer):
|
def is_visible_to(self, viewer):
|
||||||
if not viewer.is_authenticated:
|
if not viewer.is_authenticated:
|
||||||
|
@ -63,11 +53,21 @@ class Piece(PolymorphicModel):
|
||||||
return visible_entities
|
return visible_entities
|
||||||
|
|
||||||
|
|
||||||
class Content(Piece):
|
class Piece(PolymorphicModel, UserOwnedObjectMixin):
|
||||||
target: models.ForeignKey(Item, on_delete=models.PROTECT)
|
owner = models.ForeignKey(User, on_delete=models.PROTECT)
|
||||||
|
visibility = models.PositiveSmallIntegerField(default=0) # 0: Public / 1: Follower only / 2: Self only
|
||||||
|
created_time = models.DateTimeField(auto_now_add=True)
|
||||||
|
edited_time = models.DateTimeField(auto_now=True)
|
||||||
|
is_deleted = models.BooleanField(default=False, db_index=True)
|
||||||
|
metadata = models.JSONField(default=dict)
|
||||||
|
attached_to = models.ForeignKey(User, null=True, default=None, on_delete=models.SET_NULL, related_name="attached_with")
|
||||||
|
|
||||||
|
|
||||||
|
class Content(SoftDeleteMixin, Piece):
|
||||||
|
item: models.ForeignKey(Item, on_delete=models.PROTECT)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"{self.id}({self.target})"
|
return f"{self.id}({self.item})"
|
||||||
|
|
||||||
|
|
||||||
class Note(Content):
|
class Note(Content):
|
||||||
|
@ -107,7 +107,7 @@ class List(Piece):
|
||||||
self._owner = self.owner
|
self._owner = self.owner
|
||||||
super().save(*args, **kwargs)
|
super().save(*args, **kwargs)
|
||||||
|
|
||||||
MEMBER_CLASS = None # subclass must override this
|
# MEMBER_CLASS = None # subclass must override this
|
||||||
# subclass must add this:
|
# subclass must add this:
|
||||||
# items = models.ManyToManyField(Item, through='ListMember')
|
# items = models.ManyToManyField(Item, through='ListMember')
|
||||||
|
|
||||||
|
@ -127,9 +127,9 @@ class List(Piece):
|
||||||
return None
|
return None
|
||||||
else:
|
else:
|
||||||
ml = self.ordered_members
|
ml = self.ordered_members
|
||||||
p = {self.__class__.__name__.lower(): self}
|
p = {'_' + self.__class__.__name__.lower(): self}
|
||||||
p.update(params)
|
p.update(params)
|
||||||
i = self.MEMBER_CLASS.objects.create(position=ml.last().position + 1 if ml.count() else 1, item=item, **p)
|
i = self.MEMBER_CLASS.objects.create(owner=self.owner, position=ml.last().position + 1 if ml.count() else 1, item=item, **p)
|
||||||
return i
|
return i
|
||||||
|
|
||||||
def remove_item(self, item):
|
def remove_item(self, item):
|
||||||
|
@ -162,13 +162,18 @@ class List(Piece):
|
||||||
member.save()
|
member.save()
|
||||||
|
|
||||||
|
|
||||||
class ListMember(models.Model):
|
class ListMember(Piece):
|
||||||
# subclass must add this:
|
"""
|
||||||
# list = models.ForeignKey('ListClass', related_name='members', on_delete=models.CASCADE)
|
ListMember - List class's member class
|
||||||
|
It's an abstract class, subclass must add this:
|
||||||
|
|
||||||
|
_list = models.ForeignKey('ListClass', related_name='members', on_delete=models.CASCADE)
|
||||||
|
|
||||||
|
it starts with _ bc Django internally created OneToOne Field on Piece
|
||||||
|
https://docs.djangoproject.com/en/3.2/topics/db/models/#specifying-the-parent-link-field
|
||||||
|
"""
|
||||||
item = models.ForeignKey(Item, on_delete=models.PROTECT)
|
item = models.ForeignKey(Item, on_delete=models.PROTECT)
|
||||||
position = models.PositiveIntegerField()
|
position = models.PositiveIntegerField()
|
||||||
metadata = models.JSONField(default=dict)
|
|
||||||
comment = models.ForeignKey(Review, on_delete=models.PROTECT, null=True)
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
abstract = True
|
abstract = True
|
||||||
|
@ -202,12 +207,13 @@ QueueTypeNames = [
|
||||||
[ItemCategory.Game, QueueType.WISHED, _('想玩')],
|
[ItemCategory.Game, QueueType.WISHED, _('想玩')],
|
||||||
[ItemCategory.Game, QueueType.STARTED, _('在玩')],
|
[ItemCategory.Game, QueueType.STARTED, _('在玩')],
|
||||||
[ItemCategory.Game, QueueType.DONE, _('玩过')],
|
[ItemCategory.Game, QueueType.DONE, _('玩过')],
|
||||||
|
[ItemCategory.Collection, QueueType.WISHED, _('关注')],
|
||||||
# TODO add more combinations
|
# TODO add more combinations
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class QueueMember(ListMember):
|
class QueueMember(ListMember):
|
||||||
queue = models.ForeignKey('Queue', related_name='members', on_delete=models.CASCADE)
|
_queue = models.ForeignKey('Queue', related_name='members', on_delete=models.CASCADE)
|
||||||
|
|
||||||
|
|
||||||
class Queue(List):
|
class Queue(List):
|
||||||
|
@ -258,12 +264,11 @@ class QueueManager:
|
||||||
|
|
||||||
def initialize(self):
|
def initialize(self):
|
||||||
for ic in ItemCategory:
|
for ic in ItemCategory:
|
||||||
if ic != ItemCategory.Collection:
|
|
||||||
for qt in QueueType:
|
for qt in QueueType:
|
||||||
Queue.objects.create(owner=self.owner, item_category=ic, queue_type=qt)
|
Queue.objects.create(owner=self.owner, item_category=ic, queue_type=qt)
|
||||||
|
|
||||||
def _queue_member_for_item(self, item):
|
def _queue_member_for_item(self, item):
|
||||||
return QueueMember.objects.filter(item=item, queue__in=self.owner.queue_set.all()).first()
|
return QueueMember.objects.filter(item=item, _queue__in=self.owner.queue_set.all()).first()
|
||||||
|
|
||||||
def _queue_for_item_and_type(item, queue_type):
|
def _queue_for_item_and_type(item, queue_type):
|
||||||
if not item or not queue_type:
|
if not item or not queue_type:
|
||||||
|
@ -276,7 +281,7 @@ class QueueManager:
|
||||||
raise ValueError('empty item')
|
raise ValueError('empty item')
|
||||||
lastqm = self._queue_member_for_item(item)
|
lastqm = self._queue_member_for_item(item)
|
||||||
lastqmm = lastqm.metadata if lastqm else None
|
lastqmm = lastqm.metadata if lastqm else None
|
||||||
lastq = lastqm.queue if lastqm else None
|
lastq = lastqm._queue if lastqm else None
|
||||||
lastqt = lastq.queue_type if lastq else None
|
lastqt = lastq.queue_type if lastq else None
|
||||||
queue = self.get_queue(item.category, queue_type) if queue_type else None
|
queue = self.get_queue(item.category, queue_type) if queue_type else None
|
||||||
if lastq != queue:
|
if lastq != queue:
|
||||||
|
@ -301,6 +306,14 @@ class QueueManager:
|
||||||
def get_queue(self, item_category, queue_type):
|
def get_queue(self, item_category, queue_type):
|
||||||
return self.owner.queue_set.all().filter(item_category=item_category, queue_type=queue_type).first()
|
return self.owner.queue_set.all().filter(item_category=item_category, queue_type=queue_type).first()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_manager_for_user(user):
|
||||||
|
return QueueManager(user)
|
||||||
|
|
||||||
|
|
||||||
|
User.queue_manager = cached_property(QueueManager.get_manager_for_user)
|
||||||
|
User.queue_manager.__set_name__(User, 'queue_manager')
|
||||||
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Collection
|
Collection
|
||||||
|
@ -308,7 +321,7 @@ Collection
|
||||||
|
|
||||||
|
|
||||||
class CollectionMember(ListMember):
|
class CollectionMember(ListMember):
|
||||||
collection = models.ForeignKey('Collection', related_name='members', on_delete=models.CASCADE)
|
_collection = models.ForeignKey('Collection', related_name='members', on_delete=models.CASCADE)
|
||||||
|
|
||||||
|
|
||||||
class Collection(List):
|
class Collection(List):
|
||||||
|
@ -340,7 +353,7 @@ Tag
|
||||||
|
|
||||||
|
|
||||||
class TagMember(ListMember):
|
class TagMember(ListMember):
|
||||||
tag = models.ForeignKey('Tag', related_name='members', on_delete=models.CASCADE)
|
_tag = models.ForeignKey('Tag', related_name='members', on_delete=models.CASCADE)
|
||||||
|
|
||||||
|
|
||||||
TagValidators = [RegexValidator(regex=r'\s+', inverse_match=True)]
|
TagValidators = [RegexValidator(regex=r'\s+', inverse_match=True)]
|
||||||
|
|
|
@ -32,7 +32,7 @@ class QueueTest(TestCase):
|
||||||
user = User.objects.create(mastodon_site="site", username="name")
|
user = User.objects.create(mastodon_site="site", username="name")
|
||||||
queue_manager = QueueManager(user=user)
|
queue_manager = QueueManager(user=user)
|
||||||
queue_manager.initialize()
|
queue_manager.initialize()
|
||||||
self.assertEqual(user.queue_set.all().count(), 30)
|
self.assertEqual(user.queue_set.all().count(), 33)
|
||||||
book1 = Edition.objects.create(title="Hyperion")
|
book1 = Edition.objects.create(title="Hyperion")
|
||||||
book2 = Edition.objects.create(title="Andymion")
|
book2 = Edition.objects.create(title="Andymion")
|
||||||
q1 = queue_manager.get_queue(ItemCategory.Book, QueueType.WISHED)
|
q1 = queue_manager.get_queue(ItemCategory.Book, QueueType.WISHED)
|
||||||
|
|
0
social/__init__.py
Normal file
0
social/__init__.py
Normal file
3
social/admin.py
Normal file
3
social/admin.py
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
# Register your models here.
|
6
social/apps.py
Normal file
6
social/apps.py
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class SocialConfig(AppConfig):
|
||||||
|
default_auto_field = 'django.db.models.BigAutoField'
|
||||||
|
name = 'social'
|
173
social/models.py
Normal file
173
social/models.py
Normal file
|
@ -0,0 +1,173 @@
|
||||||
|
"""
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class ActionType(models.TextChoices):
|
||||||
|
Create = 'create'
|
||||||
|
Delete = 'delete'
|
||||||
|
Update = 'update'
|
||||||
|
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)
|
||||||
|
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__
|
||||||
|
|
||||||
|
|
||||||
|
class DefaultSignalProcessor():
|
||||||
|
def __init__(self, action_object):
|
||||||
|
self.action_object = action_object
|
||||||
|
|
||||||
|
def activity_viewable(self, action_type):
|
||||||
|
return action_type == ActionType.Create and bool(getattr(self.action_object, 'attached_to', None))
|
||||||
|
|
||||||
|
def created(self):
|
||||||
|
return 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))
|
||||||
|
|
||||||
|
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:
|
||||||
|
create_activity.viewable = False
|
||||||
|
create_activity.save()
|
||||||
|
# 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):
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
@DataSignalManager.register
|
||||||
|
class MarkProcessor(DefaultSignalProcessor):
|
||||||
|
model = QueueMember
|
||||||
|
|
||||||
|
|
||||||
|
# @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
|
23
social/tests.py
Normal file
23
social/tests.py
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
from django.test import TestCase
|
||||||
|
from catalog.models import *
|
||||||
|
from journal.models import *
|
||||||
|
from .models import *
|
||||||
|
from users.models import User
|
||||||
|
|
||||||
|
|
||||||
|
class SocialTest(TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.book1 = Edition.objects.create(title="Hyperion")
|
||||||
|
self.book2 = Edition.objects.create(title="Andymion")
|
||||||
|
self.alice = User.objects.create(mastodon_site="MySpace", username="Alice")
|
||||||
|
self.alice.queue_manager.initialize()
|
||||||
|
self.bob = User.objects.create(mastodon_site="KKCity", username="Bob")
|
||||||
|
self.bob.queue_manager.initialize()
|
||||||
|
|
||||||
|
def test_timeline(self):
|
||||||
|
timeline = list(self.alice.activity_manager.get_viewable_activities())
|
||||||
|
self.assertEqual(timeline, [])
|
||||||
|
|
||||||
|
self.alice.queue_manager.update_for_item(self.book1, QueueType.WISHED)
|
||||||
|
timeline = list(self.alice.activity_manager.get_viewable_activities())
|
||||||
|
self.assertEqual(len(timeline), 1)
|
3
social/views.py
Normal file
3
social/views.py
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
from django.shortcuts import render
|
||||||
|
|
||||||
|
# Create your views here.
|
Loading…
Add table
Reference in a new issue