Fandom count fix + Explore 3-pane workspace + modal rail layout #131
@@ -55,21 +55,20 @@ class TagDirectoryService:
|
||||
fandom = aliased(Tag)
|
||||
member = aliased(Tag)
|
||||
# image_count aggregates the tag's images INCLUDING, for a fandom, every
|
||||
# image carrying one of its characters (member.fandom_id == Tag.id) — the
|
||||
# member subquery is empty for non-fandom tags, so they stay direct-only.
|
||||
# DISTINCT so an image with both the fandom and ≥1 of its characters
|
||||
# counts once; a correlated scalar subquery keeps it 1 row per tag with
|
||||
# no group_by (the fandom self-join below is 1:1).
|
||||
# image carrying one of its characters. `member` is the tag actually on
|
||||
# the image; an image counts for the outer Tag if that applied tag IS the
|
||||
# Tag (direct) OR is a character whose fandom_id is the Tag (the fandom
|
||||
# leg). Both legs correlate the OUTER Tag.id at a SINGLE level — a nested
|
||||
# `tag_id IN (SELECT ... WHERE member.fandom_id == Tag.id)` does NOT
|
||||
# correlate and silently counts every fandom-character globally (~every
|
||||
# tag collapses to the same inflated number). DISTINCT so an image with
|
||||
# the fandom AND ≥1 of its characters counts once; a correlated scalar
|
||||
# subquery keeps it 1 row per tag with no group_by.
|
||||
count_col = (
|
||||
select(func.count(image_tag.c.image_record_id.distinct()))
|
||||
.where(
|
||||
or_(
|
||||
image_tag.c.tag_id == Tag.id,
|
||||
image_tag.c.tag_id.in_(
|
||||
select(member.id).where(member.fandom_id == Tag.id)
|
||||
),
|
||||
)
|
||||
)
|
||||
.select_from(image_tag)
|
||||
.join(member, member.id == image_tag.c.tag_id)
|
||||
.where(or_(image_tag.c.tag_id == Tag.id, member.fandom_id == Tag.id))
|
||||
.correlate(Tag)
|
||||
.scalar_subquery()
|
||||
.label("image_count")
|
||||
|
||||
@@ -47,20 +47,35 @@ async def test_directory_fandom_counts_and_previews_via_characters(db):
|
||||
char = Tag(name="Ichigo", kind=TagKind.character, fandom_id=fandom.id)
|
||||
db.add(char)
|
||||
await db.flush()
|
||||
# A SECOND, unrelated fandom + character + image. The count for "Bleach"
|
||||
# must NOT pick this up — guards against the count's fandom leg failing to
|
||||
# correlate the outer tag and counting EVERY fandom-character globally
|
||||
# (which inflates every card to the same number).
|
||||
other_fandom = Tag(name="Naruto", kind=TagKind.fandom)
|
||||
db.add(other_fandom)
|
||||
await db.flush()
|
||||
other_char = Tag(name="Sasuke", kind=TagKind.character, fandom_id=other_fandom.id)
|
||||
db.add(other_char)
|
||||
await db.flush()
|
||||
# img0: character only; img1: fandom direct; img2: BOTH (dedup → counts once)
|
||||
img0, img1, img2 = await _img(db, 0), await _img(db, 1), await _img(db, 2)
|
||||
other_img = await _img(db, 3)
|
||||
await db.execute(image_tag.insert().values([
|
||||
{"image_record_id": img0.id, "tag_id": char.id, "source": "manual"},
|
||||
{"image_record_id": img1.id, "tag_id": fandom.id, "source": "manual"},
|
||||
{"image_record_id": img2.id, "tag_id": char.id, "source": "manual"},
|
||||
{"image_record_id": img2.id, "tag_id": fandom.id, "source": "manual"},
|
||||
{"image_record_id": other_img.id, "tag_id": other_char.id, "source": "manual"},
|
||||
]))
|
||||
await db.flush()
|
||||
svc = TagDirectoryService(db)
|
||||
page = await svc.list_tags(kind="fandom", q=None, cursor=None, limit=10)
|
||||
card = next(c for c in page.cards if c["name"] == "Bleach")
|
||||
assert card["image_count"] == 3
|
||||
assert card["image_count"] == 3 # NOT 4 — Naruto's image must be excluded
|
||||
assert len(card["preview_thumbnails"]) == 3
|
||||
# The unrelated fandom counts only its own one image.
|
||||
other = next(c for c in page.cards if c["name"] == "Naruto")
|
||||
assert other["image_count"] == 1
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
||||
Reference in New Issue
Block a user