additional tag edditing functionality. and delete by tag. added import tuning options to settings
This commit is contained in:
+145
-1
@@ -267,7 +267,7 @@ def search_tags():
|
||||
.all()
|
||||
)
|
||||
|
||||
return jsonify(tags=[{"name": t.name, "kind": t.kind} for t in tags])
|
||||
return jsonify(tags=[{"id": t.id, "name": t.name, "kind": t.kind} for t in tags])
|
||||
|
||||
|
||||
@main.get("/image/<int:image_id>/tags")
|
||||
@@ -427,3 +427,147 @@ def delete_tag(tag_id):
|
||||
db.session.commit()
|
||||
|
||||
return jsonify(ok=True)
|
||||
|
||||
|
||||
# ----------------------------
|
||||
# Filtered deletion endpoints
|
||||
# ----------------------------
|
||||
|
||||
@main.post("/api/delete-by-tag")
|
||||
def delete_images_by_tag():
|
||||
"""
|
||||
Delete all images that have a specific tag.
|
||||
Also removes the image files and thumbnails from disk.
|
||||
Query params:
|
||||
- tag_id: ID of the tag whose images should be deleted
|
||||
- tag_name: Name of the tag (must match for security validation)
|
||||
- delete_tag: if "true", also delete the tag itself
|
||||
"""
|
||||
tag_id = request.form.get("tag_id", type=int)
|
||||
tag_name = request.form.get("tag_name", "").strip()
|
||||
delete_tag_also = request.form.get("delete_tag") == "true"
|
||||
|
||||
if not tag_id:
|
||||
return jsonify(ok=False, error="tag_id is required"), 400
|
||||
|
||||
if not tag_name:
|
||||
return jsonify(ok=False, error="tag_name confirmation is required"), 400
|
||||
|
||||
tag = Tag.query.get_or_404(tag_id)
|
||||
|
||||
# Security: verify the provided tag name matches the actual tag
|
||||
if tag.name != tag_name:
|
||||
return jsonify(ok=False, error="Tag name confirmation does not match"), 403
|
||||
|
||||
images_to_delete = list(tag.images)
|
||||
|
||||
deleted_count = 0
|
||||
errors = []
|
||||
|
||||
for img in images_to_delete:
|
||||
try:
|
||||
# Remove image file from disk
|
||||
if img.filepath and os.path.exists(img.filepath):
|
||||
os.remove(img.filepath)
|
||||
# Remove thumbnail if exists
|
||||
if img.thumb_path and os.path.exists(img.thumb_path):
|
||||
os.remove(img.thumb_path)
|
||||
# Delete DB record
|
||||
db.session.delete(img)
|
||||
deleted_count += 1
|
||||
except Exception as e:
|
||||
errors.append(f"Failed to delete {img.filename}: {e}")
|
||||
|
||||
# Optionally delete the tag itself
|
||||
if delete_tag_also:
|
||||
db.session.delete(tag)
|
||||
|
||||
db.session.commit()
|
||||
|
||||
return jsonify(
|
||||
ok=True,
|
||||
deleted_count=deleted_count,
|
||||
tag_deleted=delete_tag_also,
|
||||
errors=errors if errors else None
|
||||
)
|
||||
|
||||
|
||||
@main.get("/api/tag/<int:tag_id>/image-count")
|
||||
def get_tag_image_count(tag_id):
|
||||
"""Get the number of images associated with a tag."""
|
||||
tag = Tag.query.get_or_404(tag_id)
|
||||
count = len(tag.images)
|
||||
return jsonify(ok=True, count=count, tag_name=tag.name)
|
||||
|
||||
|
||||
# ----------------------------
|
||||
# Import settings endpoints
|
||||
# ----------------------------
|
||||
|
||||
@main.get("/api/import-settings")
|
||||
def get_import_settings():
|
||||
"""Get current import filter settings."""
|
||||
# Load from file first, fall back to env defaults
|
||||
import json
|
||||
settings_path = "/import/settings.json"
|
||||
settings = {
|
||||
"min_width": int(os.environ.get("IMPORT_MIN_WIDTH", "0")),
|
||||
"min_height": int(os.environ.get("IMPORT_MIN_HEIGHT", "0")),
|
||||
"skip_transparent": os.environ.get("IMPORT_SKIP_TRANSPARENT", "false").lower() == "true",
|
||||
"transparency_threshold": float(os.environ.get("IMPORT_TRANSPARENCY_THRESHOLD", "0.9")),
|
||||
"phash_threshold": int(os.environ.get("IMPORT_PHASH_THRESHOLD", "2")),
|
||||
}
|
||||
try:
|
||||
if os.path.exists(settings_path):
|
||||
with open(settings_path, "r") as f:
|
||||
file_settings = json.load(f)
|
||||
settings.update(file_settings)
|
||||
except Exception:
|
||||
pass
|
||||
return jsonify(ok=True, settings=settings)
|
||||
|
||||
|
||||
@main.post("/api/import-settings")
|
||||
def save_import_settings():
|
||||
"""
|
||||
Save import filter settings.
|
||||
Settings are stored in /import/settings.json and loaded by the importer.
|
||||
"""
|
||||
settings = {
|
||||
"min_width": request.form.get("min_width", 0, type=int),
|
||||
"min_height": request.form.get("min_height", 0, type=int),
|
||||
"skip_transparent": request.form.get("skip_transparent") == "true",
|
||||
"transparency_threshold": request.form.get("transparency_threshold", 0.9, type=float),
|
||||
"phash_threshold": request.form.get("phash_threshold", 2, type=int),
|
||||
}
|
||||
|
||||
import json
|
||||
settings_path = "/import/settings.json"
|
||||
try:
|
||||
with open(settings_path, "w") as f:
|
||||
json.dump(settings, f, indent=2)
|
||||
return jsonify(ok=True, settings=settings)
|
||||
except Exception as e:
|
||||
return jsonify(ok=False, error=str(e)), 500
|
||||
|
||||
|
||||
@main.get("/api/import-stats")
|
||||
def get_import_stats():
|
||||
"""Get import statistics including filtered image counts."""
|
||||
import json
|
||||
stats_path = "/import/stats.json"
|
||||
try:
|
||||
if os.path.exists(stats_path):
|
||||
with open(stats_path, "r") as f:
|
||||
stats = json.load(f)
|
||||
else:
|
||||
stats = {
|
||||
"total_processed": 0,
|
||||
"total_imported": 0,
|
||||
"filtered_by_dimension": 0,
|
||||
"filtered_by_transparency": 0,
|
||||
"filtered_by_duplicate": 0,
|
||||
}
|
||||
return jsonify(ok=True, stats=stats)
|
||||
except Exception as e:
|
||||
return jsonify(ok=False, error=str(e)), 500
|
||||
|
||||
Reference in New Issue
Block a user