Files
notytex/routes/classes.py

263 lines
11 KiB
Python

from flask import Blueprint, render_template, redirect, url_for, flash, request, jsonify, current_app, abort
from models import db, ClassGroup, Student, Assessment
from forms import ClassGroupForm
from utils import handle_db_errors, ValidationError
from repositories.class_repository import ClassRepository
bp = Blueprint('classes', __name__, url_prefix='/classes')
@bp.route('/new')
@handle_db_errors
def new():
"""Formulaire de création d'une nouvelle classe."""
form = ClassGroupForm()
return render_template('class_form.html',
form=form,
title="Créer une nouvelle classe",
is_edit=False)
@bp.route('/', methods=['POST'])
@handle_db_errors
def create():
"""Traitement de la création d'une classe."""
form = ClassGroupForm()
class_repo = ClassRepository()
if form.validate_on_submit():
try:
# Vérification d'unicité du nom de classe
if class_repo.exists_by_name(form.name.data):
flash('Une classe avec ce nom existe déjà.', 'error')
return render_template('class_form.html',
form=form,
title="Créer une nouvelle classe",
is_edit=False)
# Création de la nouvelle classe
class_group = ClassGroup(
name=form.name.data,
description=form.description.data,
year=form.year.data
)
db.session.add(class_group)
db.session.commit()
current_app.logger.info(f'Nouvelle classe créée: {class_group.name}')
flash(f'Classe "{class_group.name}" créée avec succès !', 'success')
return redirect(url_for('classes'))
except Exception as e:
db.session.rollback()
current_app.logger.error(f'Erreur lors de la création de la classe: {e}')
flash('Erreur lors de la création de la classe.', 'error')
return render_template('class_form.html',
form=form,
title="Créer une nouvelle classe",
is_edit=False)
@bp.route('/<int:id>/edit')
@handle_db_errors
def edit(id):
"""Formulaire de modification d'une classe."""
class_repo = ClassRepository()
class_group = class_repo.get_or_404(id)
form = ClassGroupForm(obj=class_group)
return render_template('class_form.html',
form=form,
class_group=class_group,
title=f"Modifier la classe {class_group.name}",
is_edit=True)
@bp.route('/<int:id>', methods=['POST'])
@handle_db_errors
def update(id):
"""Traitement de la modification d'une classe."""
class_repo = ClassRepository()
class_group = class_repo.get_or_404(id)
form = ClassGroupForm()
if form.validate_on_submit():
try:
# Vérification d'unicité du nom (sauf si c'est le même nom)
if class_repo.exists_by_name(form.name.data, exclude_id=id):
flash('Une autre classe avec ce nom existe déjà.', 'error')
return render_template('class_form.html',
form=form,
class_group=class_group,
title=f"Modifier la classe {class_group.name}",
is_edit=True)
# Mise à jour des données
class_group.name = form.name.data
class_group.description = form.description.data
class_group.year = form.year.data
db.session.commit()
current_app.logger.info(f'Classe modifiée: {class_group.name}')
flash(f'Classe "{class_group.name}" modifiée avec succès !', 'success')
return redirect(url_for('classes'))
except Exception as e:
db.session.rollback()
current_app.logger.error(f'Erreur lors de la modification de la classe: {e}')
flash('Erreur lors de la modification de la classe.', 'error')
return render_template('class_form.html',
form=form,
class_group=class_group,
title=f"Modifier la classe {class_group.name}",
is_edit=True)
@bp.route('/<int:id>/delete', methods=['POST'])
@handle_db_errors
def delete(id):
"""Suppression d'une classe avec vérifications."""
class_repo = ClassRepository()
class_group = class_repo.get_or_404(id)
try:
# Vérifier s'il y a des étudiants ou des évaluations liés
can_delete, dependencies = class_repo.can_be_deleted(id)
if not can_delete:
students_count = dependencies['students']
assessments_count = dependencies['assessments']
flash(
f'Impossible de supprimer la classe "{class_group.name}". '
f'Elle contient {students_count} élève(s) et {assessments_count} évaluation(s). '
f'Supprimez d\'abord ces éléments.',
'error'
)
return redirect(url_for('classes'))
# Suppression de la classe
db.session.delete(class_group)
db.session.commit()
current_app.logger.info(f'Classe supprimée: {class_group.name}')
flash(f'Classe "{class_group.name}" supprimée avec succès.', 'success')
except Exception as e:
db.session.rollback()
current_app.logger.error(f'Erreur lors de la suppression de la classe: {e}')
flash('Erreur lors de la suppression de la classe.', 'error')
return redirect(url_for('classes'))
@bp.route('/<int:id>', methods=['GET'])
@handle_db_errors
def details(id):
"""Redirection transparente vers le dashboard de classe."""
return redirect(url_for('classes.dashboard', id=id))
@bp.route('/<int:id>/dashboard')
@handle_db_errors
def dashboard(id):
"""Page de présentation de classe avec statistiques."""
# Récupération paramètre trimestre
trimester = request.args.get('trimestre', type=int)
if trimester and trimester not in [1, 2, 3]:
trimester = None
# Repository optimisé
class_repo = ClassRepository()
class_group = class_repo.find_with_statistics(id, trimester)
if not class_group:
abort(404)
current_app.logger.debug(f'Dashboard classe {id} affiché pour trimestre {trimester}')
return render_template('class_dashboard.html',
class_group=class_group,
selected_trimester=trimester)
@bp.route('/<int:id>/stats')
@handle_db_errors
def get_stats_api(id):
"""API JSON pour statistiques dynamiques (AJAX)."""
# Récupération paramètre trimestre
trimester = request.args.get('trimestre', type=int)
if trimester and trimester not in [1, 2, 3]:
trimester = None
# Repository optimisé
class_repo = ClassRepository()
class_group = class_repo.find_with_statistics(id, trimester)
if not class_group:
abort(404)
try:
# Construction de la réponse JSON avec les nouvelles méthodes du modèle
quantity_stats = class_group.get_trimester_statistics(trimester)
domain_analysis = class_group.get_domain_analysis(trimester)
competence_analysis = class_group.get_competence_analysis(trimester)
class_results = class_group.get_class_results(trimester)
# Compter le nombre d'évaluations selon le trimestre
if hasattr(class_group, '_filtered_assessments'):
assessments_count = len(class_group._filtered_assessments)
current_app.logger.debug(f'Assessments count from _filtered_assessments: {assessments_count}')
else:
# Fallback si _filtered_assessments n'existe pas
if trimester:
assessments_count = len([a for a in class_group.assessments if a.trimester == trimester])
else:
assessments_count = len(class_group.assessments)
current_app.logger.debug(f'Assessments count from fallback: {assessments_count}')
current_app.logger.debug(f'Final assessments_count value: {assessments_count}, type: {type(assessments_count)}')
stats = {
"quantity": {
"total": quantity_stats["total"],
"completed": quantity_stats["completed"],
"in_progress": quantity_stats["in_progress"],
"not_started": quantity_stats["not_started"]
},
"domains": domain_analysis["domains"], # Extraire directement le tableau
"competences": competence_analysis["competences"], # Extraire directement le tableau
"results": {
"average": class_results["overall_statistics"]["mean"],
"min": class_results["overall_statistics"]["min"],
"max": class_results["overall_statistics"]["max"],
"median": class_results["overall_statistics"]["median"],
"std_dev": class_results["overall_statistics"]["std_dev"],
"assessments_count": assessments_count,
"student_averages": class_results["student_averages"],
"student_averages_distribution": class_results["student_averages_distribution"]
}
}
current_app.logger.debug(f'Statistiques API générées pour classe {id}, trimestre {trimester}')
return jsonify(stats)
except Exception as e:
current_app.logger.error(f'Erreur génération statistiques API classe {id}: {e}')
return jsonify({"error": "Erreur lors de la génération des statistiques"}), 500
@bp.route('/<int:id>/details')
@handle_db_errors
def details_legacy(id):
"""Page de détail d'une classe avec ses étudiants et évaluations (legacy)."""
class_repo = ClassRepository()
class_group = class_repo.find_with_full_details(id)
if not class_group:
abort(404)
# Trier les étudiants par nom (optimisé en Python car déjà chargés)
students = sorted(class_group.students, key=lambda s: (s.last_name, s.first_name))
# Prendre les 5 évaluations les plus récentes (optimisé en Python car déjà chargées)
recent_assessments = sorted(class_group.assessments, key=lambda a: a.date, reverse=True)[:5]
return render_template('class_details.html',
class_group=class_group,
students=students,
recent_assessments=recent_assessments)