Files
formations-app/ops/push-quiz-bank.py
T
2026-06-11 00:04:24 +02:00

98 lines
3.3 KiB
Python

#!/usr/bin/env python3
"""Push the Moodle quiz-bank export into Grist table QuizBank (SP3).
Usage (on Tower, after running ops/export-quiz-bank.php):
set -a; . /home/clems/formations-app.env; set +a
python3 ops/push-quiz-bank.py /tmp/quizbank.json
Reads GRIST_API_KEY and GRIST_DOC_ID from the environment
(GRIST_URL optional, defaults to https://grist.saillant.cc).
Idempotent: creates the QuizBank table if missing (AddTable apply, as
SP2 did); if it exists, deletes ALL rows then re-inserts. One row per
quiz; `payload` is the JSON string {"questions": [...]} INCLUDING the
correct flags and feedback — answers live ONLY in Grist, never in the
public repo.
"""
import json
import os
import sys
import urllib.request
# Moodle course id -> app slug (mirrors src/config/courses.ts)
ID_TO_SLUG = {
2: "esp32-ia",
3: "kicad-makers",
4: "llm-locaux",
5: "freertos",
6: "iot-az",
7: "docker-selfhosting",
}
COLUMNS = [
{"id": "course_slug", "type": "Text"},
{"id": "quiz_n", "type": "Int"},
{"id": "cmid", "type": "Int"},
{"id": "name", "type": "Text"},
{"id": "gradepass", "type": "Numeric"},
{"id": "payload", "type": "Text"},
]
def api(base, key, path, payload=None, method=None):
req = urllib.request.Request(
base + path,
data=json.dumps(payload).encode() if payload is not None else None,
headers={
"Authorization": "Bearer " + key,
"Content-Type": "application/json",
},
method=method or ("POST" if payload is not None else "GET"),
)
with urllib.request.urlopen(req) as resp:
return json.loads(resp.read() or b"null")
def main():
key = os.environ["GRIST_API_KEY"]
doc = os.environ["GRIST_DOC_ID"]
url = os.environ.get("GRIST_URL", "https://grist.saillant.cc").rstrip("/")
base = f"{url}/api/docs/{doc}"
bank = json.load(open(sys.argv[1] if len(sys.argv) > 1
else "/tmp/quizbank.json"))
tables = [t["id"] for t in api(base, key, "/tables")["tables"]]
if "QuizBank" not in tables:
api(base, key, "/apply", [["AddTable", "QuizBank", COLUMNS]])
print("table QuizBank created")
else:
ids = [r["id"] for r in
api(base, key, "/tables/QuizBank/records")["records"]]
if ids:
api(base, key, "/tables/QuizBank/data/delete", ids)
print(f"table QuizBank exists, {len(ids)} rows deleted")
records = []
for cid_str, course in sorted(bank["courses"].items(), key=lambda kv: int(kv[0])):
slug = ID_TO_SLUG[int(cid_str)]
for quiz in course["quizzes"]:
records.append({"fields": {
"course_slug": slug,
"quiz_n": quiz["quiz_n"],
"cmid": quiz["cmid"],
"name": quiz["name"],
"gradepass": quiz["gradepass"],
"payload": json.dumps({"questions": quiz["questions"]},
ensure_ascii=False),
}})
api(base, key, "/tables/QuizBank/records", {"records": records})
n = len(api(base, key, "/tables/QuizBank/records")["records"])
print(f"inserted {len(records)} rows, table now has {n} rows")
if n != len(records):
sys.exit(f"row count mismatch: expected {len(records)}, got {n}")
if __name__ == "__main__":
main()