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 <noreply@anthropic.com>
This commit is contained in:
+59
-62
@@ -2333,85 +2333,82 @@ def get_common_tags():
|
|||||||
@main.route('/api/images/bulk-add-tag', methods=['POST'])
|
@main.route('/api/images/bulk-add-tag', methods=['POST'])
|
||||||
def bulk_add_tag():
|
def bulk_add_tag():
|
||||||
"""
|
"""
|
||||||
Add a tag to multiple images at once.
|
Bulk-apply one tag to many images in a single transaction.
|
||||||
Expects JSON body with 'image_ids' array and 'tag_name' string.
|
|
||||||
|
|
||||||
For character tags with a fandom, automatically adds the fandom tag too.
|
Accepts the same 'kind:name' shortcut as /image/<id>/tags/add. For
|
||||||
|
character tags, accepts optional fandom_id / fandom_name just like
|
||||||
|
the single-image endpoint.
|
||||||
"""
|
"""
|
||||||
data = request.get_json() or {}
|
from app.utils.tag_prefix import parse_kind_prefix
|
||||||
image_ids = data.get('image_ids', [])
|
|
||||||
tag_name = (data.get('tag_name') or '').strip()
|
|
||||||
|
|
||||||
if not image_ids:
|
|
||||||
return jsonify({'ok': False, 'error': 'No image IDs provided'}), 400
|
|
||||||
|
|
||||||
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
|
from app.utils.tag_names import underscores_to_spaces
|
||||||
kind = 'user'
|
|
||||||
tag_name = underscores_to_spaces(tag_name)
|
|
||||||
|
|
||||||
# Get or create the tag
|
data = request.get_json() or {}
|
||||||
tag = Tag.query.filter_by(name=tag_name).first()
|
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
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
fandom_tag = None
|
fandom_tag = None
|
||||||
|
if kind == "character":
|
||||||
if not tag:
|
fandom_id = data.get("fandom_id")
|
||||||
if kind == 'character':
|
fandom_name = (data.get("fandom_name") or "").strip() or None
|
||||||
char_part = tag_name.split(':', 1)[1]
|
if fandom_id:
|
||||||
char_name, fandom_name = _parse_character_fandom(char_part)
|
fandom_tag = Tag.query.filter_by(id=fandom_id, kind="fandom").first()
|
||||||
norm_char = normalize_display_name(char_name)
|
if fandom_tag is None:
|
||||||
if fandom_name:
|
return jsonify(ok=False, error="fandom_not_found"), 400
|
||||||
|
elif fandom_name:
|
||||||
fandom_tag = _ensure_fandom_tag(fandom_name)
|
fandom_tag = _ensure_fandom_tag(fandom_name)
|
||||||
tag_name = f"character:{norm_char} ({fandom_tag.name.split(':', 1)[1]})"
|
|
||||||
else:
|
tag = Tag.query.filter_by(
|
||||||
tag_name = f"character:{norm_char}"
|
kind="character",
|
||||||
elif kind == 'fandom':
|
name=name,
|
||||||
fandom_part = tag_name.split(':', 1)[1]
|
fandom_id=fandom_tag.id if fandom_tag else None,
|
||||||
tag_name = f"fandom:{normalize_display_name(fandom_part)}"
|
).first()
|
||||||
tag = Tag.query.filter_by(name=tag_name).first()
|
if tag is None:
|
||||||
if not tag:
|
tag = Tag(
|
||||||
tag = Tag(name=tag_name, kind=kind)
|
kind="character",
|
||||||
if kind == 'character' and fandom_name:
|
name=name,
|
||||||
tag.fandom_id = fandom_tag.id
|
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.add(tag)
|
||||||
db.session.flush()
|
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
|
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:
|
if tag not in img.tags:
|
||||||
img.tags.append(tag)
|
img.tags.append(tag)
|
||||||
added_count += 1
|
added_count += 1
|
||||||
# Auto-apply fandom tag
|
|
||||||
if fandom_tag and fandom_tag not in img.tags:
|
if fandom_tag and fandom_tag not in img.tags:
|
||||||
img.tags.append(fandom_tag)
|
img.tags.append(fandom_tag)
|
||||||
|
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
return jsonify(
|
||||||
result = {'id': tag.id, 'name': tag.name, 'kind': tag.kind}
|
ok=True,
|
||||||
if fandom_tag:
|
added_count=added_count,
|
||||||
result['fandom_tag'] = {'id': fandom_tag.id, 'name': fandom_tag.name, 'kind': fandom_tag.kind}
|
total=len(image_ids),
|
||||||
|
tag={
|
||||||
return jsonify({
|
"id": tag.id,
|
||||||
'ok': True,
|
"name": tag.name,
|
||||||
'added_count': added_count,
|
"display_name": tag.display_name,
|
||||||
'tag': result
|
"kind": tag.kind,
|
||||||
})
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@main.route('/api/images/bulk-remove-tag', methods=['POST'])
|
@main.route('/api/images/bulk-remove-tag', methods=['POST'])
|
||||||
|
|||||||
Reference in New Issue
Block a user