mirror of
https://github.com/arthur-pbty/QCM_physique.git
synced 2026-06-15 15:55:29 +02:00
first commit
This commit is contained in:
@@ -0,0 +1,178 @@
|
||||
from flask import Flask, render_template, request
|
||||
import unicodedata
|
||||
import sqlite3
|
||||
import json
|
||||
import os
|
||||
|
||||
APP_DIR = os.path.dirname(__file__)
|
||||
DB_PATH = os.path.join(APP_DIR, 'qcm.db')
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
|
||||
def get_questions():
|
||||
conn = sqlite3.connect(DB_PATH)
|
||||
# Récupérer les champs textes comme octets bruts pour les décoder manuellement
|
||||
conn.text_factory = bytes
|
||||
c = conn.cursor()
|
||||
c.execute('SELECT id, question, answers, last_scraped FROM questions ORDER BY id')
|
||||
rows = c.fetchall()
|
||||
conn.close()
|
||||
|
||||
questions = []
|
||||
|
||||
def norm(s):
|
||||
return unicodedata.normalize('NFC', s) if isinstance(s, str) else s
|
||||
|
||||
def decode_text(raw) -> str:
|
||||
"""Décoder une valeur provenant de la base : bytes ou str.
|
||||
Essaie plusieurs décodages usuels pour éviter le caractère de remplacement �.
|
||||
"""
|
||||
if raw is None:
|
||||
return raw
|
||||
|
||||
# Si on reçoit déjà une str, vérifier si elle contient des séquences suspectes
|
||||
if isinstance(raw, str):
|
||||
s = raw
|
||||
if '\ufffd' not in s and 'Ã' not in s and 'Â' not in s:
|
||||
return norm(s)
|
||||
# tenter de ré-interpréter comme latin1 -> utf-8
|
||||
try:
|
||||
cand = s.encode('latin1').decode('utf-8')
|
||||
if '\ufffd' not in cand:
|
||||
return norm(cand)
|
||||
except Exception:
|
||||
pass
|
||||
return s
|
||||
|
||||
# Si raw est bytes, essayer plusieurs encodages
|
||||
if isinstance(raw, (bytes, bytearray)):
|
||||
b = bytes(raw)
|
||||
# Ordre: utf-8 strict, cp1252, latin1, utf-8 replace
|
||||
try:
|
||||
s = b.decode('utf-8')
|
||||
# Cas fréquent : double-encodage UTF-8 -> on obtient des séquences "Ã"/"Â".
|
||||
# Tenter la réparation double-encodage : encoder en latin1 puis décoder en utf-8.
|
||||
if 'Ã' in s or 'Â' in s:
|
||||
try:
|
||||
repaired = s.encode('latin1', errors='replace').decode('utf-8', errors='replace')
|
||||
# si la réparation donne des caractères accentués, la garder
|
||||
if any(ch in repaired for ch in 'éèàêôçùÉÈÀÂ'):
|
||||
return norm(repaired)
|
||||
except Exception:
|
||||
pass
|
||||
if '\ufffd' not in s:
|
||||
return norm(s)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
for enc in ('cp1252', 'latin1'):
|
||||
try:
|
||||
s = b.decode(enc)
|
||||
if '\ufffd' not in s:
|
||||
return norm(s)
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
# fallback permissif
|
||||
try:
|
||||
return norm(b.decode('utf-8', errors='replace'))
|
||||
except Exception:
|
||||
return norm(b.decode('latin1', errors='replace'))
|
||||
|
||||
# si autre type, forcer str
|
||||
try:
|
||||
return norm(str(raw))
|
||||
finally:
|
||||
pass
|
||||
|
||||
for r in rows:
|
||||
qid, text, answers_json, last_scraped = r
|
||||
# Décoder proprement les champs (text, answers_json, last_scraped peuvent être bytes)
|
||||
text = decode_text(text) if text is not None else text
|
||||
answers_str = decode_text(answers_json) if answers_json is not None else '[]'
|
||||
last_scraped = decode_text(last_scraped) if last_scraped is not None else None
|
||||
try:
|
||||
answers = json.loads(answers_str)
|
||||
except Exception:
|
||||
answers = []
|
||||
|
||||
# normalize answers structure
|
||||
formatted = []
|
||||
for i, a in enumerate(answers):
|
||||
# a is expected to be dict with 'text' and 'correct'
|
||||
at = a.get('text') if isinstance(a, dict) else str(a)
|
||||
ac = a.get('correct') if isinstance(a, dict) else False
|
||||
at = decode_text(at)
|
||||
formatted.append({'idx': i, 'text': at, 'correct': bool(ac)})
|
||||
|
||||
questions.append({'id': qid, 'text': text, 'answers': formatted, 'last_scraped': last_scraped})
|
||||
|
||||
return questions
|
||||
|
||||
|
||||
@app.route('/')
|
||||
def index():
|
||||
qs = get_questions()
|
||||
# Sérialise les questions en JSON côté serveur pour l'insérer dans le JS sans dépendre du filtre tojson
|
||||
import json as _json
|
||||
# compact JSON, puis échapper </ pour éviter de fermer accidentellement le <script>
|
||||
questions_json = _json.dumps(qs, ensure_ascii=False, separators=(',',':'))
|
||||
questions_json = questions_json.replace('</', '<\/')
|
||||
return render_template('index.html', questions=qs, questions_json=questions_json)
|
||||
|
||||
|
||||
@app.route('/submit', methods=['POST'])
|
||||
def submit():
|
||||
qs = get_questions()
|
||||
results = []
|
||||
total = 0
|
||||
correct_count = 0
|
||||
|
||||
for q in qs:
|
||||
qid = q['id']
|
||||
name = f'q_{qid}'
|
||||
selected = request.form.getlist(name)
|
||||
selected_idx = set(int(x) for x in selected) if selected else set()
|
||||
|
||||
# compute correct set
|
||||
correct_set = set(i for i, a in enumerate(q['answers']) if a.get('correct'))
|
||||
|
||||
is_correct = (selected_idx == correct_set)
|
||||
if is_correct:
|
||||
correct_count += 1
|
||||
total += 1
|
||||
|
||||
# prepare per-answer feedback
|
||||
answers_feedback = []
|
||||
for a in q['answers']:
|
||||
idx = a['idx']
|
||||
answers_feedback.append({
|
||||
'text': a['text'],
|
||||
'is_correct': a['correct'],
|
||||
'selected': idx in selected_idx
|
||||
})
|
||||
|
||||
results.append({
|
||||
'id': qid,
|
||||
'text': q['text'],
|
||||
'answers': answers_feedback,
|
||||
'question_correct': is_correct,
|
||||
'correct_set': list(correct_set),
|
||||
'selected': list(selected_idx)
|
||||
})
|
||||
|
||||
score = {'correct': correct_count, 'total': total}
|
||||
|
||||
return render_template('result.html', results=results, score=score)
|
||||
|
||||
|
||||
@app.route('/external')
|
||||
def external():
|
||||
# Page qui embarque le "vrai" site dans une iframe
|
||||
external_url = 'https://alienor.myds.me/~cahierlabo/tmp/qcm_entrainement.html'
|
||||
return render_template('external.html', external_url=external_url)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(host='0.0.0.0', port=5000, debug=True)
|
||||
Reference in New Issue
Block a user