157 lines
		
	
	
		
			5.5 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			157 lines
		
	
	
		
			5.5 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| """
 | |
| Implémentations concrètes des providers pour l'injection de dépendances.
 | |
| 
 | |
| Ce module fournit les adaptateurs concrets qui implémentent les interfaces
 | |
| définies dans assessment_services.py, résolvant ainsi les imports circulaires.
 | |
| """
 | |
| from typing import Dict, Any, List
 | |
| from sqlalchemy.orm import joinedload
 | |
| from sqlalchemy import func
 | |
| 
 | |
| from models import db, Grade, GradingElement, Exercise
 | |
| 
 | |
| 
 | |
| class FlaskConfigProvider:
 | |
|     """
 | |
|     Implémentation concrète du ConfigProvider utilisant app_config.
 | |
|     Résout les imports circulaires en encapsulant l'accès à la configuration.
 | |
|     """
 | |
|     
 | |
|     def __init__(self):
 | |
|         # Import paresseux pour éviter les dépendances circulaires
 | |
|         self._config_manager = None
 | |
|     
 | |
|     @property
 | |
|     def config_manager(self):
 | |
|         """Accès paresseux au config_manager."""
 | |
|         if self._config_manager is None:
 | |
|             from app_config import config_manager
 | |
|             self._config_manager = config_manager
 | |
|         return self._config_manager
 | |
|     
 | |
|     def is_special_value(self, value: str) -> bool:
 | |
|         """Vérifie si une valeur est spéciale."""
 | |
|         return self.config_manager.is_special_value(value)
 | |
|     
 | |
|     def get_special_values(self) -> Dict[str, Dict[str, Any]]:
 | |
|         """Retourne la configuration des valeurs spéciales."""
 | |
|         return self.config_manager.get_special_values()
 | |
| 
 | |
| 
 | |
| class SQLAlchemyDatabaseProvider:
 | |
|     """
 | |
|     Implémentation concrète du DatabaseProvider utilisant SQLAlchemy.
 | |
|     Fournit des requêtes optimisées pour éviter les problèmes N+1.
 | |
|     """
 | |
|     
 | |
|     def get_grades_for_assessment(self, assessment_id: int) -> List[Dict[str, Any]]:
 | |
|         """
 | |
|         Récupère toutes les notes d'une évaluation en une seule requête optimisée.
 | |
|         Résout le problème N+1 identifié dans calculate_student_scores.
 | |
|         """
 | |
|         query = (
 | |
|             db.session.query(
 | |
|                 Grade.student_id,
 | |
|                 Grade.grading_element_id,
 | |
|                 Grade.value,
 | |
|                 GradingElement.grading_type,
 | |
|                 GradingElement.max_points
 | |
|             )
 | |
|             .join(GradingElement)
 | |
|             .join(Exercise)
 | |
|             .filter(Exercise.assessment_id == assessment_id)
 | |
|             .filter(Grade.value.isnot(None))
 | |
|             .filter(Grade.value != '')
 | |
|         )
 | |
|         
 | |
|         return [
 | |
|             {
 | |
|                 'student_id': row.student_id,
 | |
|                 'grading_element_id': row.grading_element_id,
 | |
|                 'value': row.value,
 | |
|                 'grading_type': row.grading_type,
 | |
|                 'max_points': row.max_points
 | |
|             }
 | |
|             for row in query.all()
 | |
|         ]
 | |
|     
 | |
|     def get_grading_elements_with_students(self, assessment_id: int) -> List[Dict[str, Any]]:
 | |
|         """
 | |
|         Récupère les éléments de notation avec le nombre de notes complétées.
 | |
|         Résout le problème N+1 identifié dans grading_progress.
 | |
|         """
 | |
|         # Sous-requête pour compter les grades complétés par élément
 | |
|         grades_subquery = (
 | |
|             db.session.query(
 | |
|                 Grade.grading_element_id,
 | |
|                 func.count(Grade.id).label('completed_count')
 | |
|             )
 | |
|             .filter(Grade.value.isnot(None))
 | |
|             .filter(Grade.value != '')
 | |
|             .group_by(Grade.grading_element_id)
 | |
|             .subquery()
 | |
|         )
 | |
|         
 | |
|         # Requête principale avec jointure
 | |
|         query = (
 | |
|             db.session.query(
 | |
|                 GradingElement.id,
 | |
|                 GradingElement.label,
 | |
|                 func.coalesce(grades_subquery.c.completed_count, 0).label('completed_grades_count')
 | |
|             )
 | |
|             .join(Exercise)
 | |
|             .outerjoin(grades_subquery, GradingElement.id == grades_subquery.c.grading_element_id)
 | |
|             .filter(Exercise.assessment_id == assessment_id)
 | |
|         )
 | |
|         
 | |
|         return [
 | |
|             {
 | |
|                 'element_id': row.id,
 | |
|                 'element_label': row.label,
 | |
|                 'completed_grades_count': row.completed_grades_count
 | |
|             }
 | |
|             for row in query.all()
 | |
|         ]
 | |
| 
 | |
| 
 | |
| # =================== FACTORY pour la création des services ===================
 | |
| 
 | |
| class AssessmentServicesFactory:
 | |
|     """
 | |
|     Factory pour créer l'ensemble des services avec injection de dépendances.
 | |
|     Centralise la création et la configuration des services.
 | |
|     """
 | |
|     
 | |
|     @classmethod
 | |
|     def create_facade(cls) -> 'AssessmentServicesFacade':
 | |
|         """
 | |
|         Crée une facade complète avec toutes les dépendances injectées.
 | |
|         Point d'entrée principal pour obtenir les services.
 | |
|         """
 | |
|         from services.assessment_services import AssessmentServicesFacade
 | |
|         
 | |
|         config_provider = FlaskConfigProvider()
 | |
|         db_provider = SQLAlchemyDatabaseProvider()
 | |
|         
 | |
|         return AssessmentServicesFacade(
 | |
|             config_provider=config_provider,
 | |
|             db_provider=db_provider
 | |
|         )
 | |
|     
 | |
|     @classmethod
 | |
|     def create_with_custom_providers(cls, 
 | |
|                                    config_provider=None, 
 | |
|                                    db_provider=None) -> 'AssessmentServicesFacade':
 | |
|         """
 | |
|         Crée une facade avec des providers personnalisés.
 | |
|         Utile pour les tests avec des mocks.
 | |
|         """
 | |
|         from services.assessment_services import AssessmentServicesFacade
 | |
|         
 | |
|         config_provider = config_provider or FlaskConfigProvider()
 | |
|         db_provider = db_provider or SQLAlchemyDatabaseProvider()
 | |
|         
 | |
|         return AssessmentServicesFacade(
 | |
|             config_provider=config_provider,
 | |
|             db_provider=db_provider
 | |
|         ) |