refactor(main): set_tag_fandom is pure fandom_id update

No rename, no retroactive-backfill-in-second-txn. display_name @property
reflects the change instantly. The auto-apply side effect (attach fandom
tag to images with the character) is preserved but simplified.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-04-22 15:40:17 -04:00
parent 1e19048b09
commit 8e6a3d84ba
+30 -59
View File
@@ -1038,90 +1038,61 @@ def get_tag(tag_id):
def set_tag_fandom(tag_id):
"""
Set or clear a character tag's fandom association.
Updates the tag name to include/remove the (Fandom) suffix.
Form params:
fandom_id: ID of an existing fandom tag (optional, clears if absent)
fandom_name: Name to find/create a fandom tag (used if fandom_id not given)
fandom_id: ID of an existing fandom tag (preferred).
fandom_name: Name to find/create a fandom tag (used if fandom_id
not given). If both omitted, the fandom is cleared.
"""
tag = Tag.query.get_or_404(tag_id)
if tag.kind != "character":
return jsonify(ok=False, error="Only character tags can have fandoms"), 400
fandom_id = request.form.get("fandom_id", type=int)
fandom_name = (request.form.get("fandom_name") or "").strip()
# Get the character's base name (strip existing fandom suffix) and normalize it.
char_part = tag.name.split(":", 1)[1] if ":" in tag.name else tag.name
char_base, _ = _parse_character_fandom(char_part)
char_base = normalize_display_name(char_base)
if fandom_id:
fandom_tag = Tag.query.get(fandom_id)
if not fandom_tag or fandom_tag.kind != "fandom":
fandom_tag = Tag.query.filter_by(id=fandom_id, kind="fandom").first()
if fandom_tag is None:
return jsonify(ok=False, error="Invalid fandom tag"), 400
fandom_display = fandom_tag.name.split(":", 1)[1] if ":" in fandom_tag.name else fandom_tag.name
tag.fandom_id = fandom_tag.id
elif fandom_name:
# _ensure_fandom_tag normalizes internally.
fandom_tag = _ensure_fandom_tag(fandom_name)
fandom_display = fandom_tag.name.split(":", 1)[1]
tag.fandom_id = fandom_tag.id
else:
# Clear fandom
tag.fandom_id = None
tag.name = f"character:{char_base}"
db.session.commit()
return jsonify(ok=True, tag={
"id": tag.id,
"name": tag.name,
"kind": tag.kind,
"fandom_id": None,
"fandom_name": None
})
fandom_tag = None
# Check for name conflict before renaming
new_name = f"character:{char_base} ({fandom_display})"
conflict = Tag.query.filter(Tag.name == new_name, Tag.id != tag_id).first()
if conflict:
return jsonify(ok=False, error=f"A tag named '{new_name}' already exists"), 409
tag.fandom_id = fandom_tag.id
tag.name = new_name
db.session.commit()
# Retroactive backfill: every image already tagged with this character should
# also have the fandom tag attached. Runs in a second transaction so a failed
# backfill doesn't roll back the rename.
try:
db.session.execute(
text("""
INSERT INTO image_tags (image_id, tag_id)
SELECT it.image_id, :fandom_id
FROM image_tags it
WHERE it.tag_id = :char_id
AND NOT EXISTS (
SELECT 1 FROM image_tags it2
WHERE it2.image_id = it.image_id
AND it2.tag_id = :fandom_id
)
"""),
{'char_id': tag.id, 'fandom_id': fandom_tag.id},
)
db.session.commit()
except Exception as e:
db.session.rollback()
# Rename already committed; log and continue. Sweep button recovers.
current_app.logger.warning(
"set_tag_fandom backfill failed for tag %s -> fandom %s: %s",
tag.id, fandom_tag.id, e,
)
# Auto-apply the fandom tag to images already tagged with this character.
if fandom_tag is not None:
try:
db.session.execute(
text("""
INSERT INTO image_tags (image_id, tag_id)
SELECT it.image_id, :fandom_id
FROM image_tags it
WHERE it.tag_id = :char_id
ON CONFLICT DO NOTHING
"""),
{"char_id": tag.id, "fandom_id": fandom_tag.id},
)
db.session.commit()
except Exception as e:
db.session.rollback()
current_app.logger.warning(
"set_tag_fandom auto-apply failed for tag %s -> fandom %s: %s",
tag.id, fandom_tag.id, e,
)
return jsonify(ok=True, tag={
"id": tag.id,
"name": tag.name,
"display_name": tag.display_name,
"kind": tag.kind,
"fandom_id": tag.fandom_id,
"fandom_name": fandom_display
"fandom_name": fandom_tag.name if fandom_tag else None,
})