2023-07-20 21:59:49 -04:00
from datetime import datetime
2023-08-10 11:27:31 -04:00
from functools import cached_property
2025-01-11 17:20:02 -05:00
from typing import TYPE_CHECKING , Any
2023-08-10 11:27:31 -04:00
2024-07-03 16:42:20 -04:00
from django . conf import settings
2023-08-10 11:27:31 -04:00
from django . db import connection , models
from django . utils import timezone
from django . utils . translation import gettext_lazy as _
2023-07-20 21:59:49 -04:00
from loguru import logger
2025-03-08 16:37:59 -05:00
from polymorphic . models import PolymorphicManager
2023-08-10 11:27:31 -04:00
from catalog . models import Item , ItemCategory
2024-06-13 20:44:15 -04:00
from takahe . utils import Takahe
2023-07-20 21:59:49 -04:00
from users . models import APIdentity
2023-08-10 11:27:31 -04:00
2023-07-20 21:59:49 -04:00
from . common import q_item_in_category
2023-08-10 11:27:31 -04:00
from . itemlist import List , ListMember
2024-07-05 16:26:26 -04:00
from . renderers import render_post_with_macro , render_rating , render_spoiler_text
2023-08-10 11:27:31 -04:00
if TYPE_CHECKING :
2024-07-03 16:42:20 -04:00
from . comment import Comment
2023-08-10 11:27:31 -04:00
from . mark import Mark
2024-07-03 16:42:20 -04:00
from . rating import Rating
2023-08-10 11:27:31 -04:00
class ShelfType ( models . TextChoices ) :
2024-05-29 09:49:13 -04:00
WISHLIST = " wishlist " , _ ( " WISHLIST " ) # type:ignore[reportCallIssue]
PROGRESS = " progress " , _ ( " PROGRESS " ) # type:ignore[reportCallIssue]
COMPLETE = " complete " , _ ( " COMPLETE " ) # type:ignore[reportCallIssue]
DROPPED = " dropped " , _ ( " DROPPED " ) # type:ignore[reportCallIssue]
2023-08-10 11:27:31 -04:00
2024-04-03 23:10:21 -04:00
_REVIEWED = " reviewed "
_SHELF_LABELS = [
[
ItemCategory . Book ,
ShelfType . WISHLIST ,
2024-05-29 10:50:41 -04:00
_ ( " books to read " ) , # shelf label
_ ( " want to read " ) , # action label
_ ( " wants to read {item} " ) , # feed
_ ( " to read " ) , # status label
2024-04-03 23:10:21 -04:00
] ,
[
ItemCategory . Book ,
ShelfType . PROGRESS ,
_ ( " books reading " ) ,
_ ( " start reading " ) ,
_ ( " started reading {item} " ) ,
2024-05-29 10:50:41 -04:00
_ ( " reading " ) ,
2024-04-03 23:10:21 -04:00
] ,
[
ItemCategory . Book ,
ShelfType . COMPLETE ,
_ ( " books completed " ) ,
_ ( " finish reading " ) ,
_ ( " finished reading {item} " ) ,
2024-05-29 10:50:41 -04:00
_ ( " read " ) ,
2024-04-03 23:10:21 -04:00
] ,
[
ItemCategory . Book ,
ShelfType . DROPPED ,
_ ( " books dropped " ) ,
_ ( " stop reading " ) ,
_ ( " stopped reading {item} " ) ,
2024-05-29 10:50:41 -04:00
_ ( " stopped reading " ) ,
2024-04-03 23:10:21 -04:00
] ,
[
ItemCategory . Book ,
_REVIEWED ,
_ ( " books reviewed " ) ,
_ ( " review " ) ,
_ ( " wrote a review of {item} " ) ,
2024-05-29 10:50:41 -04:00
" " ,
2024-04-03 23:10:21 -04:00
] ,
[
ItemCategory . Movie ,
ShelfType . WISHLIST ,
_ ( " movies to watch " ) ,
_ ( " want to watch " ) ,
_ ( " wants to watch {item} " ) ,
2024-05-29 10:50:41 -04:00
_ ( " to watch " ) ,
2024-04-03 23:10:21 -04:00
] ,
[
ItemCategory . Movie ,
ShelfType . PROGRESS ,
_ ( " movies watching " ) ,
_ ( " start watching " ) ,
_ ( " started watching {item} " ) ,
2024-05-29 10:50:41 -04:00
_ ( " watching " ) ,
2024-04-03 23:10:21 -04:00
] ,
[
ItemCategory . Movie ,
ShelfType . COMPLETE ,
_ ( " movies watched " ) ,
_ ( " finish watching " ) ,
_ ( " finished watching {item} " ) ,
2024-05-29 10:50:41 -04:00
_ ( " watched " ) ,
2024-04-03 23:10:21 -04:00
] ,
[
ItemCategory . Movie ,
ShelfType . DROPPED ,
_ ( " movies dropped " ) ,
_ ( " stop watching " ) ,
_ ( " stopped watching {item} " ) ,
2024-05-29 10:50:41 -04:00
_ ( " stopped watching " ) ,
2024-04-03 23:10:21 -04:00
] ,
[
ItemCategory . Movie ,
_REVIEWED ,
_ ( " movies reviewed " ) ,
_ ( " review " ) ,
_ ( " wrote a review of {item} " ) ,
2024-05-29 10:50:41 -04:00
" " ,
2024-04-03 23:10:21 -04:00
] ,
[
ItemCategory . TV ,
ShelfType . WISHLIST ,
_ ( " TV shows to watch " ) ,
_ ( " want to watch " ) ,
_ ( " wants to watch {item} " ) ,
2024-05-29 10:50:41 -04:00
_ ( " to watch " ) ,
2024-04-03 23:10:21 -04:00
] ,
[
ItemCategory . TV ,
ShelfType . PROGRESS ,
_ ( " TV shows watching " ) ,
_ ( " start watching " ) ,
_ ( " started watching {item} " ) ,
2024-05-29 10:50:41 -04:00
_ ( " watching " ) ,
2024-04-03 23:10:21 -04:00
] ,
[
ItemCategory . TV ,
ShelfType . COMPLETE ,
_ ( " TV shows watched " ) ,
_ ( " finish watching " ) ,
_ ( " finished watching {item} " ) ,
2024-05-29 10:50:41 -04:00
_ ( " watched " ) ,
2024-04-03 23:10:21 -04:00
] ,
[
ItemCategory . TV ,
ShelfType . DROPPED ,
_ ( " TV shows dropped " ) ,
_ ( " stop watching " ) ,
_ ( " stopped watching {item} " ) ,
2024-05-29 10:50:41 -04:00
_ ( " stopped watching " ) ,
2024-04-03 23:10:21 -04:00
] ,
[
ItemCategory . TV ,
_REVIEWED ,
_ ( " TV shows reviewed " ) ,
_ ( " review " ) ,
_ ( " wrote a review of {item} " ) ,
2024-05-29 10:50:41 -04:00
" " ,
2024-04-03 23:10:21 -04:00
] ,
[
ItemCategory . Music ,
ShelfType . WISHLIST ,
_ ( " albums to listen " ) ,
_ ( " want to listen " ) ,
_ ( " wants to listen {item} " ) ,
2024-05-29 10:50:41 -04:00
_ ( " to listen " ) ,
2024-04-03 23:10:21 -04:00
] ,
[
ItemCategory . Music ,
ShelfType . PROGRESS ,
_ ( " albums listening " ) ,
_ ( " start listening " ) ,
_ ( " started listening {item} " ) ,
2024-05-29 10:50:41 -04:00
_ ( " listening " ) ,
2024-04-03 23:10:21 -04:00
] ,
[
ItemCategory . Music ,
ShelfType . COMPLETE ,
2024-04-07 00:43:58 -04:00
_ ( " albums listened " ) ,
2024-04-03 23:10:21 -04:00
_ ( " finish listening " ) ,
_ ( " finished listening {item} " ) ,
2024-05-29 10:50:41 -04:00
_ ( " listened " ) ,
2024-04-03 23:10:21 -04:00
] ,
[
ItemCategory . Music ,
ShelfType . DROPPED ,
_ ( " albums dropped " ) ,
_ ( " stop listening " ) ,
_ ( " stopped listening {item} " ) ,
2024-05-29 10:50:41 -04:00
_ ( " stopped listening " ) ,
2024-04-03 23:10:21 -04:00
] ,
[
ItemCategory . Music ,
_REVIEWED ,
_ ( " albums reviewed " ) ,
_ ( " review " ) ,
_ ( " wrote a review of {item} " ) ,
2024-05-29 10:50:41 -04:00
" " ,
2024-04-03 23:10:21 -04:00
] ,
[
ItemCategory . Game ,
ShelfType . WISHLIST ,
_ ( " games to play " ) ,
_ ( " want to play " ) ,
_ ( " wants to play {item} " ) ,
2024-05-29 10:50:41 -04:00
_ ( " to play " ) ,
2024-04-03 23:10:21 -04:00
] ,
[
ItemCategory . Game ,
ShelfType . PROGRESS ,
_ ( " games playing " ) ,
_ ( " start playing " ) ,
_ ( " started playing {item} " ) ,
2024-05-29 10:50:41 -04:00
_ ( " playing " ) ,
2024-04-03 23:10:21 -04:00
] ,
[
ItemCategory . Game ,
ShelfType . COMPLETE ,
_ ( " games played " ) ,
_ ( " finish playing " ) ,
_ ( " finished playing {item} " ) ,
2024-05-29 10:50:41 -04:00
_ ( " played " ) ,
2024-04-03 23:10:21 -04:00
] ,
[
ItemCategory . Game ,
ShelfType . DROPPED ,
_ ( " games dropped " ) ,
_ ( " stop playing " ) ,
_ ( " stopped playing {item} " ) ,
2024-05-29 10:50:41 -04:00
_ ( " stopped playing " ) ,
2024-04-03 23:10:21 -04:00
] ,
[
ItemCategory . Game ,
_REVIEWED ,
_ ( " games reviewed " ) ,
_ ( " review " ) ,
_ ( " wrote a review of {item} " ) ,
2024-05-29 10:50:41 -04:00
" " ,
2024-04-03 23:10:21 -04:00
] ,
[
ItemCategory . Podcast ,
ShelfType . WISHLIST ,
_ ( " podcasts to listen " ) ,
_ ( " want to listen " ) ,
_ ( " wants to listen {item} " ) ,
2024-05-29 10:50:41 -04:00
_ ( " to listen " ) ,
2024-04-03 23:10:21 -04:00
] ,
[
ItemCategory . Podcast ,
ShelfType . PROGRESS ,
_ ( " podcasts listening " ) ,
_ ( " start listening " ) ,
_ ( " started listening {item} " ) ,
2024-05-29 10:50:41 -04:00
_ ( " listening " ) ,
2024-04-03 23:10:21 -04:00
] ,
[
ItemCategory . Podcast ,
ShelfType . COMPLETE ,
_ ( " podcasts listened " ) ,
_ ( " finish listening " ) ,
_ ( " finished listening {item} " ) ,
2024-05-29 10:50:41 -04:00
_ ( " listened " ) ,
2024-04-03 23:10:21 -04:00
] ,
[
ItemCategory . Podcast ,
ShelfType . DROPPED ,
_ ( " podcasts dropped " ) ,
_ ( " stop listening " ) ,
_ ( " stopped listening {item} " ) ,
2024-05-29 10:50:41 -04:00
_ ( " stopped listening " ) ,
2024-04-03 23:10:21 -04:00
] ,
[
ItemCategory . Podcast ,
_REVIEWED ,
_ ( " podcasts reviewed " ) ,
_ ( " review " ) ,
_ ( " wrote a review of {item} " ) ,
2024-05-29 10:50:41 -04:00
" " ,
2024-04-03 23:10:21 -04:00
] ,
[
ItemCategory . Performance ,
ShelfType . WISHLIST ,
_ ( " performances to see " ) ,
_ ( " want to see " ) ,
_ ( " wants to see {item} " ) ,
2024-05-29 10:50:41 -04:00
_ ( " to see " ) ,
2024-04-03 23:10:21 -04:00
] ,
2023-08-10 11:27:31 -04:00
# disable progress shelf for Performance
2024-05-29 10:50:41 -04:00
[ ItemCategory . Performance , ShelfType . PROGRESS , " " , " " , " " , " " ] ,
2024-04-03 23:10:21 -04:00
[
ItemCategory . Performance ,
ShelfType . COMPLETE ,
_ ( " performances saw " ) ,
_ ( " finish seeing " ) ,
_ ( " finished seeing {item} " ) ,
2024-05-29 10:50:41 -04:00
_ ( " seen " ) ,
2024-04-03 23:10:21 -04:00
] ,
[
ItemCategory . Performance ,
ShelfType . DROPPED ,
_ ( " performances dropped " ) ,
_ ( " stop seeing " ) ,
_ ( " stopped seeing {item} " ) ,
2024-05-29 10:50:41 -04:00
_ ( " stopped seeing " ) ,
2024-04-03 23:10:21 -04:00
] ,
[
ItemCategory . Performance ,
_REVIEWED ,
_ ( " performances reviewed " ) ,
_ ( " review " ) ,
_ ( " wrote a review of {item} " ) ,
2024-05-29 10:50:41 -04:00
" " ,
2024-04-03 23:10:21 -04:00
] ,
2023-08-10 11:27:31 -04:00
]
2024-03-24 22:28:22 -04:00
# grammatically problematic, for translation only
2023-08-10 11:27:31 -04:00
2025-03-08 16:37:59 -05:00
class ShelfMemberManager ( PolymorphicManager ) :
def get_queryset ( self ) :
from . comment import Comment
from . rating import Rating
rating_subquery = Rating . objects . filter (
owner_id = models . OuterRef ( " owner_id " ) , item_id = models . OuterRef ( " item_id " )
) . values ( " grade " ) [ : 1 ]
comment_subquery = Comment . objects . filter (
owner_id = models . OuterRef ( " owner_id " ) , item_id = models . OuterRef ( " item_id " )
) . values ( " text " ) [ : 1 ]
return (
super ( )
. get_queryset ( )
. annotate (
_rating_grade = models . Subquery ( rating_subquery ) ,
_comment_text = models . Subquery ( comment_subquery ) ,
_shelf_type = models . F ( " parent__shelf_type " ) ,
)
)
2023-08-10 11:27:31 -04:00
class ShelfMember ( ListMember ) :
2024-05-27 15:44:12 -04:00
if TYPE_CHECKING :
parent : models . ForeignKey [ " ShelfMember " , " Shelf " ]
2023-08-10 11:27:31 -04:00
parent = models . ForeignKey (
" Shelf " , related_name = " members " , on_delete = models . CASCADE
)
2025-03-08 16:37:59 -05:00
objects = ShelfMemberManager ( )
2023-08-10 11:27:31 -04:00
class Meta :
unique_together = [ [ " owner " , " item " ] ]
indexes = [
models . Index ( fields = [ " parent_id " , " visibility " , " created_time " ] ) ,
]
2023-07-20 21:59:49 -04:00
@property
def ap_object ( self ) :
return {
" id " : self . absolute_url ,
" type " : " Status " ,
" status " : self . parent . shelf_type ,
" published " : self . created_time . isoformat ( ) ,
" updated " : self . edited_time . isoformat ( ) ,
" attributedTo " : self . owner . actor_uri ,
2023-11-28 08:45:04 -05:00
" withRegardTo " : self . item . absolute_url ,
2023-11-17 22:46:31 -05:00
" href " : self . absolute_url ,
2023-07-20 21:59:49 -04:00
}
@classmethod
2025-01-19 16:04:22 -05:00
def update_by_ap_object (
cls , owner : APIdentity , item : Item , obj : dict , post , crosspost = None
) :
2025-02-03 17:20:35 -05:00
if post . local : # ignore local user updating their post via Mastodon API
return
2023-11-20 19:11:02 -05:00
p = cls . objects . filter ( owner = owner , item = item ) . first ( )
if p and p . edited_time > = datetime . fromisoformat ( obj [ " updated " ] ) :
return p # incoming ap object is older than what we have, no update needed
2023-07-20 21:59:49 -04:00
shelf = owner . shelf_manager . get_shelf ( obj [ " status " ] )
if not shelf :
logger . warning ( f " unable to locate shelf for { owner } , { obj } " )
return
d = {
" parent " : shelf ,
" position " : 0 ,
" local " : False ,
2024-06-13 20:44:15 -04:00
" visibility " : Takahe . visibility_t2n ( post . visibility ) ,
2023-07-20 21:59:49 -04:00
" created_time " : datetime . fromisoformat ( obj [ " published " ] ) ,
" edited_time " : datetime . fromisoformat ( obj [ " updated " ] ) ,
}
p , _ = cls . objects . update_or_create ( owner = owner , item = item , defaults = d )
2024-06-13 20:44:15 -04:00
p . link_post_id ( post . id )
2023-07-20 21:59:49 -04:00
return p
2024-07-03 16:42:20 -04:00
def get_crosspost_postfix ( self ) :
tags = render_post_with_macro (
self . owner . user . preference . mastodon_append_tag , self . item
)
return " \n " + tags if tags else " "
def get_crosspost_template ( self ) :
return _ ( ShelfManager . get_action_template ( self . shelf_type , self . item . category ) )
def to_crosspost_params ( self ) :
2024-07-05 16:26:26 -04:00
action = self . get_crosspost_template ( ) . format ( item = " ##obj## " )
2024-07-03 16:42:20 -04:00
if self . sibling_comment :
2024-07-05 16:26:26 -04:00
spoiler , txt = render_spoiler_text ( self . sibling_comment . text , self . item )
2024-07-03 16:42:20 -04:00
else :
spoiler , txt = None , " "
2024-07-05 16:26:26 -04:00
rating = self . sibling_rating . grade if self . sibling_rating else " "
txt = " \n " + txt if txt else " "
content = f " { action } ##rating## \n ##obj_link_if_plain## { txt } { self . get_crosspost_postfix ( ) } "
params = {
" content " : content ,
" spoiler_text " : spoiler ,
" obj " : self . item ,
" rating " : rating ,
}
2024-07-03 16:42:20 -04:00
return params
def to_post_params ( self ) :
item_link = f " { settings . SITE_INFO [ ' site_url ' ] } /~neodb~ { self . item . url } "
action = self . get_crosspost_template ( ) . format (
item = f ' <a href= " { item_link } " > { self . item . display_title } </a> '
)
if self . sibling_comment :
2024-07-05 16:26:26 -04:00
spoiler , txt = render_spoiler_text ( self . sibling_comment . text , self . item )
2024-07-03 16:42:20 -04:00
else :
spoiler , txt = None , " "
postfix = self . get_crosspost_postfix ( )
# add @user.mastodon.handle so that user can see it on Mastodon ?
# if self.visibility and self.owner.user.mastodon:
# postfix += f" @{self.owner.user.mastodon.handle}"
2024-07-05 16:26:26 -04:00
content = f " { render_rating ( self . sibling_rating . grade ) if self . sibling_rating else ' ' } \n { txt } \n { postfix } "
2024-07-03 16:42:20 -04:00
return {
" prepend_content " : action ,
" content " : content ,
" summary " : spoiler ,
" sensitive " : spoiler is not None ,
}
def get_ap_data ( self ) :
data = super ( ) . get_ap_data ( )
if self . sibling_comment :
data [ " object " ] [ " relatedWith " ] . append ( self . sibling_comment . ap_object )
if self . sibling_rating :
data [ " object " ] [ " relatedWith " ] . append ( self . sibling_rating . ap_object )
return data
2024-07-15 16:56:10 -04:00
def sync_to_timeline ( self , update_mode : int = 0 ) :
post = super ( ) . sync_to_timeline ( update_mode )
if post and self . sibling_comment :
self . sibling_comment . link_post_id ( post . id )
return post
2024-12-30 01:51:19 -05:00
def to_indexable_doc ( self ) - > dict [ str , Any ] :
ids = [ self . pk ]
classes = [ self . __class__ . __name__ ]
content = [ ]
rating = 0
if self . sibling_rating :
# ids.append(self.sibling_rating.pk)
classes . append ( " Rating " )
rating = self . sibling_rating . grade
if self . sibling_comment :
# ids.append(self.sibling_comment.pk)
classes . append ( " Comment " )
content = [ self . sibling_comment . text ]
return {
" piece_id " : ids ,
" piece_class " : classes ,
" item_id " : [ self . item . id ] ,
" item_class " : [ self . item . __class__ . __name__ ] ,
" item_title " : self . item . to_indexable_titles ( ) ,
" shelf_type " : self . shelf_type ,
" rating " : rating ,
" tag " : self . tags ,
" content " : content ,
}
2025-03-08 16:37:59 -05:00
def save ( self , * args , * * kwargs ) :
try :
del self . _shelf_type # type:ignore
del self . _rating_grade # type:ignore
del self . _comment_text # type:ignore
except AttributeError :
pass
return super ( ) . save ( * args , * * kwargs )
2024-07-03 16:42:20 -04:00
@cached_property
def sibling_comment ( self ) - > " Comment | None " :
from . comment import Comment
return Comment . objects . filter ( owner = self . owner , item = self . item ) . first ( )
@cached_property
def sibling_rating ( self ) - > " Rating | None " :
from . rating import Rating
return Rating . objects . filter ( owner = self . owner , item = self . item ) . first ( )
2023-08-10 11:27:31 -04:00
@cached_property
def mark ( self ) - > " Mark " :
from . mark import Mark
m = Mark ( self . owner , self . item )
m . shelfmember = self
return m
@property
def shelf_label ( self ) - > str | None :
2025-03-08 16:37:59 -05:00
return ShelfManager . get_label ( self . shelf_type , self . item . category )
2023-08-10 11:27:31 -04:00
@property
def shelf_type ( self ) :
2025-03-08 16:37:59 -05:00
try :
return getattr ( self , " _shelf_type " )
except AttributeError :
return self . parent . shelf_type
2023-08-10 11:27:31 -04:00
@property
def rating_grade ( self ) :
2025-03-08 16:37:59 -05:00
try :
return getattr ( self , " _rating_grade " )
except AttributeError :
return self . mark . rating_grade
2023-08-10 11:27:31 -04:00
@property
def comment_text ( self ) :
2025-03-08 16:37:59 -05:00
try :
return getattr ( self , " _comment_text " )
except AttributeError :
return self . mark . comment_text
2023-08-10 11:27:31 -04:00
@property
def tags ( self ) :
return self . mark . tags
2023-11-20 12:13:43 -05:00
def ensure_log_entry ( self ) :
log , _ = ShelfLogEntry . objects . get_or_create (
2023-11-20 01:59:26 -05:00
owner = self . owner ,
shelf_type = self . shelf_type ,
item = self . item ,
timestamp = self . created_time ,
)
2023-11-20 12:13:43 -05:00
return log
2023-11-20 01:59:26 -05:00
def log_and_delete ( self ) :
2023-11-22 22:29:38 -05:00
ShelfLogEntry . objects . get_or_create (
2023-11-20 01:59:26 -05:00
owner = self . owner ,
shelf_type = None ,
item = self . item ,
timestamp = timezone . now ( ) ,
)
self . delete ( )
def link_post_id ( self , post_id : int ) :
2023-11-20 19:11:02 -05:00
if self . local :
self . ensure_log_entry ( ) . link_post_id ( post_id )
2023-11-20 01:59:26 -05:00
return super ( ) . link_post_id ( post_id )
2023-08-10 11:27:31 -04:00
class Shelf ( List ) :
"""
Shelf
"""
2024-05-27 15:44:12 -04:00
if TYPE_CHECKING :
members : models . QuerySet [ ShelfMember ]
2023-08-10 11:27:31 -04:00
class Meta :
unique_together = [ [ " owner " , " shelf_type " ] ]
MEMBER_CLASS = ShelfMember
items = models . ManyToManyField ( Item , through = " ShelfMember " , related_name = " + " )
shelf_type = models . CharField (
choices = ShelfType . choices , max_length = 100 , null = False , blank = False
)
def __str__ ( self ) :
2023-12-25 09:58:18 -05:00
return f " Shelf: { self . owner . username } : { self . shelf_type } "
2023-08-10 11:27:31 -04:00
2024-12-30 01:51:19 -05:00
def to_indexable_doc ( self ) - > dict [ str , Any ] :
return { }
2023-08-10 11:27:31 -04:00
class ShelfLogEntry ( models . Model ) :
2023-07-20 21:59:49 -04:00
owner = models . ForeignKey ( APIdentity , on_delete = models . PROTECT )
2023-08-10 11:27:31 -04:00
shelf_type = models . CharField ( choices = ShelfType . choices , max_length = 100 , null = True )
item = models . ForeignKey ( Item , on_delete = models . PROTECT )
timestamp = models . DateTimeField ( ) # this may later be changed by user
created_time = models . DateTimeField ( auto_now_add = True )
edited_time = models . DateTimeField ( auto_now = True )
2023-11-20 01:59:26 -05:00
posts = models . ManyToManyField (
" takahe.Post " , related_name = " log_entries " , through = " ShelfLogEntryPost "
)
2023-08-10 11:27:31 -04:00
2023-11-22 22:29:38 -05:00
class Meta :
constraints = [
models . UniqueConstraint (
fields = [ " owner " , " item " , " timestamp " , " shelf_type " ] ,
name = " unique_shelf_log_entry " ,
) ,
]
2023-08-10 11:27:31 -04:00
def __str__ ( self ) :
2023-12-24 18:04:55 -05:00
return f " LOG: { self . owner } : { self . shelf_type } : { self . item . uuid } : { self . timestamp } "
2023-08-10 11:27:31 -04:00
@property
def action_label ( self ) :
if self . shelf_type :
return ShelfManager . get_action_label ( self . shelf_type , self . item . category )
else :
2024-03-24 22:28:22 -04:00
return _ ( " removed mark " )
2023-08-10 11:27:31 -04:00
2023-11-20 01:59:26 -05:00
def link_post_id ( self , post_id : int ) :
ShelfLogEntryPost . objects . get_or_create ( log_entry = self , post_id = post_id )
2024-07-03 16:42:20 -04:00
def all_post_ids ( self ) :
return ShelfLogEntryPost . objects . filter ( log_entry = self ) . values_list (
" post_id " , flat = True
)
2023-11-20 01:59:26 -05:00
class ShelfLogEntryPost ( models . Model ) :
log_entry = models . ForeignKey ( ShelfLogEntry , on_delete = models . CASCADE )
post = models . ForeignKey (
" takahe.Post " , db_constraint = False , db_index = True , on_delete = models . CASCADE
)
class Meta :
constraints = [
models . UniqueConstraint (
fields = [ " log_entry " , " post " ] , name = " unique_log_entry_post "
) ,
]
2023-08-10 11:27:31 -04:00
class ShelfManager :
"""
ShelfManager
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
"""
2023-07-20 21:59:49 -04:00
def __init__ ( self , owner ) :
self . owner = owner
2023-08-10 11:27:31 -04:00
qs = Shelf . objects . filter ( owner = self . owner )
self . shelf_list = { v . shelf_type : v for v in qs }
2024-01-20 22:52:08 -05:00
if len ( self . shelf_list ) < len ( ShelfType ) :
2023-08-10 11:27:31 -04:00
self . initialize ( )
def initialize ( self ) :
for qt in ShelfType :
2024-01-20 22:52:08 -05:00
self . shelf_list [ qt ] = Shelf . objects . get_or_create (
owner = self . owner , shelf_type = qt
) [ 0 ]
2023-08-10 11:27:31 -04:00
2023-07-20 21:59:49 -04:00
def locate_item ( self , item : Item ) - > ShelfMember | None :
2023-08-10 11:27:31 -04:00
return ShelfMember . objects . filter ( item = item , owner = self . owner ) . first ( )
2023-07-20 21:59:49 -04:00
def get_log_for_item ( self , item : Item ) :
2023-08-10 11:27:31 -04:00
return ShelfLogEntry . objects . filter ( owner = self . owner , item = item ) . order_by (
" timestamp "
)
2023-07-20 21:59:49 -04:00
def get_shelf ( self , shelf_type : ShelfType ) :
2023-08-10 11:27:31 -04:00
return self . shelf_list [ shelf_type ]
2023-07-20 21:59:49 -04:00
def get_latest_members (
self , shelf_type : ShelfType , item_category : ItemCategory | None = None
) :
2023-08-10 11:27:31 -04:00
qs = self . shelf_list [ shelf_type ] . members . all ( ) . order_by ( " -created_time " )
if item_category :
2023-07-20 21:59:49 -04:00
return qs . filter ( q_item_in_category ( item_category ) )
2023-08-10 11:27:31 -04:00
else :
return qs
2023-12-30 22:20:15 -05:00
def get_members (
self , shelf_type : ShelfType , item_category : ItemCategory | None = None
) :
qs = self . shelf_list [ shelf_type ] . members . all ( )
if item_category :
return qs . filter ( q_item_in_category ( item_category ) )
else :
return qs
2023-08-10 11:27:31 -04:00
# 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
2024-04-03 23:10:21 -04:00
@classmethod
def get_labels_for_category ( cls , item_category : ItemCategory ) :
return [ ( n [ 1 ] , n [ 2 ] ) for n in _SHELF_LABELS if n [ 0 ] == item_category ]
@classmethod
def get_actions_for_category ( cls , item_category : ItemCategory ) :
return [
( n [ 1 ] , n [ 3 ] )
for n in _SHELF_LABELS
if n [ 0 ] == item_category and n [ 1 ] != _REVIEWED
]
2024-05-29 10:50:41 -04:00
@classmethod
def get_statuses_for_category ( cls , item_category : ItemCategory ) :
return [
( n [ 1 ] , n [ 5 ] )
for n in _SHELF_LABELS
if n [ 0 ] == item_category and n [ 1 ] != _REVIEWED
]
2024-04-03 23:10:21 -04:00
@classmethod
def get_label ( cls , shelf_type : ShelfType | str , item_category : ItemCategory ) - > str :
st = str ( shelf_type )
sts = [ n [ 2 ] for n in _SHELF_LABELS if n [ 0 ] == item_category and n [ 1 ] == st ]
return sts [ 0 ] if sts else st
2023-08-10 11:27:31 -04:00
@classmethod
2023-07-20 21:59:49 -04:00
def get_action_label (
2023-08-26 01:27:18 +00:00
cls , shelf_type : ShelfType | str , item_category : ItemCategory
2023-07-20 21:59:49 -04:00
) - > str :
2023-08-26 01:27:18 +00:00
st = str ( shelf_type )
2024-04-03 23:10:21 -04:00
sts = [ n [ 3 ] for n in _SHELF_LABELS if n [ 0 ] == item_category and n [ 1 ] == st ]
2023-08-26 01:27:18 +00:00
return sts [ 0 ] if sts else st
2023-08-10 11:27:31 -04:00
@classmethod
2024-05-29 10:50:41 -04:00
def get_status_label (
cls , shelf_type : ShelfType | str , item_category : ItemCategory
) - > str :
st = str ( shelf_type )
sts = [ n [ 5 ] for n in _SHELF_LABELS if n [ 0 ] == item_category and n [ 1 ] == st ]
return sts [ 0 ] if sts else st
@classmethod
2024-04-03 23:10:21 -04:00
def get_action_template (
cls , shelf_type : ShelfType | str , item_category : ItemCategory
) - > str :
st = str ( shelf_type )
sts = [ n [ 4 ] for n in _SHELF_LABELS if n [ 0 ] == item_category and n [ 1 ] == st ]
return sts [ 0 ] if sts else st
2023-08-10 11:27:31 -04:00
@staticmethod
2023-07-20 21:59:49 -04:00
def get_manager_for_user ( owner : APIdentity ) :
return ShelfManager ( owner )
2023-08-10 11:27:31 -04:00
2023-07-20 21:59:49 -04:00
def get_calendar_data ( self , max_visiblity : int ) :
2023-08-10 11:27:31 -04:00
shelf_id = self . get_shelf ( ShelfType . COMPLETE ) . pk
timezone_offset = timezone . localtime ( timezone . now ( ) ) . strftime ( " % z " )
timezone_offset = timezone_offset [ : len ( timezone_offset ) - 2 ]
calendar_data = { }
2023-11-22 22:06:19 -05:00
queries = [
(
" 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; " ,
[ timezone_offset , shelf_id , int ( max_visiblity ) ] ,
) ,
(
2025-02-03 17:26:50 -05:00
" SELECT to_char(DATE(journal_comment.created_time::timestamp AT TIME ZONE %s ), ' YYYY-MM-DD ' ) AS dat, django_content_type.model typ, COUNT(1) count FROM journal_comment, catalog_item, django_content_type WHERE journal_comment.owner_id = %s AND journal_comment.item_id = catalog_item.id AND django_content_type.id = catalog_item.polymorphic_ctype_id AND journal_comment.created_time >= NOW() - INTERVAL ' 366 days ' AND journal_comment.visibility <= %s AND django_content_type.model in ( ' tvepisode ' , ' podcastepisode ' ) GROUP BY item_id, dat, typ; " ,
2023-11-23 14:02:12 +00:00
[ timezone_offset , self . owner . id , int ( max_visiblity ) ] ,
2023-11-22 22:06:19 -05:00
) ,
]
for sql , params in queries :
with connection . cursor ( ) as cursor :
cursor . execute ( sql , params )
data = cursor . fetchall ( )
for line in data :
date = line [ 0 ]
typ = line [ 1 ]
if date not in calendar_data :
calendar_data [ date ] = { " items " : [ ] }
if typ [ : 2 ] == " tv " :
typ = " tv "
elif typ [ : 7 ] == " podcast " :
typ = " podcast "
elif typ == " album " :
typ = " music "
elif typ == " edition " :
typ = " book "
elif typ not in [
" book " ,
" movie " ,
" tv " ,
" music " ,
" game " ,
" podcast " ,
" performance " ,
] :
typ = " other "
if typ not in calendar_data [ date ] [ " items " ] :
calendar_data [ date ] [ " items " ] . append ( typ )
2023-08-10 11:27:31 -04:00
return calendar_data