2023-08-10 11:27:31 -04:00
|
|
|
import re
|
|
|
|
from functools import cached_property
|
2024-05-27 15:44:12 -04:00
|
|
|
from typing import TYPE_CHECKING
|
2023-08-10 11:27:31 -04:00
|
|
|
|
2023-07-20 21:59:49 -04:00
|
|
|
from django.db import models
|
2023-08-10 11:27:31 -04:00
|
|
|
from django.utils.translation import gettext_lazy as _
|
|
|
|
|
|
|
|
from catalog.collection.models import Collection as CatalogCollection
|
|
|
|
from catalog.common import jsondata
|
|
|
|
from catalog.common.utils import DEFAULT_ITEM_COVER, piece_cover_path
|
|
|
|
from catalog.models import Item
|
2023-07-20 21:59:49 -04:00
|
|
|
from users.models import APIdentity
|
2023-08-10 11:27:31 -04:00
|
|
|
|
|
|
|
from .common import Piece
|
|
|
|
from .itemlist import List, ListMember
|
|
|
|
from .renderers import render_md
|
|
|
|
|
|
|
|
_RE_HTML_TAG = re.compile(r"<[^>]*>")
|
|
|
|
|
|
|
|
|
|
|
|
class CollectionMember(ListMember):
|
|
|
|
parent = models.ForeignKey(
|
|
|
|
"Collection", related_name="members", on_delete=models.CASCADE
|
|
|
|
)
|
|
|
|
|
2024-03-24 22:28:22 -04:00
|
|
|
note = jsondata.CharField(_("note"), null=True, blank=True)
|
2023-08-10 11:27:31 -04:00
|
|
|
|
2024-02-04 18:41:34 -05:00
|
|
|
@property
|
|
|
|
def ap_object(self):
|
|
|
|
return {
|
|
|
|
"id": self.absolute_url,
|
|
|
|
"type": "CollectionItem",
|
|
|
|
"collection": self.parent.absolute_url,
|
|
|
|
"published": self.created_time.isoformat(),
|
|
|
|
"updated": self.edited_time.isoformat(),
|
|
|
|
"attributedTo": self.owner.actor_uri,
|
|
|
|
"withRegardTo": self.item.absolute_url,
|
|
|
|
"note": self.note,
|
|
|
|
"href": self.absolute_url,
|
|
|
|
}
|
|
|
|
|
2023-08-10 11:27:31 -04:00
|
|
|
|
|
|
|
class Collection(List):
|
2024-05-27 15:44:12 -04:00
|
|
|
if TYPE_CHECKING:
|
|
|
|
members: models.QuerySet[CollectionMember]
|
2023-08-10 11:27:31 -04:00
|
|
|
url_path = "collection"
|
|
|
|
MEMBER_CLASS = CollectionMember
|
|
|
|
catalog_item = models.OneToOneField(
|
|
|
|
CatalogCollection, on_delete=models.PROTECT, related_name="journal_item"
|
|
|
|
)
|
2024-03-24 22:28:22 -04:00
|
|
|
title = models.CharField(_("title"), max_length=1000, default="")
|
|
|
|
brief = models.TextField(_("description"), blank=True, default="")
|
2023-08-10 11:27:31 -04:00
|
|
|
cover = models.ImageField(
|
|
|
|
upload_to=piece_cover_path, default=DEFAULT_ITEM_COVER, blank=True
|
|
|
|
)
|
|
|
|
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-07-20 21:59:49 -04:00
|
|
|
featured_by = models.ManyToManyField(
|
|
|
|
to=APIdentity, related_name="featured_collections", through="FeaturedCollection"
|
2023-08-10 11:27:31 -04:00
|
|
|
)
|
|
|
|
|
|
|
|
@property
|
2024-01-28 09:04:35 -05:00
|
|
|
def html_content(self):
|
2023-08-10 11:27:31 -04:00
|
|
|
html = render_md(self.brief)
|
|
|
|
return html
|
|
|
|
|
|
|
|
@property
|
2023-12-31 07:13:39 -05:00
|
|
|
def plain_content(self):
|
2023-08-10 11:27:31 -04:00
|
|
|
html = render_md(self.brief)
|
|
|
|
return _RE_HTML_TAG.sub(" ", html)
|
|
|
|
|
2023-07-20 21:59:49 -04:00
|
|
|
def featured_since(self, owner: APIdentity):
|
|
|
|
f = FeaturedCollection.objects.filter(target=self, owner=owner).first()
|
2023-08-10 11:27:31 -04:00
|
|
|
return f.created_time if f else None
|
|
|
|
|
2023-07-20 21:59:49 -04:00
|
|
|
def get_stats(self, owner: APIdentity):
|
2023-08-10 11:27:31 -04:00
|
|
|
items = list(self.members.all().values_list("item_id", flat=True))
|
|
|
|
stats = {"total": len(items)}
|
2023-07-20 21:59:49 -04:00
|
|
|
for st, shelf in owner.shelf_manager.shelf_list.items():
|
2023-08-10 11:27:31 -04:00
|
|
|
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
|
|
|
|
|
2023-07-20 21:59:49 -04:00
|
|
|
def get_progress(self, owner: APIdentity):
|
2023-08-10 11:27:31 -04:00
|
|
|
items = list(self.members.all().values_list("item_id", flat=True))
|
|
|
|
if len(items) == 0:
|
|
|
|
return 0
|
2023-07-20 21:59:49 -04:00
|
|
|
shelf = owner.shelf_manager.shelf_list["complete"]
|
2023-08-10 11:27:31 -04:00
|
|
|
return round(
|
|
|
|
shelf.members.all().filter(item_id__in=items).count() * 100 / len(items)
|
|
|
|
)
|
|
|
|
|
|
|
|
def save(self, *args, **kwargs):
|
2023-11-27 23:02:58 -05:00
|
|
|
from takahe.utils import Takahe
|
|
|
|
|
2023-08-10 11:27:31 -04:00
|
|
|
if getattr(self, "catalog_item", None) is None:
|
|
|
|
self.catalog_item = CatalogCollection()
|
|
|
|
if (
|
|
|
|
self.catalog_item.title != self.title
|
|
|
|
or self.catalog_item.brief != self.brief
|
|
|
|
):
|
|
|
|
self.catalog_item.title = self.title
|
|
|
|
self.catalog_item.brief = self.brief
|
2023-12-05 23:14:29 -05:00
|
|
|
self.catalog_item.cover = self.cover
|
2023-08-10 11:27:31 -04:00
|
|
|
self.catalog_item.save()
|
|
|
|
super().save(*args, **kwargs)
|
2023-11-27 23:02:58 -05:00
|
|
|
Takahe.post_collection(self)
|
|
|
|
|
2024-01-29 00:35:11 -05:00
|
|
|
def delete(self, *args, **kwargs):
|
|
|
|
existing_post = self.latest_post
|
|
|
|
if existing_post:
|
|
|
|
from takahe.utils import Takahe
|
|
|
|
|
|
|
|
Takahe.delete_posts([existing_post.pk])
|
|
|
|
return super().delete(*args, **kwargs)
|
|
|
|
|
2023-11-27 23:02:58 -05:00
|
|
|
@property
|
|
|
|
def ap_object(self):
|
|
|
|
return {
|
|
|
|
"id": self.absolute_url,
|
|
|
|
"type": "Collection",
|
|
|
|
"name": self.title,
|
|
|
|
"content": self.brief,
|
|
|
|
"mediaType": "text/markdown",
|
|
|
|
"published": self.created_time.isoformat(),
|
|
|
|
"updated": self.edited_time.isoformat(),
|
|
|
|
"attributedTo": self.owner.actor_uri,
|
|
|
|
"href": self.absolute_url,
|
|
|
|
}
|
2023-08-10 11:27:31 -04:00
|
|
|
|
|
|
|
|
|
|
|
class FeaturedCollection(Piece):
|
2023-07-20 21:59:49 -04:00
|
|
|
owner = models.ForeignKey(APIdentity, on_delete=models.CASCADE)
|
2023-08-10 11:27:31 -04:00
|
|
|
target = models.ForeignKey(Collection, on_delete=models.CASCADE)
|
|
|
|
created_time = models.DateTimeField(auto_now_add=True)
|
|
|
|
edited_time = models.DateTimeField(auto_now=True)
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
unique_together = [["owner", "target"]]
|
|
|
|
|
|
|
|
@property
|
|
|
|
def visibility(self):
|
|
|
|
return self.target.visibility
|
|
|
|
|
|
|
|
@cached_property
|
|
|
|
def progress(self):
|
2023-07-20 21:59:49 -04:00
|
|
|
return self.target.get_progress(self.owner)
|