From d9081917ba3d26b1b9aa6fff2f9c7130200c3487 Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Wed, 22 Apr 2026 14:44:06 -0400 Subject: [PATCH] refactor(main): bulk_add_tag uses parse_kind_prefix; bare storage Mirrors add_tag's new shape. Removes inline prefix-parsing, returns display_name in the response. Co-Authored-By: Claude Opus 4.7 --- app/main.py | 119 +++++++++++++++++++++++++--------------------------- 1 file changed, 58 insertions(+), 61 deletions(-) diff --git a/app/main.py b/app/main.py index e95d26d..9ea66f9 100644 --- a/app/main.py +++ b/app/main.py @@ -2333,85 +2333,82 @@ def get_common_tags(): @main.route('/api/images/bulk-add-tag', methods=['POST']) def bulk_add_tag(): """ - Add a tag to multiple images at once. - Expects JSON body with 'image_ids' array and 'tag_name' string. + Bulk-apply one tag to many images in a single transaction. - For character tags with a fandom, automatically adds the fandom tag too. + Accepts the same 'kind:name' shortcut as /image//tags/add. For + character tags, accepts optional fandom_id / fandom_name just like + the single-image endpoint. """ + from app.utils.tag_prefix import parse_kind_prefix + from app.utils.tag_names import underscores_to_spaces + data = request.get_json() or {} - image_ids = data.get('image_ids', []) - tag_name = (data.get('tag_name') or '').strip() + image_ids = data.get("image_ids") or [] + raw = (data.get("tag_name") or "").strip() + if not image_ids or not raw: + return jsonify(ok=False, error="image_ids and tag_name required"), 400 - if not image_ids: - return jsonify({'ok': False, 'error': 'No image IDs provided'}), 400 + kind, name = parse_kind_prefix(raw) + if kind is None: + kind = "user" + name = underscores_to_spaces(name) + elif kind in ("character", "fandom"): + name = normalize_display_name(name) - if not tag_name: - return jsonify({'ok': False, 'error': 'No tag name provided'}), 400 - - # Determine tag kind from name - if ':' in tag_name: - kind = tag_name.split(':', 1)[0] - else: - # No prefix — treat as a user-typed general tag. Strip underscores so - # WD14-style names pasted into the bulk box converge with our convention. - from app.utils.tag_names import underscores_to_spaces - kind = 'user' - tag_name = underscores_to_spaces(tag_name) - - # Get or create the tag - tag = Tag.query.filter_by(name=tag_name).first() fandom_tag = None + if kind == "character": + fandom_id = data.get("fandom_id") + fandom_name = (data.get("fandom_name") or "").strip() or None + if fandom_id: + fandom_tag = Tag.query.filter_by(id=fandom_id, kind="fandom").first() + if fandom_tag is None: + return jsonify(ok=False, error="fandom_not_found"), 400 + elif fandom_name: + fandom_tag = _ensure_fandom_tag(fandom_name) - if not tag: - if kind == 'character': - char_part = tag_name.split(':', 1)[1] - char_name, fandom_name = _parse_character_fandom(char_part) - norm_char = normalize_display_name(char_name) - if fandom_name: - fandom_tag = _ensure_fandom_tag(fandom_name) - tag_name = f"character:{norm_char} ({fandom_tag.name.split(':', 1)[1]})" - else: - tag_name = f"character:{norm_char}" - elif kind == 'fandom': - fandom_part = tag_name.split(':', 1)[1] - tag_name = f"fandom:{normalize_display_name(fandom_part)}" - tag = Tag.query.filter_by(name=tag_name).first() - if not tag: - tag = Tag(name=tag_name, kind=kind) - if kind == 'character' and fandom_name: - tag.fandom_id = fandom_tag.id + tag = Tag.query.filter_by( + kind="character", + name=name, + fandom_id=fandom_tag.id if fandom_tag else None, + ).first() + if tag is None: + tag = Tag( + kind="character", + name=name, + fandom_id=fandom_tag.id if fandom_tag else None, + ) + db.session.add(tag) + db.session.flush() + else: + tag = Tag.query.filter_by(kind=kind, name=name).first() + if tag is None: + tag = Tag(kind=kind, name=name) db.session.add(tag) db.session.flush() - elif tag.kind == 'character' and tag.fandom_id: - fandom_tag = Tag.query.get(tag.fandom_id) - elif tag.kind == 'character' and tag.fandom_id: - fandom_tag = Tag.query.get(tag.fandom_id) - - # Get images - images = ImageRecord.query.filter( - ImageRecord.id.in_(image_ids) - ).all() added_count = 0 - for img in images: + for image_id in image_ids: + img = ImageRecord.query.get(image_id) + if img is None: + continue if tag not in img.tags: img.tags.append(tag) added_count += 1 - # Auto-apply fandom tag if fandom_tag and fandom_tag not in img.tags: img.tags.append(fandom_tag) db.session.commit() - - result = {'id': tag.id, 'name': tag.name, 'kind': tag.kind} - if fandom_tag: - result['fandom_tag'] = {'id': fandom_tag.id, 'name': fandom_tag.name, 'kind': fandom_tag.kind} - - return jsonify({ - 'ok': True, - 'added_count': added_count, - 'tag': result - }) + return jsonify( + ok=True, + added_count=added_count, + total=len(image_ids), + tag={ + "id": tag.id, + "name": tag.name, + "display_name": tag.display_name, + "kind": tag.kind, + }, + ) @main.route('/api/images/bulk-remove-tag', methods=['POST'])