lib.itmens/journal/models/mark.py

325 lines
12 KiB
Python
Raw Normal View History

import re
import uuid
2024-05-29 10:50:41 -04:00
from datetime import datetime
from functools import cached_property
2024-05-29 10:50:41 -04:00
from typing import Any
import django.dispatch
from django.conf import settings
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import PermissionDenied
from django.core.validators import MaxValueValidator, MinValueValidator, RegexValidator
from django.db import connection, models
from django.db.models import Avg, Count, Q
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
from markdownx.models import MarkdownxField
from polymorphic.models import PolymorphicModel
from catalog.collection.models import Collection as CatalogCollection
from catalog.common import jsondata
from catalog.common.models import Item, ItemCategory
from catalog.common.utils import DEFAULT_ITEM_COVER, piece_cover_path
from mastodon.api import boost_toot_later, share_mark
2023-07-20 21:59:49 -04:00
from takahe.utils import Takahe
from users.models import APIdentity
from .comment import Comment
2024-06-13 20:44:15 -04:00
from .note import Note
from .rating import Rating
from .review import Review
from .shelf import Shelf, ShelfLogEntry, ShelfManager, ShelfMember, ShelfType
class Mark:
"""
Holding Mark for an item on an shelf,
which is a combo object of ShelfMember, Comment, Rating and Tags.
it mimics previous mark behaviour.
"""
2023-07-20 21:59:49 -04:00
def __init__(self, owner: APIdentity, item: Item):
self.owner = owner
self.item = item
@cached_property
2024-05-27 15:44:12 -04:00
def shelfmember(self) -> ShelfMember | None:
return self.owner.shelf_manager.locate_item(self.item)
@property
def id(self) -> int | None:
return self.shelfmember.pk if self.shelfmember else None
@cached_property
def shelf(self) -> Shelf | None:
return self.shelfmember.parent if self.shelfmember else None
@property
def shelf_type(self) -> ShelfType | None:
return self.shelfmember.parent.shelf_type if self.shelfmember else None
@property
def action_label(self) -> str:
2023-07-20 21:59:49 -04:00
if self.shelfmember and self.shelf_type:
return ShelfManager.get_action_label(self.shelf_type, self.item.category)
if self.comment:
return ShelfManager.get_action_label(
ShelfType.PROGRESS, self.comment.item.category
)
return ""
2024-05-29 10:50:41 -04:00
@property
def status_label(self) -> str:
if self.shelfmember and self.shelf_type:
return ShelfManager.get_status_label(self.shelf_type, self.item.category)
if self.comment:
return ShelfManager.get_status_label(
ShelfType.PROGRESS, self.comment.item.category
)
return ""
2024-01-20 22:52:08 -05:00
@property
def action_label_for_feed(self) -> str:
2024-03-24 22:28:22 -04:00
return str(self.action_label)
2024-01-20 22:52:08 -05:00
2024-05-29 10:50:41 -04:00
def get_action_for_feed(self, item_link: str | None = None):
2024-04-03 23:10:21 -04:00
if self.shelfmember and self.shelf_type:
tpl = ShelfManager.get_action_template(self.shelf_type, self.item.category)
elif self.comment:
tpl = ShelfManager.get_action_template(
ShelfType.PROGRESS, self.comment.item.category
)
else:
tpl = ""
if item_link:
i = f'<a href="{item_link}">{self.item.display_title}</a>'
else:
i = self.item.display_title
return _(tpl).format(item=i)
@property
def shelf_label(self) -> str | None:
return (
ShelfManager.get_label(self.shelf_type, self.item.category)
2023-07-20 21:59:49 -04:00
if self.shelf_type
else None
)
2024-06-13 20:44:15 -04:00
@cached_property
def notes(self):
2024-06-15 18:26:20 -04:00
return Note.objects.filter(owner=self.owner, item=self.item).order_by(
"-created_time"
)
2024-06-13 20:44:15 -04:00
# post_ids = PiecePost.objects.filter(
# piece__note__owner_id=self.owner.pk, piece__note__item_id=self.item.pk
# ).values_list("post_id", flat=True)
# return Takahe.get_posts(list(post_ids))
@property
2024-05-29 10:50:41 -04:00
def created_time(self) -> datetime | None:
return self.shelfmember.created_time if self.shelfmember else None
@property
2024-05-29 10:50:41 -04:00
def metadata(self) -> dict[str, Any] | None:
return self.shelfmember.metadata if self.shelfmember else None
@property
def visibility(self) -> int:
2023-07-20 21:59:49 -04:00
if self.shelfmember:
return self.shelfmember.visibility
else:
2023-11-20 12:13:43 -05:00
# mark not created/saved yet, use user's default visibility
2023-08-20 21:46:53 +00:00
return self.owner.preference.default_visibility
@cached_property
def tags(self) -> list[str]:
return self.owner.tag_manager.get_item_tags(self.item)
2024-06-04 11:06:12 -04:00
@cached_property
def tag_text(self) -> str:
tags = [f"#{t}" for t in self.tags]
appending = self.owner.user.preference.mastodon_append_tag
if appending:
tags.append(appending)
tag_text = f"\n{' '.join(tags)}\n" if tags else ""
return tag_text
2023-07-20 21:59:49 -04:00
@cached_property
def rating(self):
return Rating.objects.filter(owner=self.owner, item=self.item).first()
@cached_property
def rating_grade(self) -> int | None:
2023-07-20 21:59:49 -04:00
return Rating.get_item_rating(self.item, self.owner)
@cached_property
def comment(self) -> Comment | None:
return Comment.objects.filter(owner=self.owner, item=self.item).first()
@property
def comment_text(self) -> str | None:
return (self.comment.text or None) if self.comment else None
@property
def comment_html(self) -> str | None:
return self.comment.html if self.comment else None
@cached_property
def review(self) -> Review | None:
return Review.objects.filter(owner=self.owner, item=self.item).first()
2023-11-20 01:59:26 -05:00
@property
def logs(self):
return ShelfLogEntry.objects.filter(owner=self.owner, item=self.item).order_by(
"timestamp"
)
"""
log entries
log entry will be created when item is added to shelf
log entry will be created when item is moved to another shelf
log entry will be created when item is removed from shelf (TODO change this to DEFERRED shelf)
timestamp of log entry will be updated whenever created_time of shelfmember is updated
any log entry can be deleted by user arbitrarily
posts
post will be created and set as current when item added to shelf
current post will be updated when comment or rating is updated
post will not be updated if only created_time is changed
post will be deleted, re-created and set as current if visibility changed
when item is moved to another shelf, a new post will be created
when item is removed from shelf, all post will be deleted
boost
post will be boosted to mastodon if user has mastodon token and site configured
"""
@property
def all_post_ids(self):
"""all post ids for this user and item"""
2023-11-20 12:13:43 -05:00
return self.logs.values_list("posts", flat=True)
2023-11-20 01:59:26 -05:00
@property
def current_post_ids(self):
2023-11-20 12:13:43 -05:00
"""all post ids for this user and item for its current status"""
return self.shelfmember.all_post_ids if self.shelfmember else []
2023-11-20 01:59:26 -05:00
@property
def latest_post_id(self):
2023-11-20 12:13:43 -05:00
"""latest post id for this user and item for its current status"""
return self.shelfmember.latest_post_id if self.shelfmember else None
2023-11-20 01:59:26 -05:00
def update(
self,
2023-11-20 01:59:26 -05:00
shelf_type: ShelfType | None,
2023-11-20 12:13:43 -05:00
comment_text: str | None = None,
rating_grade: int | None = None,
2024-06-04 10:12:04 -04:00
tags: list[str] | None = None,
2023-11-20 12:13:43 -05:00
visibility: int | None = None,
2024-05-29 10:50:41 -04:00
metadata: dict[str, Any] | None = None,
created_time: datetime | None = None,
share_to_mastodon: bool = False,
):
2023-11-20 01:59:26 -05:00
"""change shelf, comment or rating"""
if created_time and created_time >= timezone.now():
created_time = None
2023-11-20 12:13:43 -05:00
if visibility is None:
visibility = self.visibility
2023-11-20 01:59:26 -05:00
last_shelf_type = self.shelf_type
last_visibility = self.visibility if last_shelf_type else None
2024-06-04 10:12:04 -04:00
if tags is not None:
self.owner.tag_manager.tag_item(self.item, tags, visibility)
if shelf_type is None:
2023-11-20 01:59:26 -05:00
# take item off shelf
2024-05-27 15:44:12 -04:00
if self.shelfmember:
2023-11-20 01:59:26 -05:00
Takahe.delete_posts(self.shelfmember.all_post_ids)
self.shelfmember.log_and_delete()
2023-12-15 20:41:18 -05:00
if self.comment:
self.comment.delete()
if self.rating:
self.rating.delete()
2023-11-20 01:59:26 -05:00
return
# create/update shelf member and shelf log if necessary
2024-05-27 15:44:12 -04:00
if self.shelfmember and last_shelf_type == shelf_type:
2023-11-20 01:59:26 -05:00
shelfmember_changed = False
2023-11-20 12:13:43 -05:00
log_entry = self.shelfmember.ensure_log_entry()
if metadata is not None and metadata != self.shelfmember.metadata:
self.shelfmember.metadata = metadata
shelfmember_changed = True
2023-11-20 01:59:26 -05:00
if last_visibility != visibility:
self.shelfmember.visibility = visibility
shelfmember_changed = True
# retract most recent post about this status when visibility changed
2023-11-22 22:29:38 -05:00
if self.shelfmember.latest_post:
Takahe.delete_posts([self.shelfmember.latest_post.pk])
2023-11-20 01:59:26 -05:00
if created_time and created_time != self.shelfmember.created_time:
self.shelfmember.created_time = created_time
log_entry.timestamp = created_time
2023-12-17 22:21:29 -05:00
try:
log_entry.save(update_fields=["timestamp"])
2024-04-06 00:13:50 -04:00
except Exception:
2023-12-17 22:21:29 -05:00
log_entry.delete()
2023-11-20 01:59:26 -05:00
shelfmember_changed = True
if shelfmember_changed:
self.shelfmember.save()
else:
shelf = Shelf.objects.get(owner=self.owner, shelf_type=shelf_type)
d = {"parent": shelf, "visibility": visibility, "position": 0}
if metadata:
d["metadata"] = metadata
d["created_time"] = created_time or timezone.now()
2023-11-20 01:59:26 -05:00
self.shelfmember, _ = ShelfMember.objects.update_or_create(
owner=self.owner, item=self.item, defaults=d
)
2023-11-20 12:13:43 -05:00
self.shelfmember.ensure_log_entry()
2023-11-20 01:59:26 -05:00
self.shelfmember.clear_post_ids()
# create/update/detele comment if necessary
2023-11-20 12:13:43 -05:00
if comment_text is not None:
if comment_text != self.comment_text or visibility != last_visibility:
self.comment = Comment.comment_item(
self.item,
self.owner,
comment_text,
visibility,
self.shelfmember.created_time,
)
2023-11-20 01:59:26 -05:00
# create/update/detele rating if necessary
2023-11-20 12:13:43 -05:00
if rating_grade is not None:
if rating_grade != self.rating_grade or visibility != last_visibility:
Rating.update_item_rating(
self.item, self.owner, rating_grade, visibility
)
self.rating_grade = rating_grade
2023-11-20 01:59:26 -05:00
# publish a new or updated ActivityPub post
2024-06-15 23:38:33 -04:00
user = self.owner.user
2023-11-20 12:13:43 -05:00
post_as_new = shelf_type != last_shelf_type or visibility != last_visibility
2024-06-16 21:54:20 -04:00
classic_crosspost = user.preference.mastodon_repost_mode == 1
2023-12-09 23:22:03 -05:00
append = (
2024-06-15 23:38:33 -04:00
f"@{user.mastodon_acct}\n"
2024-06-16 21:54:20 -04:00
if visibility > 0 and share_to_mastodon and not classic_crosspost
2023-12-09 23:22:03 -05:00
else ""
)
post = Takahe.post_mark(self, post_as_new, append)
2024-06-15 23:38:33 -04:00
if post and self.item.category in (user.preference.auto_bookmark_cats or []):
if shelf_type == ShelfType.PROGRESS:
Takahe.bookmark(post.pk, self.owner.pk)
2023-11-20 01:59:26 -05:00
# async boost to mastodon
if post and share_to_mastodon:
2024-06-16 21:54:20 -04:00
if classic_crosspost:
2023-12-15 20:53:27 -05:00
share_mark(self, post_as_new)
else:
2024-06-15 23:38:33 -04:00
boost_toot_later(user, post.url)
2023-11-20 01:59:26 -05:00
return True
2023-07-20 21:59:49 -04:00
2024-06-04 10:12:04 -04:00
def delete(self, keep_tags=False):
self.update(None, tags=None if keep_tags else [])
2024-05-29 10:50:41 -04:00
def delete_log(self, log_id: int):
ShelfLogEntry.objects.filter(
owner=self.owner, item=self.item, id=log_id
).delete()
def delete_all_logs(self):
self.logs.delete()