alpinux.site.2026/dynamic/templates/quiz/list.html
Cédrix 27847dfad3 feat: dynamic.alpinux.org — quiz interactifs Flask + AlpID OIDC
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>
2026-05-03 12:09:37 +02:00

64 lines
2.2 KiB
HTML

{% extends "base.html" %}
{% block title %}Tous les quiz{% endblock %}
{% block description %}10 quiz Linux de tous niveaux — découverte, débutant, intermédiaire, avancé, expert.{% endblock %}
{% block content %}
<div class="page-header">
<h1>Tous les quiz</h1>
<p>{{ quizzes|length }} quiz — du niveau Découverte à Expert</p>
</div>
{% set levels = [
(1, "🌱", "Découverte", "public"),
(2, "📘", "Débutant", "public"),
(3, "⚡", "Intermédiaire", "public"),
(4, "🔧", "Avancé", "members"),
(5, "🏆", "Expert", "members"),
] %}
{% for level_id, icon, label, access in levels %}
{% set level_quizzes = quizzes | selectattr("level_id", "equalto", level_id) | list %}
{% if level_quizzes %}
<section class="level-section">
<h2 class="level-heading level-heading-{{ level_id }}">
{{ icon }} Niveau {{ label }}
{% if access == "members" %}
<span class="badge members">Membres AlpID</span>
{% else %}
<span class="badge public">Public</span>
{% endif %}
</h2>
<div class="quiz-grid">
{% for quiz in level_quizzes %}
<a href="{{ url_for('public.quiz_intro', quiz_id=quiz.id) }}"
class="quiz-card {% if quiz.members_only %}quiz-card--members{% endif %}">
<span class="quiz-icon">{{ quiz.icon }}</span>
<div class="quiz-card-body">
<h3>{{ quiz.title }}</h3>
<p>{{ quiz.description }}</p>
<div class="quiz-meta">
<span class="quiz-duration">⏱ {{ quiz.duration_min }} min</span>
<span class="quiz-count">{{ quiz.questions | length }} questions</span>
{% if quiz.members_only and not user %}
<span class="lock-badge">🔒 Connexion requise</span>
{% endif %}
</div>
</div>
</a>
{% endfor %}
</div>
</section>
{% endif %}
{% endfor %}
{% if not user %}
<div class="members-cta">
<span class="cta-icon">🔒</span>
<div>
<strong>Quiz Avancé et Expert réservés aux membres</strong>
<p>Créez un compte AlpID pour accéder aux 4 quiz avancés.</p>
</div>
<a href="{{ url_for('auth.login') }}" class="btn btn-primary">Se connecter</a>
</div>
{% endif %}
{% endblock %}