98 lines
3.3 KiB
Python
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()
|