lib.itmens/journal/models/tag.py

173 lines
5.5 KiB
Python
Raw Normal View History

import re
2024-06-03 00:14:34 -04:00
from datetime import timedelta
2024-05-27 15:44:12 -04:00
from typing import TYPE_CHECKING
from django.core.validators import RegexValidator
from django.db import models
from django.db.models import Count, F
2024-06-03 00:14:34 -04:00
from django.utils import timezone
from catalog.models import Item
2023-07-20 21:59:49 -04:00
from users.models import APIdentity
from .itemlist import List, ListMember
class TagMember(ListMember):
2024-05-27 15:44:12 -04:00
if TYPE_CHECKING:
parent: models.ForeignKey["TagMember", "Tag"]
parent = models.ForeignKey("Tag", related_name="members", on_delete=models.CASCADE)
class Meta:
unique_together = [["parent", "item"]]
2024-02-04 18:41:34 -05:00
@property
def ap_object(self):
return {
"id": self.absolute_url,
"type": "Tag",
"tag": self.parent.title,
"published": self.created_time.isoformat(),
"updated": self.edited_time.isoformat(),
"attributedTo": self.owner.actor_uri,
"withRegardTo": self.item.absolute_url,
"href": self.absolute_url,
}
2024-12-30 01:51:19 -05:00
def to_indexable_doc(self):
return {}
TagValidators = [RegexValidator(regex=r"\s+", inverse_match=True)]
class Tag(List):
MEMBER_CLASS = TagMember
items = models.ManyToManyField(Item, through="TagMember")
title = models.CharField(
max_length=100, null=False, blank=False, validators=TagValidators
)
2024-06-04 16:51:51 -04:00
pinned = models.BooleanField(default=False, null=True)
class Meta:
unique_together = [["owner", "title"]]
2024-06-04 20:49:23 -04:00
indexes = [models.Index(fields=["owner", "pinned"])]
@staticmethod
def cleanup_title(title, replace=True):
2025-02-22 18:48:41 -05:00
t = re.sub(r"\s+", " ", title.rstrip().lstrip("# "))[:100]
2024-06-04 11:06:12 -04:00
return "_" if not t and replace else t
@staticmethod
2024-07-19 08:33:42 -04:00
def deep_cleanup_title(title, default="_"):
"""Remove all non-word characters, only for public index purpose"""
2024-07-19 08:33:42 -04:00
return re.sub(r"\W+", " ", title).rstrip().lstrip("# ").lower()[:100] or default
2024-06-04 16:51:51 -04:00
def update(
self, title: str, visibility: int | None = None, pinned: bool | None = None
):
old_title = Tag.deep_cleanup_title(self.title)
new_title = Tag.deep_cleanup_title(title)
was_pinned = bool(self.pinned)
if visibility is not None:
self.visibility = 2 if visibility else 0
if pinned is not None:
self.pinned = pinned
self.title = title
self.save()
if was_pinned != self.pinned or (old_title != new_title and self.pinned):
from takahe.utils import Takahe
if was_pinned:
Takahe.unpin_hashtag_for_user(self.owner.pk, old_title)
if self.pinned:
Takahe.pin_hashtag_for_user(self.owner.pk, new_title)
2024-12-30 01:51:19 -05:00
def to_indexable_doc(self):
return {}
class TagManager:
@staticmethod
def indexable_tags_for_item(item):
tags = (
item.tag_set.all()
.filter(visibility=0)
.values("title")
.annotate(frequency=Count("owner"))
.order_by("-frequency")[:20]
)
tag_titles = sorted(
[
t
for t in set(map(lambda t: Tag.deep_cleanup_title(t["title"]), tags))
2024-06-04 11:06:12 -04:00
if t and t != "_"
]
)
return tag_titles
@staticmethod
2024-06-04 10:12:04 -04:00
def tag_item_for_owner(
2023-07-20 21:59:49 -04:00
owner: APIdentity,
2024-06-04 10:12:04 -04:00
item: Item,
2023-07-20 21:59:49 -04:00
tag_titles: list[str],
default_visibility: int = 0,
):
titles = set([Tag.cleanup_title(tag_title) for tag_title in tag_titles])
current_titles = set(
2023-07-20 21:59:49 -04:00
[m.parent.title for m in TagMember.objects.filter(owner=owner, item=item)]
)
for title in titles - current_titles:
2023-07-20 21:59:49 -04:00
tag = Tag.objects.filter(owner=owner, title=title).first()
if not tag:
tag = Tag.objects.create(
2023-07-20 21:59:49 -04:00
owner=owner, title=title, visibility=default_visibility
)
tag.append_item(item, visibility=default_visibility)
for title in current_titles - titles:
2023-07-20 21:59:49 -04:00
tag = Tag.objects.filter(owner=owner, title=title).first()
if tag:
tag.remove_item(item)
2024-06-04 10:12:04 -04:00
def tag_item(self, item: Item, tag_titles: list[str], default_visibility: int = 0):
TagManager.tag_item_for_owner(self.owner, item, tag_titles, default_visibility)
@staticmethod
2023-07-20 21:59:49 -04:00
def get_manager_for_user(owner):
return TagManager(owner)
2023-07-20 21:59:49 -04:00
def __init__(self, owner):
self.owner = owner
2024-06-04 20:49:23 -04:00
def get_tags(self, public_only=False, pinned_only=False):
tags = self.owner.tag_set.all()
tags = tags.annotate(total=Count("members")).order_by("-total")
if public_only:
tags = tags.filter(visibility=0)
if pinned_only:
tags = tags.filter(pinned=True)
return tags
2024-06-03 00:14:34 -04:00
@staticmethod
2024-07-15 16:49:21 -04:00
def popular_tags(days: int = 30, local_only: bool = False):
2024-06-03 00:14:34 -04:00
t = timezone.now() - timedelta(days=days)
2024-07-15 16:49:21 -04:00
tags = (
2024-06-03 00:14:34 -04:00
TagMember.objects.filter(created_time__gt=t)
.filter(parent__visibility=0)
.annotate(title=F("parent__title"))
.values("title")
.annotate(total=Count("parent_id", distinct=True))
.order_by("-total")
)
2024-07-15 16:49:21 -04:00
if local_only:
tags = tags.filter(local=True)
titles = tags.values_list("title", flat=True)
2024-06-03 07:27:44 -04:00
return titles
2024-06-03 00:14:34 -04:00
2023-07-20 21:59:49 -04:00
def get_item_tags(self, item: Item):
return sorted(
2024-06-04 10:12:04 -04:00
TagMember.objects.filter(parent__owner=self.owner, item=item).values_list(
"parent__title", flat=True
)
)