from flask import Blueprint, render_template, redirect, url_for, flash, request, jsonify, current_app from models import db from forms import AssessmentForm from services import AssessmentService from repositories import AssessmentRepository, ClassRepository from utils import handle_db_errors, ValidationError bp = Blueprint('assessments', __name__, url_prefix='/assessments') @bp.route('/') @handle_db_errors def list(): assessment_repo = AssessmentRepository() class_repo = ClassRepository() # Récupérer les paramètres de filtrage trimester_filter = request.args.get('trimester', '') class_filter = request.args.get('class', '') correction_filter = request.args.get('correction', '') sort_by = request.args.get('sort', 'date_desc') # Utiliser le repository pour les filtres assessments = assessment_repo.find_by_filters( trimester=int(trimester_filter) if trimester_filter else None, class_id=int(class_filter) if class_filter else None, correction_status=correction_filter if correction_filter else None, sort_by=sort_by ) # Récupérer le total non filtré pour le compteur total_assessments = assessment_repo.find_by_filters() # Récupérer toutes les classes pour le filtre classes = class_repo.find_for_form_choices() return render_template('assessments.html', assessments=assessments, classes=classes, total_assessments_count=len(total_assessments), current_trimester=trimester_filter, current_class=class_filter, current_correction=correction_filter, current_sort=sort_by) # Route obsolète supprimée - utiliser new_unified à la place @bp.route('/') @handle_db_errors def detail(id): assessment_repo = AssessmentRepository() assessment = assessment_repo.get_with_full_details_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('//edit', methods=['GET', 'POST']) @handle_db_errors def edit(id): assessment_repo = AssessmentRepository() class_repo = ClassRepository() assessment = assessment_repo.get_with_full_details_or_404(id) form = AssessmentForm(obj=assessment) form.populate_class_choices(class_repo) 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 class_repo = ClassRepository() form = AssessmentForm() form.populate_class_choices(class_repo) 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('//results') @handle_db_errors def results(id): assessment_repo = AssessmentRepository() assessment = assessment_repo.get_with_full_details_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('//delete', methods=['POST']) @handle_db_errors def delete(id): assessment_repo = AssessmentRepository() assessment = assessment_repo.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'))