feat: object label per object + {obj:UUID} sentence placeholders
- Annotate: per-object single label input (M2M via db_objects_db_words), auto-save on blur, remove picture-level word section
- Generate: object chips insert {obj:UUID} at cursor position in question/statement textarea
- Live preview resolves {obj:UUID} → actual object label
- PairsList display also resolves placeholders
- Remove F/A/B word chip system from pair form (replaced by object placeholders)
- Backend: POST /api/directus/db-objects/<id>/words replaces existing word with single label
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
58
app.py
58
app.py
@@ -1895,31 +1895,45 @@ def directus_db_object_pairs(obj_id):
|
||||
return jsonify({"ok": True, "pair_id": pair_id, "statement_id": stmt_id, "question_id": q_id})
|
||||
|
||||
|
||||
@app.route("/api/directus/db-objects/<obj_id>/words", methods=["GET"])
|
||||
@app.route("/api/directus/db-objects/<obj_id>/words", methods=["GET", "POST"])
|
||||
def directus_db_object_words(obj_id):
|
||||
"""Gibt alle db_words zurück, die via db_objects_db_words mit dem Objekt verknüpft sind."""
|
||||
token = request.headers.get("Authorization", "")
|
||||
data, s = _directus(
|
||||
"GET",
|
||||
f"/items/db_objects_db_words?filter[db_objects_id][_eq]={obj_id}"
|
||||
f"&fields=id,db_words_id.id,db_words_id.titel_de,db_words_id.level,db_words_id.status&limit=500",
|
||||
token,
|
||||
)
|
||||
if s != 200:
|
||||
return jsonify({"data": []})
|
||||
items = []
|
||||
for entry in (data.get("data") or []):
|
||||
word = entry.get("db_words_id") or {}
|
||||
if not isinstance(word, dict) or not word.get("id"):
|
||||
continue
|
||||
if word.get("status") == "archived":
|
||||
continue
|
||||
items.append({
|
||||
"word_id": word["id"],
|
||||
"titel_de": word.get("titel_de", ""),
|
||||
"level": word.get("level") or 50,
|
||||
})
|
||||
return jsonify({"data": items})
|
||||
if request.method == "GET":
|
||||
data, s = _directus(
|
||||
"GET",
|
||||
f"/items/db_objects_db_words?filter[db_objects_id][_eq]={obj_id}"
|
||||
f"&fields=id,db_words_id.id,db_words_id.titel_de,db_words_id.level,db_words_id.status&limit=500",
|
||||
token,
|
||||
)
|
||||
if s != 200:
|
||||
return jsonify({"data": []})
|
||||
items = []
|
||||
for entry in (data.get("data") or []):
|
||||
word = entry.get("db_words_id") or {}
|
||||
if not isinstance(word, dict) or not word.get("id"):
|
||||
continue
|
||||
if word.get("status") == "archived":
|
||||
continue
|
||||
items.append({
|
||||
"word_id": word["id"],
|
||||
"titel_de": word.get("titel_de", ""),
|
||||
"level": word.get("level") or 50,
|
||||
})
|
||||
return jsonify({"data": items})
|
||||
else: # POST — replace with single word
|
||||
body = request.get_json(force=True, silent=True) or {}
|
||||
titel_de = (body.get("titel_de") or "").strip()
|
||||
level = int(body.get("level") or 50)
|
||||
# Delete all existing junctions for this object
|
||||
existing, _ = _directus("GET", f"/items/db_objects_db_words?filter[db_objects_id][_eq]={obj_id}&fields=id&limit=20", token)
|
||||
for e in (existing.get("data") or []):
|
||||
_directus("DELETE", f"/items/db_objects_db_words/{e['id']}", token)
|
||||
if not titel_de:
|
||||
return jsonify({"ok": True, "cleared": True})
|
||||
wid, _ = _find_or_create_db_word(titel_de, level, token)
|
||||
_directus("POST", "/items/db_objects_db_words", token, {"db_objects_id": obj_id, "db_words_id": wid})
|
||||
return jsonify({"ok": True, "word_id": wid})
|
||||
|
||||
|
||||
@app.route("/api/directus/db-pairs/<pair_id>", methods=["PATCH", "DELETE"])
|
||||
|
||||
Reference in New Issue
Block a user