Files
notytex/routes/assessments.py
2025-08-06 20:34:55 +02:00

239 lines
9.4 KiB
Python

from flask import Blueprint, render_template, redirect, url_for, flash, request, jsonify, current_app
from models import db, Assessment, ClassGroup
from forms import AssessmentForm
from services import AssessmentService
from utils import handle_db_errors, ValidationError
from datetime import datetime
bp = Blueprint('assessments', __name__, url_prefix='/assessments')
@bp.route('/')
@handle_db_errors
def list():
from sqlalchemy.orm import joinedload
# Récupérer les paramètres de filtrage
trimester_filter = request.args.get('trimester', '')
class_filter = request.args.get('class', '')
sort_by = request.args.get('sort', 'date_desc')
# Construire la requête de base
query = Assessment.query.options(joinedload(Assessment.class_group))
# Appliquer les filtres
if trimester_filter:
query = query.filter(Assessment.trimester == int(trimester_filter))
if class_filter:
query = query.filter(Assessment.class_group_id == int(class_filter))
# Appliquer le tri
if sort_by == 'date_desc':
query = query.order_by(Assessment.date.desc())
elif sort_by == 'date_asc':
query = query.order_by(Assessment.date.asc())
elif sort_by == 'title':
query = query.order_by(Assessment.title.asc())
elif sort_by == 'class':
query = query.join(ClassGroup).order_by(ClassGroup.name.asc())
assessments = query.all()
# Récupérer toutes les classes pour le filtre
classes = ClassGroup.query.order_by(ClassGroup.name.asc()).all()
return render_template('assessments.html',
assessments=assessments,
classes=classes,
current_trimester=trimester_filter,
current_class=class_filter,
current_sort=sort_by)
# Route obsolète supprimée - utiliser new_unified à la place
@bp.route('/<int:id>')
@handle_db_errors
def detail(id):
from sqlalchemy.orm import joinedload
from models import Exercise, GradingElement
assessment = Assessment.query.options(
joinedload(Assessment.class_group),
joinedload(Assessment.exercises).joinedload(Exercise.grading_elements)
).get_or_404(id)
return render_template('assessment_detail.html', assessment=assessment)
def _handle_unified_assessment_request(form, assessment=None, is_edit=False):
"""Fonction helper pour traiter les requêtes JSON d'évaluation unifiée"""
# Ne traiter que les requêtes POST
if request.method != 'POST':
return None
if request.is_json:
try:
data = request.get_json()
if not data:
return jsonify({'success': False, 'error': 'Aucune donnée fournie'}), 400
# Peupler le formulaire pour validation CSRF
form.csrf_token.data = data.get('csrf_token')
# Traitement via le service
if is_edit and assessment:
processed_assessment = AssessmentService.process_assessment_with_exercises(
data, is_edit=True, existing_assessment=assessment
)
else:
processed_assessment = AssessmentService.process_assessment_with_exercises(data)
db.session.commit()
flash('Évaluation ' + ('modifiée' if is_edit else 'créée') + ' avec succès !', 'success')
return jsonify({'success': True, 'assessment_id': processed_assessment.id})
except ValidationError as e:
current_app.logger.warning(f'Erreur de validation: {e}')
return jsonify({'success': False, 'error': str(e)}), 400
except ValueError as e:
current_app.logger.warning(f'Erreur de données: {e}')
return jsonify({'success': False, 'error': str(e)}), 400
else: # request.method == 'POST' and not request.is_json
# Traitement classique du formulaire (fallback)
if form.validate_on_submit():
if is_edit and assessment:
AssessmentService.update_assessment_basic_info(assessment, {
'title': form.title.data,
'description': form.description.data,
'date': form.date.data,
'trimester': form.trimester.data,
'class_group_id': form.class_group_id.data,
'coefficient': form.coefficient.data
})
else:
assessment = AssessmentService.create_assessment({
'title': form.title.data,
'description': form.description.data,
'date': form.date.data,
'trimester': form.trimester.data,
'class_group_id': form.class_group_id.data,
'coefficient': form.coefficient.data
})
db.session.commit()
flash('Évaluation ' + ('modifiée' if is_edit else 'créée') + ' avec succès !', 'success')
return redirect(url_for('assessments.detail', id=assessment.id))
return None
@bp.route('/<int:id>/edit', methods=['GET', 'POST'])
@handle_db_errors
def edit(id):
from sqlalchemy.orm import joinedload
from models import Exercise, GradingElement
assessment = Assessment.query.options(
joinedload(Assessment.class_group),
joinedload(Assessment.exercises).joinedload(Exercise.grading_elements)
).get_or_404(id)
form = AssessmentForm(obj=assessment)
result = _handle_unified_assessment_request(form, assessment, is_edit=True)
if result:
return result
# Préparer les exercices pour la sérialisation JSON
exercises_data = []
for exercise in assessment.exercises:
exercise_data = {
'id': exercise.id,
'title': exercise.title,
'description': exercise.description or '',
'order': exercise.order,
'grading_elements': []
}
for element in exercise.grading_elements:
element_data = {
'id': element.id,
'label': element.label,
'description': element.description or '',
'skill': element.skill or '',
'max_points': float(element.max_points),
'grading_type': element.grading_type,
'domain_id': element.domain_id
}
exercise_data['grading_elements'].append(element_data)
exercises_data.append(exercise_data)
# Récupérer les compétences et domaines configurées
from app_config import config_manager
competences = config_manager.get_competences_list()
domains = config_manager.get_domains_list()
return render_template('assessment_form_unified.html',
form=form,
title='Modifier l\'évaluation complète',
assessment=assessment,
exercises_json=exercises_data,
is_edit=True,
competences=competences,
domains=domains)
@bp.route('/new', methods=['GET', 'POST'])
@handle_db_errors
def new():
from app_config import config_manager
form = AssessmentForm()
result = _handle_unified_assessment_request(form, is_edit=False)
if result:
return result
# Récupérer les compétences et domaines configurées
competences = config_manager.get_competences_list()
domains = config_manager.get_domains_list()
return render_template('assessment_form_unified.html',
form=form,
title='Nouvelle évaluation complète',
competences=competences,
domains=domains)
@bp.route('/<int:id>/results')
@handle_db_errors
def results(id):
from sqlalchemy.orm import joinedload
from models import Exercise, GradingElement
assessment = Assessment.query.options(
joinedload(Assessment.class_group),
joinedload(Assessment.exercises).joinedload(Exercise.grading_elements)
).get_or_404(id)
# Calculer les scores des élèves
students_scores, exercise_scores = assessment.calculate_student_scores()
# Trier les élèves par ordre alphabétique
sorted_students = sorted(students_scores.values(),
key=lambda x: (x['student'].last_name.lower(), x['student'].first_name.lower()))
# Calculer les statistiques
statistics = assessment.get_assessment_statistics()
total_max_points = assessment.get_total_max_points()
# Préparer les données pour l'histogramme
scores = [data['total_score'] for data in students_scores.values()]
return render_template('assessment_results.html',
assessment=assessment,
students_scores=sorted_students,
statistics=statistics,
total_max_points=total_max_points,
scores_json=scores)
@bp.route('/<int:id>/delete', methods=['POST'])
@handle_db_errors
def delete(id):
assessment = Assessment.query.get_or_404(id)
title = assessment.title # Conserver pour le log
db.session.delete(assessment)
db.session.commit()
current_app.logger.info(f'Évaluation supprimée: {title} (ID: {id})')
flash('Évaluation supprimée avec succès !', 'success')
return redirect(url_for('assessments.list'))