Feature: Support both Free-Text and Multiple-Choice question types
This commit is contained in:
+68
-18
@@ -1,6 +1,7 @@
|
||||
import os
|
||||
import uuid
|
||||
import json
|
||||
import random
|
||||
import datetime
|
||||
from fastapi import FastAPI, Request, Depends, HTTPException, status, Form
|
||||
from fastapi.responses import HTMLResponse, RedirectResponse, JSONResponse
|
||||
@@ -28,6 +29,27 @@ BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
app.mount("/static", StaticFiles(directory=os.path.join(BASE_DIR, "static")), name="static")
|
||||
templates = Jinja2Templates(directory=os.path.join(BASE_DIR, "templates"))
|
||||
|
||||
def format_question_for_client(q: Question, number: int, total: int) -> dict:
|
||||
options_list = None
|
||||
if q.question_type == "multiple_choice" and q.options:
|
||||
try:
|
||||
parsed = json.loads(q.options)
|
||||
if isinstance(parsed, list):
|
||||
options_list = list(parsed)
|
||||
random.shuffle(options_list)
|
||||
except Exception:
|
||||
options_list = [opt.strip() for opt in q.options.split(",") if opt.strip()]
|
||||
random.shuffle(options_list)
|
||||
|
||||
return {
|
||||
"question_number": number,
|
||||
"total_questions": total,
|
||||
"category": q.category,
|
||||
"question": q.question,
|
||||
"question_type": q.question_type or "text",
|
||||
"options": options_list
|
||||
}
|
||||
|
||||
@app.on_event("startup")
|
||||
def startup_event():
|
||||
seed_db()
|
||||
@@ -65,16 +87,14 @@ async def start_game(db: Session = Depends(get_db)):
|
||||
db.commit()
|
||||
|
||||
first_q = questions[0]
|
||||
return {
|
||||
res = format_question_for_client(first_q, 1, len(question_ids))
|
||||
res.update({
|
||||
"session_id": session_id,
|
||||
"question_number": 1,
|
||||
"total_questions": len(question_ids),
|
||||
"category": first_q.category,
|
||||
"question": first_q.question,
|
||||
"score": 0,
|
||||
"attempts_used": 0,
|
||||
"max_attempts": 3
|
||||
}
|
||||
})
|
||||
return res
|
||||
|
||||
@app.post("/api/game/check")
|
||||
async def check_answer(payload: GameCheckRequest, db: Session = Depends(get_db)):
|
||||
@@ -118,12 +138,7 @@ async def check_answer(payload: GameCheckRequest, db: Session = Depends(get_db))
|
||||
if not is_finished:
|
||||
next_q = db.query(Question).filter(Question.id == question_ids[session.current_question_idx]).first()
|
||||
if next_q:
|
||||
next_q_data = {
|
||||
"question_number": session.current_question_idx + 1,
|
||||
"total_questions": len(question_ids),
|
||||
"category": next_q.category,
|
||||
"question": next_q.question
|
||||
}
|
||||
next_q_data = format_question_for_client(next_q, session.current_question_idx + 1, len(question_ids))
|
||||
|
||||
return {
|
||||
"correct": True,
|
||||
@@ -155,12 +170,7 @@ async def check_answer(payload: GameCheckRequest, db: Session = Depends(get_db))
|
||||
if not is_finished:
|
||||
next_q = db.query(Question).filter(Question.id == question_ids[session.current_question_idx]).first()
|
||||
if next_q:
|
||||
next_q_data = {
|
||||
"question_number": session.current_question_idx + 1,
|
||||
"total_questions": len(question_ids),
|
||||
"category": next_q.category,
|
||||
"question": next_q.question
|
||||
}
|
||||
next_q_data = format_question_for_client(next_q, session.current_question_idx + 1, len(question_ids))
|
||||
|
||||
return {
|
||||
"correct": False,
|
||||
@@ -218,11 +228,23 @@ async def submit_suggestion(payload: SuggestionCreateRequest, db: Session = Depe
|
||||
if not payload.frage.strip() or not payload.antwort.strip():
|
||||
raise HTTPException(status_code=400, detail="Frage und Antwort dürfen nicht leer sein.")
|
||||
|
||||
options_json = None
|
||||
q_type = payload.question_type or "text"
|
||||
if q_type == "multiple_choice":
|
||||
opts = [payload.antwort.strip()]
|
||||
if payload.falsche_antworten:
|
||||
for opt in payload.falsche_antworten:
|
||||
if opt and opt.strip():
|
||||
opts.append(opt.strip())
|
||||
options_json = json.dumps(opts, ensure_ascii=False)
|
||||
|
||||
new_suggestion = Question(
|
||||
category="community",
|
||||
question=payload.frage.strip(),
|
||||
canonical_answer=payload.antwort.strip(),
|
||||
hint=payload.tipp.strip() if payload.tipp else None,
|
||||
question_type=q_type,
|
||||
options=options_json,
|
||||
status="needs_review", # User Question Need Test / Review
|
||||
submitted_by=payload.name.strip() if payload.name else "Gast",
|
||||
sort_order=999
|
||||
@@ -304,17 +326,31 @@ async def admin_add_question(
|
||||
canonical_answer: str = Form(...),
|
||||
hint: str = Form(None),
|
||||
category: str = Form("harry_potter"),
|
||||
question_type: str = Form("text"),
|
||||
wrong_option_1: str = Form(None),
|
||||
wrong_option_2: str = Form(None),
|
||||
wrong_option_3: str = Form(None),
|
||||
sort_order: int = Form(0),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
if not request.session.get("admin_user"):
|
||||
return RedirectResponse(url="/admin/login", status_code=303)
|
||||
|
||||
options_json = None
|
||||
if question_type == "multiple_choice":
|
||||
opts = [canonical_answer.strip()]
|
||||
for w in [wrong_option_1, wrong_option_2, wrong_option_3]:
|
||||
if w and w.strip():
|
||||
opts.append(w.strip())
|
||||
options_json = json.dumps(opts, ensure_ascii=False)
|
||||
|
||||
new_q = Question(
|
||||
category=category.strip(),
|
||||
question=question.strip(),
|
||||
canonical_answer=canonical_answer.strip(),
|
||||
hint=hint.strip() if hint else None,
|
||||
question_type=question_type.strip(),
|
||||
options=options_json,
|
||||
status="active",
|
||||
sort_order=sort_order,
|
||||
submitted_by=f"Admin ({request.session['admin_user']})"
|
||||
@@ -365,6 +401,8 @@ async def admin_edit_question(
|
||||
question: str = Form(...),
|
||||
canonical_answer: str = Form(...),
|
||||
hint: str = Form(None),
|
||||
question_type: str = Form("text"),
|
||||
options_raw: str = Form(None),
|
||||
status_val: str = Form("active"),
|
||||
sort_order: int = Form(0),
|
||||
db: Session = Depends(get_db)
|
||||
@@ -379,6 +417,18 @@ async def admin_edit_question(
|
||||
q.question = question.strip()
|
||||
q.canonical_answer = canonical_answer.strip()
|
||||
q.hint = hint.strip() if hint else None
|
||||
q.question_type = question_type.strip()
|
||||
|
||||
if options_raw and options_raw.strip():
|
||||
val = options_raw.strip()
|
||||
if val.startswith("[") and val.endswith("]"):
|
||||
q.options = val
|
||||
else:
|
||||
items = [x.strip() for x in val.split(",") if x.strip()]
|
||||
q.options = json.dumps(items, ensure_ascii=False)
|
||||
else:
|
||||
q.options = None
|
||||
|
||||
q.status = status_val.strip()
|
||||
q.sort_order = sort_order
|
||||
db.commit()
|
||||
|
||||
Reference in New Issue
Block a user