2022-12-11 23:20:28 +00:00
from django . db import models
from polymorphic . models import PolymorphicModel
from users . models import User
2022-12-13 18:12:43 +00:00
from catalog . common . models import Item , ItemCategory
from . mixins import UserOwnedObjectMixin
2022-12-12 16:46:37 +00:00
from catalog . collection . models import Collection as CatalogCollection
2022-12-11 23:20:28 +00:00
from markdownx . models import MarkdownxField
from django . utils import timezone
from django . conf import settings
from django . core . validators import MaxValueValidator , MinValueValidator
from django . utils . translation import gettext_lazy as _
from django . core . validators import RegexValidator
from functools import cached_property
2022-12-14 21:12:37 -05:00
from django . db . models import Count , Avg
2022-12-29 16:20:33 -05:00
from django . contrib . contenttypes . models import ContentType
2022-12-13 18:12:43 +00:00
import django . dispatch
2022-12-17 16:18:16 -05:00
import uuid
2022-12-29 16:20:33 -05:00
import re
2023-01-12 11:15:28 -05:00
from catalog . common . utils import DEFAULT_ITEM_COVER , piece_cover_path
2022-12-21 14:34:36 -05:00
from django . utils . baseconv import base62
2022-12-23 00:08:42 -05:00
from django . db . models import Q
2022-12-27 14:52:03 -05:00
from catalog . models import *
from django . contrib . contenttypes . models import ContentType
2023-01-31 10:24:57 -05:00
from . renderers import render_md , render_text
2022-12-29 14:30:31 -05:00
from catalog . common import jsondata
2023-04-20 13:36:12 -04:00
from django . db import connection
2023-01-30 17:12:25 -05:00
2023-01-07 00:35:30 -05:00
_logger = logging . getLogger ( __name__ )
2022-12-23 00:08:42 -05:00
2022-12-25 13:45:24 -05:00
class VisibilityType ( models . IntegerChoices ) :
2022-12-29 14:30:31 -05:00
Public = 0 , _ ( " 公开 " )
Follower_Only = 1 , _ ( " 仅关注者 " )
Private = 2 , _ ( " 仅自己 " )
2022-12-25 13:45:24 -05:00
2022-12-28 01:09:55 -05:00
def q_visible_to ( viewer , owner ) :
if viewer == owner :
return Q ( )
# elif viewer.is_blocked_by(owner):
# return Q(pk__in=[])
2023-01-01 23:50:57 -05:00
elif viewer . is_authenticated and viewer . is_following ( owner ) :
2023-01-09 01:16:10 -05:00
return Q ( visibility__in = [ 0 , 1 ] )
2022-12-28 01:09:55 -05:00
else :
return Q ( visibility = 0 )
2023-04-20 13:36:12 -04:00
def max_visiblity_to ( viewer , owner ) :
if viewer == owner :
return 2
# elif viewer.is_blocked_by(owner):
# return Q(pk__in=[])
elif viewer . is_authenticated and viewer . is_following ( owner ) :
return 1
else :
return 0
2022-12-23 00:08:42 -05:00
def query_visible ( user ) :
2022-12-29 14:30:31 -05:00
return (
Q ( visibility = 0 )
2023-01-01 23:50:57 -05:00
| Q ( owner_id__in = user . following if user . is_authenticated else [ ] , visibility = 1 )
2022-12-29 14:30:31 -05:00
| Q ( owner_id = user . id )
)
2022-12-11 23:20:28 +00:00
2022-12-24 01:28:24 -05:00
def query_following ( user ) :
return Q ( owner_id__in = user . following , visibility__lt = 2 ) | Q ( owner_id = user . id )
2022-12-27 14:52:03 -05:00
def query_item_category ( item_category ) :
2022-12-28 10:24:07 -05:00
classes = all_categories ( ) [ item_category ]
2022-12-27 14:52:03 -05:00
# q = Q(item__instance_of=classes[0])
# for cls in classes[1:]:
# q = q | Q(instance_of=cls)
# return q
2022-12-28 10:24:07 -05:00
ct = all_content_types ( )
contenttype_ids = [ ct [ cls ] for cls in classes ]
2022-12-29 23:49:28 -05:00
return Q ( item__polymorphic_ctype__in = contenttype_ids )
2022-12-27 14:52:03 -05:00
2022-12-31 00:20:20 -05:00
# class ImportStatus(Enum):
# QUEUED = 0
# PROCESSING = 1
# FINISHED = 2
# class ImportSession(models.Model):
# owner = models.ForeignKey(User, on_delete=models.CASCADE)
# status = models.PositiveSmallIntegerField(default=ImportStatus.QUEUED)
# importer = models.CharField(max_length=50)
# file = models.CharField()
# default_visibility = models.PositiveSmallIntegerField()
# total = models.PositiveIntegerField()
# processed = models.PositiveIntegerField()
# skipped = models.PositiveIntegerField()
# imported = models.PositiveIntegerField()
# failed = models.PositiveIntegerField()
# logs = models.JSONField(default=list)
# created_time = models.DateTimeField(auto_now_add=True)
# edited_time = models.DateTimeField(auto_now=True)
# class Meta:
# indexes = [
# models.Index(fields=["owner", "importer", "created_time"]),
# ]
2022-12-13 06:44:29 +00:00
class Piece ( PolymorphicModel , UserOwnedObjectMixin ) :
2023-01-23 16:31:30 -05:00
url_path = " p " # subclass must specify this
2022-12-17 16:18:16 -05:00
uid = models . UUIDField ( default = uuid . uuid4 , editable = False , db_index = True )
2022-12-13 06:44:29 +00:00
2022-12-21 14:34:36 -05:00
@property
def uuid ( self ) :
return base62 . encode ( self . uid . int )
2022-12-24 01:28:24 -05:00
@property
def url ( self ) :
2022-12-29 14:30:31 -05:00
return f " / { self . url_path } / { self . uuid } " if self . url_path else None
2022-12-24 01:28:24 -05:00
@property
def absolute_url ( self ) :
return ( settings . APP_WEBSITE + self . url ) if self . url_path else None
@property
def api_url ( self ) :
2022-12-29 16:20:33 -05:00
return f " /api/ { self . url } " if self . url_path else None
2022-12-24 01:28:24 -05:00
2023-02-11 20:40:00 -05:00
@property
def like_count ( self ) :
return self . likes . all ( ) . count ( )
2023-02-03 00:02:43 -05:00
@classmethod
def get_by_url ( cls , url_or_b62 ) :
b62 = url_or_b62 . strip ( ) . split ( " / " ) [ - 1 ]
if len ( b62 ) not in [ 21 , 22 ] :
r = re . search ( r " [A-Za-z0-9] { 21,22} " , url_or_b62 )
if r :
b62 = r [ 0 ]
try :
obj = cls . objects . get ( uid = uuid . UUID ( int = base62 . decode ( b62 ) ) )
except :
obj = None
return obj
2022-12-13 06:44:29 +00:00
2022-12-15 17:29:35 -05:00
class Content ( Piece ) :
2022-12-29 16:20:33 -05:00
owner = models . ForeignKey ( User , on_delete = models . PROTECT )
visibility = models . PositiveSmallIntegerField (
default = 0
) # 0: Public / 1: Follower only / 2: Self only
2023-01-11 09:20:14 -05:00
created_time = models . DateTimeField ( default = timezone . now )
2022-12-29 16:20:33 -05:00
edited_time = models . DateTimeField (
default = timezone . now
) # auto_now=True FIXME revert this after migration
metadata = models . JSONField ( default = dict )
2022-12-14 21:12:37 -05:00
item = models . ForeignKey ( Item , on_delete = models . PROTECT )
2022-12-11 23:20:28 +00:00
2022-12-27 14:52:03 -05:00
@cached_property
def mark ( self ) :
m = Mark ( self . owner , self . item )
m . review = self
return m
2022-12-11 23:20:28 +00:00
def __str__ ( self ) :
2022-12-25 13:45:24 -05:00
return f " { self . uuid } @ { self . item } "
2022-12-11 23:20:28 +00:00
2022-12-15 17:29:35 -05:00
class Meta :
abstract = True
2022-12-11 23:20:28 +00:00
2022-12-20 11:32:44 -05:00
class Like ( Piece ) :
2022-12-29 16:20:33 -05:00
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 (
default = timezone . now
) # auto_now_add=True FIXME revert this after migration
edited_time = models . DateTimeField (
default = timezone . now
) # auto_now=True FIXME revert this after migration
2022-12-29 14:30:31 -05:00
target = models . ForeignKey ( Piece , on_delete = models . CASCADE , related_name = " likes " )
2022-12-20 11:32:44 -05:00
2022-12-29 16:20:33 -05:00
@staticmethod
def user_liked_piece ( user , piece ) :
2023-02-11 20:40:00 -05:00
return Like . objects . filter ( owner = user , target = piece ) . exists ( )
2022-12-29 16:20:33 -05:00
2022-12-21 14:34:36 -05:00
@staticmethod
def user_like_piece ( user , piece ) :
2023-02-11 20:40:00 -05:00
if not piece :
2022-12-21 14:34:36 -05:00
return
like = Like . objects . filter ( owner = user , target = piece ) . first ( )
if not like :
like = Like . objects . create ( owner = user , target = piece )
return like
2022-12-29 14:30:31 -05:00
@staticmethod
def user_unlike_piece ( user , piece ) :
if not piece :
return
Like . objects . filter ( owner = user , target = piece ) . delete ( )
2022-12-29 16:20:33 -05:00
@staticmethod
def user_likes_by_class ( user , cls ) :
ctype_id = ContentType . objects . get_for_model ( cls )
return Like . objects . filter ( owner = user , target__polymorphic_ctype = ctype_id )
2022-12-20 11:32:44 -05:00
2022-12-29 14:30:31 -05:00
class Memo ( Content ) :
2022-12-11 23:20:28 +00:00
pass
2022-12-15 17:29:35 -05:00
class Comment ( Content ) :
text = models . TextField ( blank = False , null = False )
2023-01-31 21:21:50 -05:00
focus_item = models . ForeignKey (
Item , on_delete = models . PROTECT , null = True , related_name = " focused_comments "
)
2022-12-15 17:29:35 -05:00
2023-01-31 10:24:57 -05:00
@property
def html ( self ) :
return render_text ( self . text )
2023-02-01 22:40:34 -05:00
@property
def item_url ( self ) :
if self . focus_item :
return self . focus_item . get_absolute_url_with_position (
self . metadata [ " position " ]
)
else :
return self . item . url
2022-12-15 17:29:35 -05:00
@staticmethod
def comment_item_by_user ( item , user , text , visibility = 0 ) :
2023-02-11 23:10:24 -05:00
comment = Comment . objects . filter (
owner = user , item = item , focus_item__isnull = True
) . first ( )
2022-12-17 16:18:16 -05:00
if not text :
2022-12-15 17:29:35 -05:00
if comment is not None :
comment . delete ( )
comment = None
elif comment is None :
2022-12-29 14:30:31 -05:00
comment = Comment . objects . create (
owner = user , item = item , text = text , visibility = visibility
)
2022-12-17 16:18:16 -05:00
elif comment . text != text or comment . visibility != visibility :
2022-12-15 17:29:35 -05:00
comment . text = text
comment . visibility = visibility
comment . save ( )
return comment
2022-12-11 23:20:28 +00:00
class Review ( Content ) :
2022-12-29 14:30:31 -05:00
url_path = " review "
2022-12-15 17:29:35 -05:00
title = models . CharField ( max_length = 500 , blank = False , null = False )
2022-12-11 23:20:28 +00:00
body = MarkdownxField ( )
2022-12-24 01:28:24 -05:00
@property
def html_content ( self ) :
2023-01-30 17:12:25 -05:00
return render_md ( self . body )
2022-12-24 01:28:24 -05:00
2022-12-26 14:56:39 -05:00
@cached_property
def rating_grade ( self ) :
return Rating . get_item_rating_by_user ( self . item , self . owner )
2022-12-29 14:30:31 -05:00
@staticmethod
2022-12-19 17:50:00 -05:00
def review_item_by_user ( item , user , title , body , metadata = { } , visibility = 0 ) :
2022-12-15 17:29:35 -05:00
# allow multiple reviews per item per user.
2022-12-29 14:30:31 -05:00
review = Review . objects . create (
owner = user ,
item = item ,
title = title ,
body = body ,
metadata = metadata ,
visibility = visibility ,
)
2022-12-15 17:29:35 -05:00
"""
review = Review . objects . filter ( owner = user , item = item ) . first ( )
if title is None :
if review is not None :
review . delete ( )
review = None
elif review is None :
review = Review . objects . create ( owner = user , item = item , title = title , body = body , visibility = visibility )
else :
review . title = title
review . body = body
review . visibility = visibility
review . save ( )
"""
return review
2022-12-11 23:20:28 +00:00
2022-12-15 17:29:35 -05:00
class Rating ( Content ) :
2023-01-12 16:08:10 -05:00
class Meta :
unique_together = [ [ " owner " , " item " ] ]
2023-01-07 00:35:30 -05:00
2022-12-29 14:30:31 -05:00
grade = models . PositiveSmallIntegerField (
default = 0 , validators = [ MaxValueValidator ( 10 ) , MinValueValidator ( 1 ) ] , null = True
)
2022-12-11 23:20:28 +00:00
2022-12-29 14:30:31 -05:00
@staticmethod
2022-12-14 21:12:37 -05:00
def get_rating_for_item ( item ) :
2022-12-29 14:30:31 -05:00
stat = Rating . objects . filter ( item = item , grade__isnull = False ) . aggregate (
average = Avg ( " grade " ) , count = Count ( " item " )
)
2023-03-13 21:59:34 -04:00
return round ( stat [ " average " ] , 1 ) if stat [ " count " ] > = 5 else None
2022-12-14 21:12:37 -05:00
2022-12-29 14:30:31 -05:00
@staticmethod
2022-12-14 21:12:37 -05:00
def get_rating_count_for_item ( item ) :
2022-12-29 14:30:31 -05:00
stat = Rating . objects . filter ( item = item , grade__isnull = False ) . aggregate (
count = Count ( " item " )
)
return stat [ " count " ]
2022-12-14 21:12:37 -05:00
2022-12-29 14:30:31 -05:00
@staticmethod
2022-12-17 16:18:16 -05:00
def rate_item_by_user ( item , user , rating_grade , visibility = 0 ) :
2022-12-17 17:18:08 -05:00
if rating_grade and ( rating_grade < 1 or rating_grade > 10 ) :
2022-12-29 14:30:31 -05:00
raise ValueError ( f " Invalid rating grade: { rating_grade } " )
2022-12-15 17:29:35 -05:00
rating = Rating . objects . filter ( owner = user , item = item ) . first ( )
2022-12-17 16:18:16 -05:00
if not rating_grade :
if rating :
rating . delete ( )
rating = None
elif rating is None :
2022-12-29 14:30:31 -05:00
rating = Rating . objects . create (
owner = user , item = item , grade = rating_grade , visibility = visibility
)
2022-12-17 16:18:16 -05:00
elif rating . grade != rating_grade or rating . visibility != visibility :
2022-12-15 17:29:35 -05:00
rating . visibility = visibility
rating . grade = rating_grade
rating . save ( )
2022-12-17 16:18:16 -05:00
return rating
2022-12-15 17:29:35 -05:00
2022-12-29 14:30:31 -05:00
@staticmethod
2022-12-15 17:29:35 -05:00
def get_item_rating_by_user ( item , user ) :
rating = Rating . objects . filter ( owner = user , item = item ) . first ( )
return rating . grade if rating else None
2022-12-14 21:12:37 -05:00
2022-12-15 17:29:35 -05:00
Item . rating = property ( Rating . get_rating_for_item )
Item . rating_count = property ( Rating . get_rating_count_for_item )
2022-12-14 21:12:37 -05:00
2022-12-20 11:32:44 -05:00
class Reply ( Piece ) :
2022-12-29 14:30:31 -05:00
reply_to_content = models . ForeignKey (
Piece , on_delete = models . SET_NULL , related_name = " replies " , null = True
)
2022-12-11 23:20:28 +00:00
title = models . CharField ( max_length = 500 , null = True )
body = MarkdownxField ( )
"""
List ( abstract class )
"""
2022-12-13 18:12:43 +00:00
list_add = django . dispatch . Signal ( )
list_remove = django . dispatch . Signal ( )
2022-12-11 23:20:28 +00:00
2022-12-12 16:46:37 +00:00
class List ( Piece ) :
2022-12-29 16:20:33 -05:00
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 (
default = timezone . now
) # auto_now_add=True FIXME revert this after migration
edited_time = models . DateTimeField (
default = timezone . now
) # auto_now=True FIXME revert this after migration
metadata = models . JSONField ( default = dict )
2022-12-11 23:20:28 +00:00
class Meta :
abstract = True
2022-12-13 06:44:29 +00:00
# MEMBER_CLASS = None # subclass must override this
2022-12-11 23:20:28 +00:00
# subclass must add this:
# items = models.ManyToManyField(Item, through='ListMember')
2022-12-28 01:09:55 -05:00
@property
2022-12-11 23:20:28 +00:00
def ordered_members ( self ) :
2022-12-29 14:30:31 -05:00
return self . members . all ( ) . order_by ( " position " )
2022-12-11 23:20:28 +00:00
2022-12-28 01:09:55 -05:00
@property
2022-12-11 23:20:28 +00:00
def ordered_items ( self ) :
2022-12-29 14:30:31 -05:00
return self . items . all ( ) . order_by (
self . MEMBER_CLASS . __name__ . lower ( ) + " __position "
)
2022-12-11 23:20:28 +00:00
2022-12-28 01:09:55 -05:00
@property
def recent_items ( self ) :
2022-12-29 14:30:31 -05:00
return self . items . all ( ) . order_by (
" - " + self . MEMBER_CLASS . __name__ . lower ( ) + " __created_time "
)
2022-12-28 01:09:55 -05:00
@property
def recent_members ( self ) :
2022-12-29 14:30:31 -05:00
return self . members . all ( ) . order_by ( " -created_time " )
2022-12-28 01:09:55 -05:00
2022-12-29 23:49:28 -05:00
def get_members_in_category ( self , item_category ) :
return self . members . all ( ) . filter ( query_item_category ( item_category ) )
2022-12-29 14:30:31 -05:00
def get_member_for_item ( self , item ) :
return self . members . filter ( item = item ) . first ( )
2022-12-11 23:20:28 +00:00
2023-01-16 14:03:27 -05:00
def get_summary ( self ) :
summary = { k : 0 for k in ItemCategory . values }
for c in self . recent_items :
summary [ c . category ] + = 1
return summary
2022-12-11 23:20:28 +00:00
def append_item ( self , item , * * params ) :
2023-01-08 22:10:48 -05:00
"""
named metadata fields should be specified directly , not in metadata dict !
e . g . collection . append_item ( item , note = " abc " ) works , but collection . append_item ( item , metadata = { " note " : " abc " } ) doesn ' t
"""
2023-01-16 14:03:27 -05:00
if item is None :
2022-12-11 23:20:28 +00:00
return None
2023-01-16 14:03:27 -05:00
member = self . get_member_for_item ( item )
if member :
2022-12-13 18:12:43 +00:00
return member
2023-01-16 14:03:27 -05:00
ml = self . ordered_members
p = { " parent " : self }
p . update ( params )
member = self . MEMBER_CLASS . objects . create (
owner = self . owner ,
position = ml . last ( ) . position + 1 if ml . count ( ) else 1 ,
item = item ,
* * p ,
)
list_add . send ( sender = self . __class__ , instance = self , item = item , member = member )
return member
2022-12-11 23:20:28 +00:00
def remove_item ( self , item ) :
2022-12-29 14:30:31 -05:00
member = self . get_member_for_item ( item )
2022-12-11 23:20:28 +00:00
if member :
2022-12-29 14:30:31 -05:00
list_remove . send (
sender = self . __class__ , instance = self , item = item , member = member
)
2022-12-11 23:20:28 +00:00
member . delete ( )
def move_up_item ( self , item ) :
members = self . ordered_members
2022-12-29 14:30:31 -05:00
member = self . get_member_for_item ( item )
2022-12-11 23:20:28 +00:00
if member :
other = members . filter ( position__lt = member . position ) . last ( )
if other :
p = other . position
other . position = member . position
member . position = p
other . save ( )
member . save ( )
def move_down_item ( self , item ) :
members = self . ordered_members
2022-12-29 14:30:31 -05:00
member = self . get_member_for_item ( item )
2022-12-11 23:20:28 +00:00
if member :
other = members . filter ( position__gt = member . position ) . first ( )
if other :
p = other . position
other . position = member . position
member . position = p
other . save ( )
member . save ( )
2022-12-29 14:30:31 -05:00
def update_item_metadata ( self , item , metadata ) :
member = self . get_member_for_item ( item )
if member :
member . metadata = metadata
member . save ( )
2022-12-11 23:20:28 +00:00
2022-12-13 06:44:29 +00:00
class ListMember ( Piece ) :
"""
ListMember - List class ' s member class
It ' s an abstract class, subclass must add this:
2022-12-27 14:52:03 -05:00
parent = models . ForeignKey ( ' List ' , related_name = ' members ' , on_delete = models . CASCADE )
2022-12-13 06:44:29 +00:00
"""
2022-12-29 14:30:31 -05:00
2022-12-29 16:20:33 -05:00
owner = models . ForeignKey ( User , on_delete = models . PROTECT )
visibility = models . PositiveSmallIntegerField (
default = 0
) # 0: Public / 1: Follower only / 2: Self only
2023-01-11 09:20:14 -05:00
created_time = models . DateTimeField ( default = timezone . now )
2022-12-29 16:20:33 -05:00
edited_time = models . DateTimeField (
default = timezone . now
) # auto_now=True FIXME revert this after migration
metadata = models . JSONField ( default = dict )
2022-12-11 23:20:28 +00:00
item = models . ForeignKey ( Item , on_delete = models . PROTECT )
position = models . PositiveIntegerField ( )
2022-12-27 14:52:03 -05:00
@cached_property
def mark ( self ) :
m = Mark ( self . owner , self . item )
return m
2022-12-11 23:20:28 +00:00
class Meta :
abstract = True
2022-12-13 18:12:43 +00:00
def __str__ ( self ) :
2022-12-29 14:30:31 -05:00
return f " { self . id } : { self . position } ( { self . item } ) "
2022-12-13 18:12:43 +00:00
2022-12-11 23:20:28 +00:00
"""
2022-12-13 18:12:43 +00:00
Shelf
2022-12-11 23:20:28 +00:00
"""
2022-12-13 18:12:43 +00:00
class ShelfType ( models . TextChoices ) :
2022-12-29 14:30:31 -05:00
WISHLIST = ( " wishlist " , " 未开始 " )
PROGRESS = ( " progress " , " 进行中 " )
COMPLETE = ( " complete " , " 完成 " )
2022-12-11 23:20:28 +00:00
# DISCARDED = ('discarded', '放弃')
2022-12-13 18:12:43 +00:00
ShelfTypeNames = [
2022-12-29 14:30:31 -05:00
[ ItemCategory . Book , ShelfType . WISHLIST , _ ( " 想读 " ) ] ,
[ ItemCategory . Book , ShelfType . PROGRESS , _ ( " 在读 " ) ] ,
[ ItemCategory . Book , ShelfType . COMPLETE , _ ( " 读过 " ) ] ,
[ ItemCategory . Movie , ShelfType . WISHLIST , _ ( " 想看 " ) ] ,
[ ItemCategory . Movie , ShelfType . PROGRESS , _ ( " 在看 " ) ] ,
[ ItemCategory . Movie , ShelfType . COMPLETE , _ ( " 看过 " ) ] ,
[ ItemCategory . TV , ShelfType . WISHLIST , _ ( " 想看 " ) ] ,
[ ItemCategory . TV , ShelfType . PROGRESS , _ ( " 在看 " ) ] ,
[ ItemCategory . TV , ShelfType . COMPLETE , _ ( " 看过 " ) ] ,
[ ItemCategory . Music , ShelfType . WISHLIST , _ ( " 想听 " ) ] ,
[ ItemCategory . Music , ShelfType . PROGRESS , _ ( " 在听 " ) ] ,
[ ItemCategory . Music , ShelfType . COMPLETE , _ ( " 听过 " ) ] ,
[ ItemCategory . Game , ShelfType . WISHLIST , _ ( " 想玩 " ) ] ,
[ ItemCategory . Game , ShelfType . PROGRESS , _ ( " 在玩 " ) ] ,
[ ItemCategory . Game , ShelfType . COMPLETE , _ ( " 玩过 " ) ] ,
2023-01-29 20:05:30 -05:00
[ ItemCategory . Podcast , ShelfType . WISHLIST , _ ( " 想听 " ) ] ,
[ ItemCategory . Podcast , ShelfType . PROGRESS , _ ( " 在听 " ) ] ,
[ ItemCategory . Podcast , ShelfType . COMPLETE , _ ( " 听过 " ) ] ,
2023-02-15 23:45:12 -05:00
[ ItemCategory . Performance , ShelfType . WISHLIST , _ ( " 想看 " ) ] ,
[ ItemCategory . Performance , ShelfType . PROGRESS , _ ( " " ) ] ,
[ ItemCategory . Performance , ShelfType . COMPLETE , _ ( " 看过 " ) ] ,
2022-12-11 23:20:28 +00:00
]
2022-12-13 18:12:43 +00:00
class ShelfMember ( ListMember ) :
2022-12-29 14:30:31 -05:00
parent = models . ForeignKey (
" Shelf " , related_name = " members " , on_delete = models . CASCADE
)
2022-12-11 23:20:28 +00:00
2023-01-12 16:08:10 -05:00
class Meta :
2023-01-23 16:31:30 -05:00
unique_together = [ [ " owner " , " item " ] ]
2023-04-21 18:27:49 -04:00
indexes = [
models . Index ( fields = [ " parent_id " , " visibility " , " created_time " ] ) ,
]
2023-01-07 12:00:09 -05:00
2023-01-01 01:07:32 -05:00
@cached_property
def mark ( self ) :
m = Mark ( self . owner , self . item )
m . shelfmember = self
return m
2022-12-11 23:20:28 +00:00
2022-12-13 18:12:43 +00:00
class Shelf ( List ) :
2022-12-11 23:20:28 +00:00
class Meta :
2022-12-29 23:49:28 -05:00
unique_together = [ [ " owner " , " shelf_type " ] ]
2022-12-11 23:20:28 +00:00
2022-12-13 18:12:43 +00:00
MEMBER_CLASS = ShelfMember
2022-12-29 14:30:31 -05:00
items = models . ManyToManyField ( Item , through = " ShelfMember " , related_name = " + " )
shelf_type = models . CharField (
choices = ShelfType . choices , max_length = 100 , null = False , blank = False
)
2022-12-11 23:20:28 +00:00
def __str__ ( self ) :
2022-12-29 23:49:28 -05:00
return f " { self . id } [ { self . owner } { self . shelf_type } list] "
2022-12-11 23:20:28 +00:00
2022-12-13 18:12:43 +00:00
class ShelfLogEntry ( models . Model ) :
2022-12-12 16:46:37 +00:00
owner = models . ForeignKey ( User , on_delete = models . PROTECT )
2023-01-11 00:57:58 -05:00
shelf_type = models . CharField ( choices = ShelfType . choices , max_length = 100 , null = True )
2022-12-11 23:20:28 +00:00
item = models . ForeignKey ( Item , on_delete = models . PROTECT )
2023-01-10 22:36:13 -05:00
timestamp = models . DateTimeField ( ) # this may later be changed by user
2022-12-11 23:20:28 +00:00
metadata = models . JSONField ( default = dict )
created_time = models . DateTimeField ( auto_now_add = True )
edited_time = models . DateTimeField ( auto_now = True )
def __str__ ( self ) :
2023-01-12 11:02:14 -05:00
return f " { self . owner } : { self . shelf_type } : { self . item . uuid } : { self . timestamp } : { self . metadata } "
2022-12-11 23:20:28 +00:00
2023-01-10 22:36:13 -05:00
@property
def action_label ( self ) :
2023-01-12 11:02:14 -05:00
if self . shelf_type :
return ShelfManager . get_action_label ( self . shelf_type , self . item . category )
2023-01-10 22:36:13 -05:00
else :
return _ ( " 移除标记 " )
2022-12-11 23:20:28 +00:00
2022-12-13 18:12:43 +00:00
class ShelfManager :
2022-12-11 23:20:28 +00:00
"""
2022-12-13 18:12:43 +00:00
ShelfManager
2022-12-11 23:20:28 +00:00
2022-12-13 18:12:43 +00:00
all shelf operations should go thru this class so that ShelfLogEntry can be properly populated
ShelfLogEntry can later be modified if user wish to change history
2022-12-11 23:20:28 +00:00
"""
def __init__ ( self , user ) :
self . owner = user
2022-12-29 23:49:28 -05:00
qs = Shelf . objects . filter ( owner = self . owner )
self . shelf_list = { v . shelf_type : v for v in qs }
if len ( self . shelf_list ) == 0 :
self . initialize ( )
2022-12-11 23:20:28 +00:00
def initialize ( self ) :
2022-12-29 23:49:28 -05:00
for qt in ShelfType :
self . shelf_list [ qt ] = Shelf . objects . create ( owner = self . owner , shelf_type = qt )
2022-12-11 23:20:28 +00:00
2022-12-29 23:49:28 -05:00
def locate_item ( self , item ) - > ShelfMember :
2023-01-24 19:02:56 -05:00
return ShelfMember . objects . filter ( item = item , owner = self . owner ) . first ( )
2022-12-11 23:20:28 +00:00
2022-12-13 18:12:43 +00:00
def move_item ( self , item , shelf_type , visibility = 0 , metadata = None ) :
# shelf_type=None means remove from current shelf
# metadata=None means no change
2022-12-11 23:20:28 +00:00
if not item :
2022-12-29 14:30:31 -05:00
raise ValueError ( " empty item " )
2022-12-17 16:18:16 -05:00
new_shelfmember = None
2022-12-29 23:49:28 -05:00
last_shelfmember = self . locate_item ( item )
2022-12-21 14:34:36 -05:00
last_shelf = last_shelfmember . parent if last_shelfmember else None
2022-12-16 01:08:10 -05:00
last_metadata = last_shelfmember . metadata if last_shelfmember else None
last_visibility = last_shelfmember . visibility if last_shelfmember else None
2022-12-29 23:49:28 -05:00
shelf = self . shelf_list [ shelf_type ] if shelf_type else None
2022-12-16 01:08:10 -05:00
changed = False
if last_shelf != shelf : # change shelf
changed = True
if last_shelf :
last_shelf . remove_item ( item )
2022-12-13 18:12:43 +00:00
if shelf :
2022-12-29 14:30:31 -05:00
new_shelfmember = shelf . append_item (
item , visibility = visibility , metadata = metadata or { }
)
2022-12-16 01:08:10 -05:00
elif last_shelf is None :
2022-12-29 14:30:31 -05:00
raise ValueError ( " empty shelf " )
2022-12-16 01:08:10 -05:00
else :
2022-12-17 16:18:16 -05:00
new_shelfmember = last_shelfmember
2022-12-16 01:08:10 -05:00
if metadata is not None and metadata != last_metadata : # change metadata
changed = True
last_shelfmember . metadata = metadata
last_shelfmember . visibility = visibility
last_shelfmember . save ( )
elif visibility != last_visibility : # change visibility
last_shelfmember . visibility = visibility
last_shelfmember . save ( )
if changed :
if metadata is None :
metadata = last_metadata or { }
2023-01-10 22:36:13 -05:00
log_time = (
2023-01-12 17:13:23 -05:00
new_shelfmember . created_time
if new_shelfmember and new_shelfmember != last_shelfmember
else timezone . now ( )
2023-01-10 22:36:13 -05:00
)
2022-12-29 14:30:31 -05:00
ShelfLogEntry . objects . create (
2023-01-11 00:57:58 -05:00
owner = self . owner ,
shelf_type = shelf_type ,
item = item ,
metadata = metadata ,
2023-01-10 22:36:13 -05:00
timestamp = log_time ,
2022-12-29 14:30:31 -05:00
)
2022-12-17 16:18:16 -05:00
return new_shelfmember
2022-12-11 23:20:28 +00:00
def get_log ( self ) :
2022-12-29 14:30:31 -05:00
return ShelfLogEntry . objects . filter ( owner = self . owner ) . order_by ( " timestamp " )
2022-12-11 23:20:28 +00:00
def get_log_for_item ( self , item ) :
2022-12-29 14:30:31 -05:00
return ShelfLogEntry . objects . filter ( owner = self . owner , item = item ) . order_by (
" timestamp "
)
2022-12-11 23:20:28 +00:00
2022-12-29 23:49:28 -05:00
def get_shelf ( self , shelf_type ) :
return self . shelf_list [ shelf_type ]
2022-12-11 23:20:28 +00:00
2022-12-29 23:49:28 -05:00
def get_members ( self , shelf_type , item_category ) :
return self . shelf_list [ shelf_type ] . get_members_in_category ( item_category )
# def get_items_on_shelf(self, item_category, shelf_type):
# shelf = (
# self.owner.shelf_set.all()
# .filter(item_category=item_category, shelf_type=shelf_type)
# .first()
# )
# return shelf.members.all().order_by
2023-01-10 22:36:13 -05:00
@classmethod
def get_action_label ( cls , shelf_type , item_category ) :
2022-12-29 23:49:28 -05:00
sts = [
n [ 2 ] for n in ShelfTypeNames if n [ 0 ] == item_category and n [ 1 ] == shelf_type
]
2023-01-09 02:59:59 -05:00
return sts [ 0 ] if sts else shelf_type
def get_label ( self , shelf_type , item_category ) :
ic = ItemCategory ( item_category ) . label
st = self . get_action_label ( shelf_type , item_category )
2022-12-29 23:49:28 -05:00
return _ ( " {shelf_label} 的 {item_category} " ) . format (
shelf_label = st , item_category = ic
2022-12-29 14:30:31 -05:00
)
2022-12-28 01:09:55 -05:00
2022-12-29 14:30:31 -05:00
@staticmethod
2022-12-13 06:44:29 +00:00
def get_manager_for_user ( user ) :
2022-12-13 18:12:43 +00:00
return ShelfManager ( user )
2022-12-13 06:44:29 +00:00
2023-04-20 13:36:12 -04:00
def get_calendar_data ( self , max_visiblity ) :
shelf_id = self . get_shelf ( ShelfType . COMPLETE ) . pk
2023-04-21 00:31:31 -04:00
timezone_offset = timezone . localtime ( timezone . now ( ) ) . strftime ( " % z " )
timezone_offset = timezone_offset [ : len ( timezone_offset ) - 2 ]
2023-04-20 13:36:12 -04:00
calendar_data = { }
2023-04-21 00:31:31 -04:00
sql = " SELECT to_char(DATE(journal_shelfmember.created_time::timestamp AT TIME ZONE %s ), ' YYYY-MM-DD ' ) AS dat, django_content_type.model typ, COUNT(1) count FROM journal_shelfmember, catalog_item, django_content_type WHERE journal_shelfmember.item_id = catalog_item.id AND django_content_type.id = catalog_item.polymorphic_ctype_id AND parent_id = %s AND journal_shelfmember.created_time >= NOW() - INTERVAL ' 366 days ' AND journal_shelfmember.visibility <= %s GROUP BY item_id, dat, typ; "
2023-04-20 13:36:12 -04:00
with connection . cursor ( ) as cursor :
2023-04-21 00:31:31 -04:00
cursor . execute ( sql , [ timezone_offset , shelf_id , int ( max_visiblity ) ] )
2023-04-20 13:36:12 -04:00
data = cursor . fetchall ( )
for line in data :
2023-04-21 00:31:31 -04:00
date = line [ 0 ]
2023-04-20 13:36:12 -04:00
typ = line [ 1 ]
if date not in calendar_data :
calendar_data [ date ] = { " items " : [ ] }
if typ [ : 2 ] == " tv " :
typ = " movie "
elif typ == " album " :
typ = " music "
elif typ == " edition " :
typ = " book "
elif typ not in [ " book " , " movie " , " music " , " game " ] :
typ = " other "
if typ not in calendar_data [ date ] [ " items " ] :
calendar_data [ date ] [ " items " ] . append ( typ )
return calendar_data
2022-12-13 06:44:29 +00:00
2022-12-13 18:12:43 +00:00
User . shelf_manager = cached_property ( ShelfManager . get_manager_for_user )
2022-12-29 14:30:31 -05:00
User . shelf_manager . __set_name__ ( User , " shelf_manager " )
2022-12-13 06:44:29 +00:00
2022-12-11 23:20:28 +00:00
"""
Collection
"""
class CollectionMember ( ListMember ) :
2022-12-29 14:30:31 -05:00
parent = models . ForeignKey (
" Collection " , related_name = " members " , on_delete = models . CASCADE
)
2022-12-11 23:20:28 +00:00
2022-12-29 14:30:31 -05:00
note = jsondata . CharField ( _ ( " 备注 " ) , null = True , blank = True )
2022-12-28 01:09:55 -05:00
2022-12-11 23:20:28 +00:00
2022-12-29 16:20:33 -05:00
_RE_HTML_TAG = re . compile ( r " <[^>]*> " )
2022-12-12 16:46:37 +00:00
class Collection ( List ) :
2022-12-29 14:30:31 -05:00
url_path = " collection "
2022-12-11 23:20:28 +00:00
MEMBER_CLASS = CollectionMember
2023-01-14 09:48:30 -05:00
catalog_item = models . OneToOneField (
CatalogCollection , on_delete = models . PROTECT , related_name = " journal_item "
)
2023-01-05 03:06:13 -05:00
title = models . CharField ( _ ( " 标题 " ) , max_length = 1000 , default = " " )
2022-12-12 16:46:37 +00:00
brief = models . TextField ( _ ( " 简介 " ) , blank = True , default = " " )
2022-12-29 14:30:31 -05:00
cover = models . ImageField (
2023-01-12 11:15:28 -05:00
upload_to = piece_cover_path , default = DEFAULT_ITEM_COVER , blank = True
2022-12-29 14:30:31 -05:00
)
items = models . ManyToManyField (
Item , through = " CollectionMember " , related_name = " collections "
)
collaborative = models . PositiveSmallIntegerField (
default = 0
) # 0: Editable by owner only / 1: Editable by bi-direction followers
2023-01-13 23:48:28 -05:00
featured_by_users = models . ManyToManyField (
to = User , related_name = " featured_collections " , through = " FeaturedCollection "
)
2022-12-11 23:20:28 +00:00
2022-12-28 01:09:55 -05:00
@property
def html ( self ) :
2023-01-30 17:12:25 -05:00
html = render_md ( self . brief )
2022-12-28 01:09:55 -05:00
return html
@property
2022-12-11 23:20:28 +00:00
def plain_description ( self ) :
2023-01-30 17:12:25 -05:00
html = render_md ( self . brief )
2022-12-29 16:20:33 -05:00
return _RE_HTML_TAG . sub ( " " , html )
2022-12-11 23:20:28 +00:00
2023-01-13 23:48:28 -05:00
def is_featured_by_user ( self , user ) :
return self . featured_by_users . all ( ) . filter ( id = user . id ) . exists ( )
def get_stats_for_user ( self , user ) :
items = list ( self . members . all ( ) . values_list ( " item_id " , flat = True ) )
stats = { " total " : len ( items ) }
for st , shelf in user . shelf_manager . shelf_list . items ( ) :
stats [ st ] = shelf . members . all ( ) . filter ( item_id__in = items ) . count ( )
stats [ " percentage " ] = (
round ( stats [ " complete " ] * 100 / stats [ " total " ] ) if stats [ " total " ] else 0
)
return stats
def get_progress_for_user ( self , user ) :
items = list ( self . members . all ( ) . values_list ( " item_id " , flat = True ) )
if len ( items ) == 0 :
return 0
shelf = user . shelf_manager . shelf_list [ " complete " ]
2023-01-14 09:02:53 -05:00
return round (
shelf . members . all ( ) . filter ( item_id__in = items ) . count ( ) * 100 / len ( items )
)
2023-01-13 23:48:28 -05:00
2022-12-12 16:46:37 +00:00
def save ( self , * args , * * kwargs ) :
2022-12-29 14:30:31 -05:00
if getattr ( self , " catalog_item " , None ) is None :
2022-12-12 16:46:37 +00:00
self . catalog_item = CatalogCollection ( )
2022-12-29 14:30:31 -05:00
if (
self . catalog_item . title != self . title
or self . catalog_item . brief != self . brief
) :
2022-12-12 16:46:37 +00:00
self . catalog_item . title = self . title
self . catalog_item . brief = self . brief
2022-12-21 14:34:36 -05:00
self . catalog_item . cover = self . cover
2022-12-12 16:46:37 +00:00
self . catalog_item . save ( )
super ( ) . save ( * args , * * kwargs )
2022-12-11 23:20:28 +00:00
2023-01-14 09:02:53 -05:00
class FeaturedCollection ( Piece ) :
2023-01-13 23:48:28 -05:00
owner = models . ForeignKey ( User , on_delete = models . CASCADE )
2023-01-14 09:02:53 -05:00
target = models . ForeignKey ( Collection , on_delete = models . CASCADE )
2023-01-13 23:48:28 -05:00
created_time = models . DateTimeField ( auto_now_add = True )
edited_time = models . DateTimeField ( auto_now = True )
class Meta :
2023-01-14 09:02:53 -05:00
unique_together = [ [ " owner " , " target " ] ]
@property
def visibility ( self ) :
return self . target . visibility
@cached_property
def progress ( self ) :
return self . target . get_progress_for_user ( self . owner )
2023-01-13 23:48:28 -05:00
2022-12-11 23:20:28 +00:00
"""
Tag
"""
class TagMember ( ListMember ) :
2022-12-29 14:30:31 -05:00
parent = models . ForeignKey ( " Tag " , related_name = " members " , on_delete = models . CASCADE )
2022-12-11 23:20:28 +00:00
2023-01-12 16:08:10 -05:00
class Meta :
unique_together = [ [ " parent " , " item " ] ]
2023-01-07 12:00:09 -05:00
2022-12-11 23:20:28 +00:00
2022-12-29 14:30:31 -05:00
TagValidators = [ RegexValidator ( regex = r " \ s+ " , inverse_match = True ) ]
2022-12-11 23:20:28 +00:00
class Tag ( List ) :
2022-12-12 16:46:37 +00:00
MEMBER_CLASS = TagMember
2022-12-29 14:30:31 -05:00
items = models . ManyToManyField ( Item , through = " TagMember " )
title = models . CharField (
max_length = 100 , null = False , blank = False , validators = TagValidators
)
2022-12-11 23:20:28 +00:00
# TODO case convert and space removal on save
# TODO check on save
class Meta :
2022-12-29 16:20:33 -05:00
unique_together = [ [ " owner " , " title " ] ]
2022-12-12 16:46:37 +00:00
2022-12-29 14:30:31 -05:00
@staticmethod
2023-02-09 15:25:44 -05:00
def cleanup_title ( title , replace = True ) :
t = title . strip ( ) . lower ( )
return " _ " if not title and replace else t
2022-12-12 16:46:37 +00:00
2022-12-15 17:29:35 -05:00
class TagManager :
2022-12-29 14:30:31 -05:00
@staticmethod
2022-12-12 16:46:37 +00:00
def public_tags_for_item ( item ) :
2022-12-29 14:30:31 -05:00
tags = (
item . tag_set . all ( )
. filter ( visibility = 0 )
. values ( " title " )
. annotate ( frequency = Count ( " owner " ) )
. order_by ( " -frequency " ) [ : 20 ]
)
return sorted ( list ( map ( lambda t : t [ " title " ] , tags ) ) )
2022-12-12 16:46:37 +00:00
2022-12-29 14:30:31 -05:00
@staticmethod
2022-12-12 16:46:37 +00:00
def all_tags_for_user ( user ) :
2022-12-29 14:30:31 -05:00
tags = (
user . tag_set . all ( )
. values ( " title " )
. annotate ( frequency = Count ( " members__id " ) )
. order_by ( " -frequency " )
)
return list ( map ( lambda t : t [ " title " ] , tags ) )
2022-12-12 16:46:37 +00:00
2022-12-29 14:30:31 -05:00
@staticmethod
2022-12-17 16:18:16 -05:00
def tag_item_by_user ( item , user , tag_titles , default_visibility = 0 ) :
titles = set ( [ Tag . cleanup_title ( tag_title ) for tag_title in tag_titles ] )
2022-12-29 14:30:31 -05:00
current_titles = set (
[ m . parent . title for m in TagMember . objects . filter ( owner = user , item = item ) ]
)
2022-12-17 16:18:16 -05:00
for title in titles - current_titles :
tag = Tag . objects . filter ( owner = user , title = title ) . first ( )
if not tag :
2022-12-29 14:30:31 -05:00
tag = Tag . objects . create (
owner = user , title = title , visibility = default_visibility
)
2022-12-17 16:18:16 -05:00
tag . append_item ( item )
for title in current_titles - titles :
tag = Tag . objects . filter ( owner = user , title = title ) . first ( )
tag . remove_item ( item )
2022-12-29 14:30:31 -05:00
@staticmethod
2022-12-23 00:08:42 -05:00
def get_item_tags_by_user ( item , user ) :
2022-12-29 14:30:31 -05:00
current_titles = [
m . parent . title for m in TagMember . objects . filter ( owner = user , item = item )
]
2022-12-23 00:08:42 -05:00
return current_titles
2022-12-29 14:30:31 -05:00
@staticmethod
2022-12-12 16:46:37 +00:00
def add_tag_by_user ( item , tag_title , user , default_visibility = 0 ) :
title = Tag . cleanup_title ( tag_title )
tag = Tag . objects . filter ( owner = user , title = title ) . first ( )
if not tag :
2022-12-29 14:30:31 -05:00
tag = Tag . objects . create (
owner = user , title = title , visibility = default_visibility
)
2022-12-12 16:46:37 +00:00
tag . append_item ( item )
2022-12-29 14:30:31 -05:00
@staticmethod
2022-12-15 17:29:35 -05:00
def get_manager_for_user ( user ) :
return TagManager ( user )
def __init__ ( self , user ) :
self . owner = user
2022-12-29 14:30:31 -05:00
@property
2022-12-15 17:29:35 -05:00
def all_tags ( self ) :
return TagManager . all_tags_for_user ( self . owner )
def add_item_tags ( self , item , tags , visibility = 0 ) :
for tag in tags :
TagManager . add_tag_by_user ( item , tag , self . owner , visibility )
2022-12-12 16:46:37 +00:00
2022-12-15 17:29:35 -05:00
def get_item_tags ( self , item ) :
2022-12-29 14:30:31 -05:00
return sorted (
[
m [ " parent__title " ]
for m in TagMember . objects . filter (
parent__owner = self . owner , item = item
) . values ( " parent__title " )
]
)
2022-12-15 17:29:35 -05:00
Item . tags = property ( TagManager . public_tags_for_item )
User . tags = property ( TagManager . all_tags_for_user )
User . tag_manager = cached_property ( TagManager . get_manager_for_user )
2022-12-29 14:30:31 -05:00
User . tag_manager . __set_name__ ( User , " tag_manager " )
2022-12-15 17:29:35 -05:00
class Mark :
2022-12-29 14:30:31 -05:00
""" this mimics previous mark behaviour """
2022-12-15 17:29:35 -05:00
def __init__ ( self , user , item ) :
self . owner = user
self . item = item
2022-12-29 14:30:31 -05:00
@cached_property
2022-12-15 17:29:35 -05:00
def shelfmember ( self ) :
return self . owner . shelf_manager . locate_item ( self . item )
2022-12-29 14:30:31 -05:00
@property
2022-12-15 17:29:35 -05:00
def id ( self ) :
2022-12-18 20:28:39 -05:00
return self . shelfmember . id if self . shelfmember else None
2022-12-15 17:29:35 -05:00
2022-12-29 14:30:31 -05:00
@property
2022-12-23 00:08:42 -05:00
def shelf ( self ) :
return self . shelfmember . parent if self . shelfmember else None
2022-12-29 14:30:31 -05:00
@property
2022-12-15 17:29:35 -05:00
def shelf_type ( self ) :
2022-12-21 14:34:36 -05:00
return self . shelfmember . parent . shelf_type if self . shelfmember else None
2022-12-15 17:29:35 -05:00
2023-01-09 02:59:59 -05:00
@property
def action_label ( self ) :
return (
self . owner . shelf_manager . get_action_label (
self . shelf_type , self . item . category
)
if self . shelfmember
else None
)
2022-12-29 14:30:31 -05:00
@property
2022-12-15 17:29:35 -05:00
def shelf_label ( self ) :
2022-12-29 23:49:28 -05:00
return (
2023-01-09 02:59:59 -05:00
self . owner . shelf_manager . get_label ( self . shelf_type , self . item . category )
2022-12-29 23:49:28 -05:00
if self . shelfmember
else None
)
2022-12-15 17:29:35 -05:00
2022-12-29 14:30:31 -05:00
@property
2022-12-16 01:08:10 -05:00
def created_time ( self ) :
return self . shelfmember . created_time if self . shelfmember else None
2022-12-29 14:30:31 -05:00
@property
2022-12-18 20:28:39 -05:00
def metadata ( self ) :
return self . shelfmember . metadata if self . shelfmember else None
2022-12-29 14:30:31 -05:00
@property
2022-12-15 17:29:35 -05:00
def visibility ( self ) :
2023-01-09 09:48:01 -05:00
return (
self . shelfmember . visibility
if self . shelfmember
else self . owner . get_preference ( ) . default_visibility
)
2022-12-15 17:29:35 -05:00
2022-12-29 14:30:31 -05:00
@cached_property
2022-12-15 17:29:35 -05:00
def tags ( self ) :
return self . owner . tag_manager . get_item_tags ( self . item )
2022-12-29 14:30:31 -05:00
@cached_property
2022-12-15 17:29:35 -05:00
def rating ( self ) :
return Rating . get_item_rating_by_user ( self . item , self . owner )
2022-12-29 14:30:31 -05:00
@cached_property
2022-12-15 17:29:35 -05:00
def comment ( self ) :
2023-01-31 21:21:50 -05:00
return Comment . objects . filter (
owner = self . owner , item = self . item , focus_item__isnull = True
) . first ( )
2022-12-15 17:29:35 -05:00
2022-12-29 14:30:31 -05:00
@property
2022-12-15 17:29:35 -05:00
def text ( self ) :
return self . comment . text if self . comment else None
2023-01-31 10:24:57 -05:00
@property
def comment_html ( self ) :
return self . comment . html if self . comment else None
2022-12-29 14:30:31 -05:00
@cached_property
2022-12-15 17:29:35 -05:00
def review ( self ) :
return Review . objects . filter ( owner = self . owner , item = self . item ) . first ( )
2022-12-29 14:30:31 -05:00
def update (
self ,
shelf_type ,
comment_text ,
rating_grade ,
visibility ,
metadata = None ,
created_time = None ,
share_to_mastodon = False ,
) :
share = (
share_to_mastodon
and shelf_type is not None
and (
shelf_type != self . shelf_type
or comment_text != self . text
or rating_grade != self . rating
)
)
2023-01-10 22:36:13 -05:00
share_as_new_post = shelf_type != self . shelf_type
2022-12-15 17:29:35 -05:00
if shelf_type != self . shelf_type or visibility != self . visibility :
2022-12-29 14:30:31 -05:00
self . shelfmember = self . owner . shelf_manager . move_item (
self . item , shelf_type , visibility = visibility , metadata = metadata
)
2023-01-10 22:36:13 -05:00
if self . shelfmember and created_time :
log = ShelfLogEntry . objects . filter (
owner = self . owner ,
item = self . item ,
2023-01-12 17:13:23 -05:00
timestamp = self . shelfmember . created_time ,
) . first ( )
2023-01-10 22:36:13 -05:00
self . shelfmember . created_time = created_time
self . shelfmember . save ( update_fields = [ " created_time " ] )
if log :
log . timestamp = created_time
log . save ( update_fields = [ " timestamp " ] )
else :
ShelfLogEntry . objects . create (
owner = self . owner ,
2023-01-12 17:13:23 -05:00
shelf_type = shelf_type ,
2023-01-10 22:36:13 -05:00
item = self . item ,
metadata = self . metadata ,
timestamp = created_time ,
)
2022-12-15 17:29:35 -05:00
if comment_text != self . text or visibility != self . visibility :
2022-12-29 14:30:31 -05:00
self . comment = Comment . comment_item_by_user (
self . item , self . owner , comment_text , visibility
)
2022-12-15 17:29:35 -05:00
if rating_grade != self . rating or visibility != self . visibility :
2022-12-18 20:28:39 -05:00
Rating . rate_item_by_user ( self . item , self . owner , rating_grade , visibility )
2022-12-15 17:29:35 -05:00
self . rating = rating_grade
2022-12-23 00:08:42 -05:00
if share :
# this is a bit hacky but let's keep it until move to implement ActivityPub,
# by then, we'll just change this to boost
from mastodon . api import share_mark
2022-12-29 14:30:31 -05:00
self . shared_link = (
self . shelfmember . metadata . get ( " shared_link " )
2023-01-10 22:36:13 -05:00
if self . shelfmember . metadata and not share_as_new_post
2022-12-29 14:30:31 -05:00
else None
)
2023-01-09 09:43:35 -05:00
self . translated_status = self . action_label
2022-12-23 00:08:42 -05:00
self . save = lambda * * args : None
if not share_mark ( self ) :
raise ValueError ( " sharing failed " )
2022-12-29 14:30:31 -05:00
if self . shelfmember . metadata . get ( " shared_link " ) != self . shared_link :
self . shelfmember . metadata [ " shared_link " ] = self . shared_link
2022-12-23 00:08:42 -05:00
self . shelfmember . save ( )
2023-01-10 22:36:13 -05:00
elif share_as_new_post and self . shelfmember :
self . shelfmember . metadata [ " shared_link " ] = None
self . shelfmember . save ( )
2022-12-25 13:45:24 -05:00
def delete ( self ) :
self . update ( None , None , None , 0 )
2023-01-01 01:07:32 -05:00
2023-01-10 22:36:13 -05:00
@property
def logs ( self ) :
return ShelfLogEntry . objects . filter ( owner = self . owner , item = self . item ) . order_by (
" timestamp "
)
2023-01-01 01:07:32 -05:00
def reset_visibility_for_user ( user : User , visibility : int ) :
ShelfMember . objects . filter ( owner = user ) . update ( visibility = visibility )
Comment . objects . filter ( owner = user ) . update ( visibility = visibility )
Rating . objects . filter ( owner = user ) . update ( visibility = visibility )
Review . objects . filter ( owner = user ) . update ( visibility = visibility )
2023-01-01 23:50:57 -05:00
def remove_data_by_user ( user : User ) :
ShelfMember . objects . filter ( owner = user ) . delete ( )
Comment . objects . filter ( owner = user ) . delete ( )
Rating . objects . filter ( owner = user ) . delete ( )
Review . objects . filter ( owner = user ) . delete ( )
2023-01-07 00:35:30 -05:00
def update_journal_for_merged_item ( legacy_item_uuid ) :
legacy_item = Item . get_by_url ( legacy_item_uuid )
if not legacy_item :
_logger . error ( " update_journal_for_merged_item: unable to find item " )
return
new_item = legacy_item . merged_to_item
2023-01-23 16:31:30 -05:00
for cls in list ( Content . __subclasses__ ( ) ) + list ( ListMember . __subclasses__ ( ) ) :
2023-01-07 00:35:30 -05:00
for p in cls . objects . filter ( item = legacy_item ) :
try :
p . item = new_item
p . save ( update_fields = [ " item_id " ] )
except :
2023-01-23 16:31:30 -05:00
_logger . warn (
f " deleted piece { p } when merging { cls . __name__ } : { legacy_item } -> { new_item } "
)
2023-01-07 00:35:30 -05:00
p . delete ( )