Add /api/setup-schema endpoint für Directus M2M Relationen
Konfiguriert alle fehlenden Directus-Relationen: - questions.object → objects (FK fix) - questions.short_answer → words (FK fix) - questions ↔ objects M2M via questions_objects - words ↔ objects M2M via words_objects - questions ↔ words (distractor) M2M via questions_distractor_words - words.linked_questions backref via questions_words Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
131
app.py
131
app.py
@@ -1214,6 +1214,137 @@ def delete_word_item(w_id: str):
|
|||||||
return jsonify({"error": "Delete failed"}), status
|
return jsonify({"error": "Delete failed"}), status
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/api/setup-schema", methods=["POST"])
|
||||||
|
def setup_directus_schema():
|
||||||
|
"""
|
||||||
|
Einmalig ausführen: Konfiguriert alle M2M-Relationen in Directus.
|
||||||
|
Idempotent – bereits vorhandene Felder/Relationen werden übersprungen.
|
||||||
|
"""
|
||||||
|
token = request.headers.get("Authorization", "")
|
||||||
|
results = []
|
||||||
|
|
||||||
|
def do(label, method, path, body=None):
|
||||||
|
data, status = _directus(method, path, token, body)
|
||||||
|
ok = status in (200, 201, 204)
|
||||||
|
results.append({"label": label, "status": status, "ok": ok,
|
||||||
|
"err": None if ok else (data.get("errors") or data)})
|
||||||
|
return ok
|
||||||
|
|
||||||
|
# ── Fix M2O: questions.object → objects ──────────────────────────────────
|
||||||
|
do("relation questions.object→objects", "POST", "/relations", {
|
||||||
|
"collection": "questions", "field": "object",
|
||||||
|
"related_collection": "objects",
|
||||||
|
"schema": {"on_delete": "SET NULL"},
|
||||||
|
"meta": {"one_deselect_action": "nullify"},
|
||||||
|
})
|
||||||
|
# Update display template so the object label shows
|
||||||
|
do("update field questions.object template", "PATCH", "/fields/questions/object", {
|
||||||
|
"meta": {"options": {"template": "{{user_notes}}"}}
|
||||||
|
})
|
||||||
|
|
||||||
|
# ── Fix M2O: questions.short_answer → words ───────────────────────────────
|
||||||
|
do("relation questions.short_answer→words", "POST", "/relations", {
|
||||||
|
"collection": "questions", "field": "short_answer",
|
||||||
|
"related_collection": "words",
|
||||||
|
"schema": {"on_delete": "SET NULL"},
|
||||||
|
"meta": {"one_deselect_action": "nullify"},
|
||||||
|
})
|
||||||
|
|
||||||
|
# ── M2M: questions ↔ objects via questions_objects ────────────────────────
|
||||||
|
do("field questions.linked_objects", "POST", "/fields/questions", {
|
||||||
|
"field": "linked_objects", "type": "alias",
|
||||||
|
"meta": {"special": ["m2m"], "interface": "list-m2m",
|
||||||
|
"options": {"template": "{{objects_id.user_notes}}"},
|
||||||
|
"note": "Verknüpfte Objekte"},
|
||||||
|
})
|
||||||
|
do("field objects.linked_questions", "POST", "/fields/objects", {
|
||||||
|
"field": "linked_questions", "type": "alias",
|
||||||
|
"meta": {"special": ["m2m"], "interface": "list-m2m",
|
||||||
|
"options": {"template": "{{questions_id.question_de}}"},
|
||||||
|
"note": "Verknüpfte Fragen"},
|
||||||
|
})
|
||||||
|
do("relation questions_objects.questions_id→questions", "POST", "/relations", {
|
||||||
|
"collection": "questions_objects", "field": "questions_id",
|
||||||
|
"related_collection": "questions",
|
||||||
|
"schema": {"on_delete": "CASCADE"},
|
||||||
|
"meta": {"junction_field": "objects_id", "one_field": "linked_objects",
|
||||||
|
"one_deselect_action": "nullify"},
|
||||||
|
})
|
||||||
|
do("relation questions_objects.objects_id→objects", "POST", "/relations", {
|
||||||
|
"collection": "questions_objects", "field": "objects_id",
|
||||||
|
"related_collection": "objects",
|
||||||
|
"schema": {"on_delete": "CASCADE"},
|
||||||
|
"meta": {"junction_field": "questions_id", "one_field": "linked_questions",
|
||||||
|
"one_deselect_action": "nullify"},
|
||||||
|
})
|
||||||
|
|
||||||
|
# ── M2M: words ↔ objects via words_objects ────────────────────────────────
|
||||||
|
do("field words.linked_objects", "POST", "/fields/words", {
|
||||||
|
"field": "linked_objects", "type": "alias",
|
||||||
|
"meta": {"special": ["m2m"], "interface": "list-m2m",
|
||||||
|
"options": {"template": "{{objects_id.user_notes}}"},
|
||||||
|
"note": "Verknüpfte Objekte"},
|
||||||
|
})
|
||||||
|
do("field objects.linked_words", "POST", "/fields/objects", {
|
||||||
|
"field": "linked_words", "type": "alias",
|
||||||
|
"meta": {"special": ["m2m"], "interface": "list-m2m",
|
||||||
|
"options": {"template": "{{words_id.title_de}}"},
|
||||||
|
"note": "Verknüpfte Wörter"},
|
||||||
|
})
|
||||||
|
do("relation words_objects.words_id→words", "POST", "/relations", {
|
||||||
|
"collection": "words_objects", "field": "words_id",
|
||||||
|
"related_collection": "words",
|
||||||
|
"schema": {"on_delete": "CASCADE"},
|
||||||
|
"meta": {"junction_field": "objects_id", "one_field": "linked_objects",
|
||||||
|
"one_deselect_action": "nullify"},
|
||||||
|
})
|
||||||
|
do("relation words_objects.objects_id→objects", "POST", "/relations", {
|
||||||
|
"collection": "words_objects", "field": "objects_id",
|
||||||
|
"related_collection": "objects",
|
||||||
|
"schema": {"on_delete": "CASCADE"},
|
||||||
|
"meta": {"junction_field": "words_id", "one_field": "linked_words",
|
||||||
|
"one_deselect_action": "nullify"},
|
||||||
|
})
|
||||||
|
|
||||||
|
# ── M2M: questions ↔ words (distractor) via questions_distractor_words ────
|
||||||
|
do("field questions.distractor_words", "POST", "/fields/questions", {
|
||||||
|
"field": "distractor_words", "type": "alias",
|
||||||
|
"meta": {"special": ["m2m"], "interface": "list-m2m",
|
||||||
|
"options": {"template": "{{words_id.title_de}}"},
|
||||||
|
"note": "Ablenker-Wörter (nicht in Frage/Antwort)"},
|
||||||
|
})
|
||||||
|
do("relation questions_distractor_words.questions_id→questions", "POST", "/relations", {
|
||||||
|
"collection": "questions_distractor_words", "field": "questions_id",
|
||||||
|
"related_collection": "questions",
|
||||||
|
"schema": {"on_delete": "CASCADE"},
|
||||||
|
"meta": {"junction_field": "words_id", "one_field": "distractor_words",
|
||||||
|
"one_deselect_action": "nullify"},
|
||||||
|
})
|
||||||
|
do("relation questions_distractor_words.words_id→words", "POST", "/relations", {
|
||||||
|
"collection": "questions_distractor_words", "field": "words_id",
|
||||||
|
"related_collection": "words",
|
||||||
|
"schema": {"on_delete": "CASCADE"},
|
||||||
|
"meta": {"junction_field": "questions_id", "one_deselect_action": "nullify"},
|
||||||
|
})
|
||||||
|
|
||||||
|
# ── Backref: words → questions via existing questions_words junction ───────
|
||||||
|
do("field words.linked_questions", "POST", "/fields/words", {
|
||||||
|
"field": "linked_questions", "type": "alias",
|
||||||
|
"meta": {"special": ["m2m"], "interface": "list-m2m",
|
||||||
|
"options": {"template": "{{questions_id.question_de}}"},
|
||||||
|
"note": "Fragen in denen dieses Wort vorkommt"},
|
||||||
|
})
|
||||||
|
# Patch the existing questions_words relation to add the one_field backref on words
|
||||||
|
do("patch relation questions_words.words_id one_field", "PATCH",
|
||||||
|
"/relations/questions_words/words_id", {
|
||||||
|
"meta": {"one_field": "linked_questions", "one_deselect_action": "nullify"}
|
||||||
|
})
|
||||||
|
|
||||||
|
failed = [r for r in results if not r["ok"]]
|
||||||
|
return jsonify({"ok": len(failed) == 0, "total": len(results),
|
||||||
|
"failed": len(failed), "results": results})
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
app.run(host="0.0.0.0", port=8000, debug=True)
|
app.run(host="0.0.0.0", port=8000, debug=True)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user