App Flask complète pour https://dynamic.alpinux.org : - 10 quiz Linux, 5 niveaux (Découverte → Expert), 50+ questions - Public : Découverte, Débutant, Intermédiaire (6 quiz) - Membres AlpID : Avancé, Expert (4 quiz — Git, Admin, Sécurité, Bash) - Navigation question par question avec avance automatique après choix - Score calculé côté serveur, enregistré en SQLite si connecté - Page profil : meilleurs scores par quiz + historique des tentatives Authentification : - OIDC via authlib + AlpID (Keycloak), SSO partagé avec Gitea/Nextcloud - Décorateur @login_required, redirection post-login sur l'URL d'origine - /auth/login, /auth/callback, /auth/logout Structure : - dynamic/app.py, db.py, quiz.py, auth_utils.py - dynamic/routes/ (public.py, auth.py, protected.py) - dynamic/templates/ (base, index, quiz/*, profil/) - dynamic/static/ (style.css thème Alpinux, quiz.js vanilla) - dynamic/data/quizzes.json (source de vérité des questions) - dynamic/.env.example Infrastructure : - scripts/dynamic.alpinux.org.vhost.conf (Apache reverse proxy) - scripts/dynamic.alpinux.org.service (systemd Gunicorn) - docs/technique/deploiement-dynamic.md (procédure complète) - mkdocs.yml : page de déploiement ajoutée à la nav Technique - .gitignore : exclut venv/ et .env Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
85 lines
2.6 KiB
Python
85 lines
2.6 KiB
Python
import json
|
|
from flask import Blueprint, render_template, request, redirect, url_for, session, abort
|
|
|
|
from auth_utils import current_user, login_required
|
|
from quiz import get_all, get_by_id
|
|
from db import save_result
|
|
|
|
public_bp = Blueprint('public', __name__)
|
|
|
|
|
|
@public_bp.route('/')
|
|
def index():
|
|
quizzes = get_all()
|
|
return render_template('index.html', quizzes=quizzes, user=current_user())
|
|
|
|
|
|
@public_bp.route('/quiz/')
|
|
def quiz_list():
|
|
quizzes = get_all()
|
|
return render_template('quiz/list.html', quizzes=quizzes, user=current_user())
|
|
|
|
|
|
@public_bp.route('/quiz/<quiz_id>/')
|
|
def quiz_intro(quiz_id):
|
|
quiz = get_by_id(quiz_id)
|
|
if not quiz:
|
|
abort(404)
|
|
return render_template('quiz/intro.html', quiz=quiz, user=current_user())
|
|
|
|
|
|
@public_bp.route('/quiz/<quiz_id>/jouer', methods=['GET', 'POST'])
|
|
def quiz_play(quiz_id):
|
|
quiz = get_by_id(quiz_id)
|
|
if not quiz:
|
|
abort(404)
|
|
|
|
if quiz['members_only'] and not current_user():
|
|
session['next_url'] = url_for('public.quiz_play', quiz_id=quiz_id)
|
|
return redirect(url_for('auth.login'))
|
|
|
|
if request.method == 'POST':
|
|
answers = {}
|
|
for i in range(len(quiz['questions'])):
|
|
val = request.form.get(f'q{i}')
|
|
answers[i] = int(val) if val is not None else -1
|
|
|
|
score = sum(
|
|
1 for i, q in enumerate(quiz['questions'])
|
|
if answers.get(i) == q['answer']
|
|
)
|
|
total = len(quiz['questions'])
|
|
|
|
if current_user():
|
|
save_result(
|
|
current_user()['sub'],
|
|
current_user()['name'],
|
|
quiz_id, score, total,
|
|
)
|
|
|
|
# Stocke résultat et détail en session pour la page résultat
|
|
session['last_result'] = {
|
|
'quiz_id': quiz_id,
|
|
'score': score,
|
|
'total': total,
|
|
'answers': answers,
|
|
}
|
|
return redirect(url_for('public.quiz_result', quiz_id=quiz_id))
|
|
|
|
return render_template('quiz/play.html',
|
|
quiz=quiz,
|
|
quiz_json=json.dumps(quiz),
|
|
user=current_user())
|
|
|
|
|
|
@public_bp.route('/quiz/<quiz_id>/resultat')
|
|
def quiz_result(quiz_id):
|
|
quiz = get_by_id(quiz_id)
|
|
result = session.pop('last_result', None)
|
|
if not quiz or not result or result['quiz_id'] != quiz_id:
|
|
return redirect(url_for('public.quiz_intro', quiz_id=quiz_id))
|
|
|
|
return render_template('quiz/result.html',
|
|
quiz=quiz,
|
|
result=result,
|
|
user=current_user())
|