feat(tags): show a character's fandom on its chip (truncated)
A character chip with a fandom only rendered a bare arrow. Surface the fandom NAME inline, truncated to 15 chars (full name in the tooltip). Resolve the name via a Tag self-join in both tag paths the modal uses — list_for_image (/api/images/<id>/tags) and gallery get_image_with_tags (/api/gallery/image/<id>) — so chips show the fandom on first open and after any reload. Falls back to the bare arrow when only fandom_id is known. Operator-asked 2026-06-09. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -172,6 +172,7 @@ async def list_tags_for_image(image_id: int):
|
||||
"name": t.name,
|
||||
"kind": t.kind.value,
|
||||
"fandom_id": t.fandom_id,
|
||||
"fandom_name": t.fandom_name,
|
||||
}
|
||||
for t in tags
|
||||
]
|
||||
|
||||
@@ -558,13 +558,26 @@ class GalleryService:
|
||||
record = await self.session.get(ImageRecord, image_id)
|
||||
if record is None:
|
||||
return None
|
||||
# Self-join Tag to resolve a character's fandom NAME (not just id) so the
|
||||
# modal chip can label it without an N+1 — mirrors list_for_image.
|
||||
fandom_alias = Tag.__table__.alias("fandom_lookup")
|
||||
tag_stmt = (
|
||||
select(Tag)
|
||||
.join(image_tag, image_tag.c.tag_id == Tag.id)
|
||||
select(
|
||||
Tag.id,
|
||||
Tag.name,
|
||||
Tag.kind,
|
||||
Tag.fandom_id,
|
||||
fandom_alias.c.name.label("fandom_name"),
|
||||
)
|
||||
.select_from(
|
||||
Tag.__table__
|
||||
.join(image_tag, image_tag.c.tag_id == Tag.id)
|
||||
.outerjoin(fandom_alias, Tag.fandom_id == fandom_alias.c.id)
|
||||
)
|
||||
.where(image_tag.c.image_record_id == image_id)
|
||||
.order_by(Tag.kind.asc(), Tag.name.asc())
|
||||
)
|
||||
tags = (await self.session.execute(tag_stmt)).scalars().all()
|
||||
tags = (await self.session.execute(tag_stmt)).all()
|
||||
# Fetch the canonical post.post_date for this image (if any) so
|
||||
# the modal can show "Posted on <date>" alongside import date.
|
||||
posted_at = None
|
||||
@@ -608,6 +621,7 @@ class GalleryService:
|
||||
"name": t.name,
|
||||
"kind": t.kind.value if hasattr(t.kind, "value") else t.kind,
|
||||
"fandom_id": t.fandom_id,
|
||||
"fandom_name": t.fandom_name,
|
||||
}
|
||||
for t in tags
|
||||
],
|
||||
|
||||
@@ -225,14 +225,29 @@ class TagService:
|
||||
)
|
||||
)
|
||||
|
||||
async def list_for_image(self, image_id: int) -> Sequence[Tag]:
|
||||
async def list_for_image(self, image_id: int) -> Sequence:
|
||||
"""Tags on an image, ordered (kind, name). Each row carries the fandom's
|
||||
NAME (not just fandom_id) via a self-join on Tag, so the UI can label a
|
||||
character chip with its fandom without an N+1 (mirrors the
|
||||
autocomplete/directory resolution)."""
|
||||
fandom_alias = Tag.__table__.alias("fandom_lookup")
|
||||
stmt = (
|
||||
select(Tag)
|
||||
.join(image_tag, image_tag.c.tag_id == Tag.id)
|
||||
select(
|
||||
Tag.id,
|
||||
Tag.name,
|
||||
Tag.kind,
|
||||
Tag.fandom_id,
|
||||
fandom_alias.c.name.label("fandom_name"),
|
||||
)
|
||||
.select_from(
|
||||
Tag.__table__
|
||||
.join(image_tag, image_tag.c.tag_id == Tag.id)
|
||||
.outerjoin(fandom_alias, Tag.fandom_id == fandom_alias.c.id)
|
||||
)
|
||||
.where(image_tag.c.image_record_id == image_id)
|
||||
.order_by(Tag.kind.asc(), Tag.name.asc())
|
||||
)
|
||||
return (await self.session.execute(stmt)).scalars().all()
|
||||
return (await self.session.execute(stmt)).all()
|
||||
|
||||
async def _keep_as_alias(self, tag_id: int) -> bool:
|
||||
"""A merged-away tag's old name must survive as an alias iff the ML
|
||||
|
||||
Reference in New Issue
Block a user