Feat: add class page
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
from typing import List, Optional, Dict, Tuple
|
||||
from sqlalchemy.orm import joinedload
|
||||
from sqlalchemy.orm import joinedload, selectinload
|
||||
from sqlalchemy import and_
|
||||
from models import ClassGroup, Student, Assessment
|
||||
from models import ClassGroup, Student, Assessment, Exercise, GradingElement, Grade, Domain
|
||||
from .base_repository import BaseRepository
|
||||
|
||||
|
||||
@@ -207,4 +207,140 @@ class ClassRepository(BaseRepository[ClassGroup]):
|
||||
Returns:
|
||||
List[ClassGroup]: Liste des classes triées par nom
|
||||
"""
|
||||
return ClassGroup.query.order_by(ClassGroup.name).all()
|
||||
return ClassGroup.query.order_by(ClassGroup.name).all()
|
||||
|
||||
def find_with_statistics(self, class_id: int, trimester: Optional[int] = None) -> Optional[ClassGroup]:
|
||||
"""
|
||||
Récupère une classe avec toutes les données nécessaires pour les statistiques.
|
||||
Optimise les requêtes pour éviter les problèmes N+1 en chargeant toutes les relations
|
||||
nécessaires en une seule requête.
|
||||
|
||||
Args:
|
||||
class_id: Identifiant de la classe
|
||||
trimester: Trimestre à filtrer (1, 2, 3) ou None pour tous
|
||||
|
||||
Returns:
|
||||
Optional[ClassGroup]: La classe avec toutes ses données ou None
|
||||
"""
|
||||
try:
|
||||
# Construire la requête avec toutes les jointures optimisées
|
||||
query = ClassGroup.query.options(
|
||||
joinedload(ClassGroup.students),
|
||||
selectinload(ClassGroup.assessments).selectinload(Assessment.exercises)
|
||||
.selectinload(Exercise.grading_elements).selectinload(GradingElement.grades)
|
||||
).filter_by(id=class_id)
|
||||
|
||||
class_group = query.first()
|
||||
|
||||
# Filtrer les évaluations après récupération pour optimiser les calculs statistiques
|
||||
if class_group:
|
||||
if trimester is not None:
|
||||
class_group._filtered_assessments = [
|
||||
assessment for assessment in class_group.assessments
|
||||
if assessment.trimester == trimester
|
||||
]
|
||||
else:
|
||||
# Pour le mode global, on garde toutes les évaluations
|
||||
class_group._filtered_assessments = class_group.assessments
|
||||
|
||||
return class_group
|
||||
|
||||
except Exception as e:
|
||||
# Log l'erreur (utilisera le système de logging structuré)
|
||||
from flask import current_app
|
||||
current_app.logger.error(
|
||||
f"Erreur lors de la récupération de la classe {class_id} avec statistiques: {e}",
|
||||
extra={'class_id': class_id, 'trimester': trimester}
|
||||
)
|
||||
return None
|
||||
|
||||
def get_assessments_by_trimester(self, class_id: int, trimester: Optional[int] = None) -> List[Assessment]:
|
||||
"""
|
||||
Récupère les évaluations d'une classe filtrées par trimestre.
|
||||
Optimise le chargement pour les calculs de progression.
|
||||
|
||||
Args:
|
||||
class_id: Identifiant de la classe
|
||||
trimester: Trimestre à filtrer (1, 2, 3) ou None pour toutes
|
||||
|
||||
Returns:
|
||||
List[Assessment]: Liste des évaluations triées par date décroissante
|
||||
"""
|
||||
try:
|
||||
# Requête optimisée avec préchargement des relations pour grading_progress
|
||||
query = Assessment.query.options(
|
||||
selectinload(Assessment.exercises).selectinload(Exercise.grading_elements)
|
||||
.selectinload(GradingElement.grades)
|
||||
).filter_by(class_group_id=class_id)
|
||||
|
||||
# Filtrage par trimestre si spécifié
|
||||
if trimester is not None:
|
||||
query = query.filter(Assessment.trimester == trimester)
|
||||
|
||||
# Tri par date décroissante (plus récentes d'abord)
|
||||
assessments = query.order_by(Assessment.date.desc()).all()
|
||||
|
||||
return assessments
|
||||
|
||||
except Exception as e:
|
||||
from flask import current_app
|
||||
current_app.logger.error(
|
||||
f"Erreur lors de la récupération des évaluations pour la classe {class_id}: {e}",
|
||||
extra={'class_id': class_id, 'trimester': trimester}
|
||||
)
|
||||
return []
|
||||
|
||||
def find_with_assessments_optimized(self, class_id: int, trimester: Optional[int] = None) -> Optional[ClassGroup]:
|
||||
"""
|
||||
Version optimisée pour la page dashboard avec préchargement intelligent.
|
||||
Cette méthode évite les requêtes multiples pour les calculs de grading_progress.
|
||||
|
||||
Args:
|
||||
class_id: Identifiant de la classe
|
||||
trimester: Trimestre à filtrer (1, 2, 3) ou None pour tous
|
||||
|
||||
Returns:
|
||||
Optional[ClassGroup]: La classe avec ses évaluations optimisées ou None
|
||||
"""
|
||||
try:
|
||||
# Single-query avec toutes les relations nécessaires
|
||||
base_query = ClassGroup.query.options(
|
||||
joinedload(ClassGroup.students),
|
||||
selectinload(ClassGroup.assessments).selectinload(Assessment.exercises)
|
||||
.selectinload(Exercise.grading_elements).selectinload(GradingElement.grades)
|
||||
)
|
||||
|
||||
class_group = base_query.filter_by(id=class_id).first()
|
||||
|
||||
if not class_group:
|
||||
return None
|
||||
|
||||
# Pré-filtrer les évaluations par trimestre
|
||||
if trimester is not None:
|
||||
filtered_assessments = [
|
||||
assessment for assessment in class_group.assessments
|
||||
if assessment.trimester == trimester
|
||||
]
|
||||
# Stocker les évaluations filtrées pour éviter les recalculs
|
||||
class_group._filtered_assessments = sorted(
|
||||
filtered_assessments,
|
||||
key=lambda x: x.date,
|
||||
reverse=True
|
||||
)
|
||||
else:
|
||||
# Trier toutes les évaluations par date décroissante
|
||||
class_group._filtered_assessments = sorted(
|
||||
class_group.assessments,
|
||||
key=lambda x: x.date,
|
||||
reverse=True
|
||||
)
|
||||
|
||||
return class_group
|
||||
|
||||
except Exception as e:
|
||||
from flask import current_app
|
||||
current_app.logger.error(
|
||||
f"Erreur lors de la récupération optimisée de la classe {class_id}: {e}",
|
||||
extra={'class_id': class_id, 'trimester': trimester}
|
||||
)
|
||||
return None
|
||||
Reference in New Issue
Block a user