239 lines
9.4 KiB
Python
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')) |