Library search upgrades: tag search, tag cloud, status filter
Backend: - MediaItem gains search_tags (custom + e621 tags, lowercase) and has_custom_data, maintained on save with a data migration backfill - File list search accepts search_type=filename|tags|both (tag search is word-AND across the flattened tag text) and status=matched|custom| unknown filters - New /api/tags/cloud/ endpoint (cached 2 min per guest/auth, invalidated on item changes and deletions) returning the most-used tags, honouring guest visibility Frontend: - Library sidebar: Filename/Tags/Both selector, status pill toggles (persisted), and a clickable tag cloud that runs a tag search - Roadmap updated
This commit is contained in:
@@ -4,6 +4,26 @@ from django.db import models
|
||||
import uuid
|
||||
|
||||
|
||||
TAG_CLOUD_CACHE_KEYS = [
|
||||
"j621.library.tag_cloud.auth",
|
||||
"j621.library.tag_cloud.guest",
|
||||
]
|
||||
|
||||
|
||||
def build_search_tags(tags, e621_data):
|
||||
"""Lowercase tag text (custom + e621) used for library tag search."""
|
||||
names = [str(tag) for tag in (tags or [])]
|
||||
data = e621_data if isinstance(e621_data, dict) else {}
|
||||
categories = data.get("tags")
|
||||
if isinstance(categories, dict):
|
||||
for values in categories.values():
|
||||
if isinstance(values, list):
|
||||
names.extend(str(tag) for tag in values)
|
||||
return " ".join(
|
||||
sorted({str(name).strip().lower() for name in names if str(name).strip()})
|
||||
)
|
||||
|
||||
|
||||
class MediaItem(models.Model):
|
||||
"""A logical media file, identified by its MD5 fingerprint."""
|
||||
|
||||
@@ -29,6 +49,9 @@ class MediaItem(models.Model):
|
||||
dhash = models.CharField(max_length=32, blank=True, default="", db_index=True)
|
||||
phash = models.CharField(max_length=32, blank=True, default="", db_index=True)
|
||||
whash = models.CharField(max_length=32, blank=True, default="", db_index=True)
|
||||
# Derived search/filter helpers: flattened tag text and custom-data flag.
|
||||
search_tags = models.TextField(blank=True, default="")
|
||||
has_custom_data = models.BooleanField(default=False, db_index=True)
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
|
||||
@@ -39,13 +62,22 @@ class MediaItem(models.Model):
|
||||
return f"J-{self.pk} ({self.md5})"
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
from django.core.cache import cache
|
||||
|
||||
from .guest_filter import item_is_hidden_for_guests
|
||||
|
||||
self.hidden_from_guests = item_is_hidden_for_guests(self)
|
||||
self.has_custom_data = bool(self.tags or self.notes)
|
||||
self.search_tags = build_search_tags(self.tags, self.e621_data)
|
||||
update_fields = kwargs.get("update_fields")
|
||||
if update_fields is not None:
|
||||
kwargs["update_fields"] = set(update_fields) | {"hidden_from_guests"}
|
||||
kwargs["update_fields"] = set(update_fields) | {
|
||||
"hidden_from_guests",
|
||||
"has_custom_data",
|
||||
"search_tags",
|
||||
}
|
||||
super().save(*args, **kwargs)
|
||||
cache.delete_many(TAG_CLOUD_CACHE_KEYS)
|
||||
|
||||
|
||||
class MediaLocation(models.Model):
|
||||
|
||||
Reference in New Issue
Block a user