feat: recipe management page — browse, search, edit, delete recipes
New /recipes page with backend switching (Mealie/Tandoor), full-text search, tag filtering, multi-select bulk delete, per-recipe edit/delete buttons, and a full recipe editor reusing the import preview form. New API client methods: list_recipes, get_recipe, update_recipe, delete_recipe for both MealieClient and TandoorClient. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
+160
@@ -249,6 +249,166 @@ def send_to_tandoor():
|
||||
return jsonify({"ok": False, "error": str(exc), "trace": traceback.format_exc()})
|
||||
|
||||
|
||||
@app.route("/recipes")
|
||||
def recipes_page():
|
||||
"""Show the recipes management page."""
|
||||
cfg = config.load()
|
||||
has_mealie = bool(cfg.get("mealie_url") and cfg.get("mealie_api_key"))
|
||||
has_tandoor = bool(cfg.get("tandoor_url") and cfg.get("tandoor_api_key"))
|
||||
if not has_mealie and not has_tandoor:
|
||||
flash("Először állíts be legalább egy szolgáltatást.", "warning")
|
||||
return redirect(url_for("settings"))
|
||||
return render_template("recipes.html", cfg=cfg, version=VERSION,
|
||||
has_mealie=has_mealie, has_tandoor=has_tandoor)
|
||||
|
||||
|
||||
@app.route("/recipes/<backend>/<path:recipe_id>/edit")
|
||||
def recipe_edit_page(backend, recipe_id):
|
||||
"""Show the recipe edit page."""
|
||||
cfg = config.load()
|
||||
has_mealie = bool(cfg.get("mealie_url") and cfg.get("mealie_api_key"))
|
||||
has_tandoor = bool(cfg.get("tandoor_url") and cfg.get("tandoor_api_key"))
|
||||
return render_template("recipe_edit.html", cfg=cfg, version=VERSION,
|
||||
backend=backend, recipe_id=recipe_id,
|
||||
has_mealie=has_mealie, has_tandoor=has_tandoor)
|
||||
|
||||
|
||||
@app.route("/api/recipes/<backend>")
|
||||
def api_list_recipes(backend):
|
||||
"""AJAX — list recipes from the given backend."""
|
||||
cfg = config.load()
|
||||
search = request.args.get("search", "").strip()
|
||||
page = int(request.args.get("page", 1))
|
||||
per_page = int(request.args.get("per_page", 50))
|
||||
tag_ids_raw = request.args.get("tag_ids", "").strip()
|
||||
|
||||
try:
|
||||
if backend == "mealie":
|
||||
if not cfg.get("mealie_url") or not cfg.get("mealie_api_key"):
|
||||
return jsonify({"ok": False, "error": "Mealie nincs beállítva."})
|
||||
client = MealieClient(cfg["mealie_url"], cfg["mealie_api_key"],
|
||||
api_url=config.MEALIE_INTERNAL_URL)
|
||||
tag_ids = [t for t in tag_ids_raw.split(",") if t] if tag_ids_raw else None
|
||||
result = client.list_recipes(search=search, tag_ids=tag_ids,
|
||||
page=page, per_page=per_page)
|
||||
elif backend == "tandoor":
|
||||
if not cfg.get("tandoor_url") or not cfg.get("tandoor_api_key"):
|
||||
return jsonify({"ok": False, "error": "Tandoor nincs beállítva."})
|
||||
client = TandoorClient(cfg["tandoor_url"], cfg["tandoor_api_key"],
|
||||
api_url=config.TANDOOR_INTERNAL_URL)
|
||||
tag_ids = [int(t) for t in tag_ids_raw.split(",") if t] if tag_ids_raw else None
|
||||
result = client.list_recipes(search=search, keyword_ids=tag_ids,
|
||||
page=page, per_page=per_page)
|
||||
else:
|
||||
return jsonify({"ok": False, "error": "Ismeretlen backend."})
|
||||
return jsonify({"ok": True, **result})
|
||||
except Exception as exc:
|
||||
return jsonify({"ok": False, "error": str(exc)})
|
||||
|
||||
|
||||
@app.route("/api/recipes/<backend>/<path:recipe_id>")
|
||||
def api_get_recipe(backend, recipe_id):
|
||||
"""AJAX — get a single recipe in common format."""
|
||||
cfg = config.load()
|
||||
try:
|
||||
if backend == "mealie":
|
||||
client = MealieClient(cfg["mealie_url"], cfg["mealie_api_key"],
|
||||
api_url=config.MEALIE_INTERNAL_URL)
|
||||
recipe = client.get_recipe(recipe_id)
|
||||
elif backend == "tandoor":
|
||||
client = TandoorClient(cfg["tandoor_url"], cfg["tandoor_api_key"],
|
||||
api_url=config.TANDOOR_INTERNAL_URL)
|
||||
recipe = client.get_recipe(int(recipe_id))
|
||||
else:
|
||||
return jsonify({"ok": False, "error": "Ismeretlen backend."})
|
||||
return jsonify({"ok": True, "recipe": recipe})
|
||||
except Exception as exc:
|
||||
return jsonify({"ok": False, "error": str(exc)})
|
||||
|
||||
|
||||
@app.route("/api/recipes/<backend>/<path:recipe_id>", methods=["PUT"])
|
||||
def api_update_recipe(backend, recipe_id):
|
||||
"""AJAX — update a single recipe."""
|
||||
cfg = config.load()
|
||||
payload = request.get_json(silent=True)
|
||||
if not payload:
|
||||
return jsonify({"ok": False, "error": "Érvénytelen kérés."})
|
||||
try:
|
||||
if backend == "mealie":
|
||||
client = MealieClient(cfg["mealie_url"], cfg["mealie_api_key"],
|
||||
api_url=config.MEALIE_INTERNAL_URL)
|
||||
client.update_recipe(recipe_id, payload)
|
||||
elif backend == "tandoor":
|
||||
client = TandoorClient(cfg["tandoor_url"], cfg["tandoor_api_key"],
|
||||
api_url=config.TANDOOR_INTERNAL_URL)
|
||||
client.update_recipe(int(recipe_id), payload)
|
||||
else:
|
||||
return jsonify({"ok": False, "error": "Ismeretlen backend."})
|
||||
return jsonify({"ok": True, "message": "Recept sikeresen mentve."})
|
||||
except Exception as exc:
|
||||
return jsonify({"ok": False, "error": str(exc)})
|
||||
|
||||
|
||||
@app.route("/api/recipes/<backend>/delete", methods=["POST"])
|
||||
def api_delete_recipes(backend):
|
||||
"""AJAX — delete one or more recipes."""
|
||||
cfg = config.load()
|
||||
payload = request.get_json(silent=True)
|
||||
if not payload or not payload.get("ids"):
|
||||
return jsonify({"ok": False, "error": "Nincs kiválasztott recept."})
|
||||
|
||||
ids = payload["ids"]
|
||||
errors = []
|
||||
try:
|
||||
if backend == "mealie":
|
||||
client = MealieClient(cfg["mealie_url"], cfg["mealie_api_key"],
|
||||
api_url=config.MEALIE_INTERNAL_URL)
|
||||
for slug in ids:
|
||||
try:
|
||||
client.delete_recipe(slug)
|
||||
except Exception as e:
|
||||
errors.append(f"{slug}: {e}")
|
||||
elif backend == "tandoor":
|
||||
client = TandoorClient(cfg["tandoor_url"], cfg["tandoor_api_key"],
|
||||
api_url=config.TANDOOR_INTERNAL_URL)
|
||||
for rid in ids:
|
||||
try:
|
||||
client.delete_recipe(int(rid))
|
||||
except Exception as e:
|
||||
errors.append(f"{rid}: {e}")
|
||||
else:
|
||||
return jsonify({"ok": False, "error": "Ismeretlen backend."})
|
||||
except Exception as exc:
|
||||
return jsonify({"ok": False, "error": str(exc)})
|
||||
|
||||
deleted = len(ids) - len(errors)
|
||||
if errors:
|
||||
return jsonify({"ok": True, "deleted": deleted, "errors": errors,
|
||||
"message": f"{deleted} recept törölve, {len(errors)} hiba."})
|
||||
return jsonify({"ok": True, "deleted": deleted,
|
||||
"message": f"{deleted} recept sikeresen törölve."})
|
||||
|
||||
|
||||
@app.route("/api/tags/<backend>")
|
||||
def api_list_tags(backend):
|
||||
"""Return tags/keywords with IDs for a specific backend."""
|
||||
cfg = config.load()
|
||||
try:
|
||||
if backend == "mealie":
|
||||
client = MealieClient(cfg["mealie_url"], cfg["mealie_api_key"],
|
||||
api_url=config.MEALIE_INTERNAL_URL)
|
||||
tags = client.list_tags()
|
||||
elif backend == "tandoor":
|
||||
client = TandoorClient(cfg["tandoor_url"], cfg["tandoor_api_key"],
|
||||
api_url=config.TANDOOR_INTERNAL_URL)
|
||||
tags = client.list_keywords()
|
||||
else:
|
||||
return jsonify({"ok": False, "error": "Ismeretlen backend."})
|
||||
return jsonify({"ok": True, "tags": tags})
|
||||
except Exception as exc:
|
||||
return jsonify({"ok": False, "error": str(exc)})
|
||||
|
||||
|
||||
@app.route("/tags", methods=["GET"])
|
||||
def list_all_tags():
|
||||
"""Return existing tags from Mealie and Tandoor for autocomplete."""
|
||||
|
||||
Reference in New Issue
Block a user