From 8e6a3d84ba93d0372f3bda1103960351f1cf575c Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Wed, 22 Apr 2026 15:40:17 -0400 Subject: [PATCH] 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 --- app/main.py | 89 ++++++++++++++++++----------------------------------- 1 file changed, 30 insertions(+), 59 deletions(-) diff --git a/app/main.py b/app/main.py index cd42425..bdeb6dc 100644 --- a/app/main.py +++ b/app/main.py @@ -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, })