From 635269ff36e87a17f914856ebc4eb85498423306 Mon Sep 17 00:00:00 2001
From: Emmanuel Viennet <emmanuel.viennet@gmail.com>
Date: Fri, 22 Mar 2024 11:49:51 +0100
Subject: [PATCH] Modifie FormSemestre.etudids_actifs: retire @cached_property.
 Tests OK.

---
 app/comp/moy_mod.py             | 36 ++++++++++++++++++---------------
 app/comp/moy_ue.py              | 21 +++++++++++--------
 app/comp/res_classic.py         |  3 ++-
 app/models/formsemestre.py      | 12 +++++++----
 app/scodoc/sco_liste_notes.py   | 16 +++++++--------
 app/scodoc/sco_saisie_notes.py  |  2 +-
 tests/api/test_api_etudiants.py |  3 +++
 tests/unit/test_but_modules.py  | 10 +++++----
 8 files changed, 61 insertions(+), 42 deletions(-)

diff --git a/app/comp/moy_mod.py b/app/comp/moy_mod.py
index dacc4d87b..9418f96d5 100644
--- a/app/comp/moy_mod.py
+++ b/app/comp/moy_mod.py
@@ -72,7 +72,15 @@ class ModuleImplResults:
     les caches sont gérés par  ResultatsSemestre.
     """
 
-    def __init__(self, moduleimpl: ModuleImpl):
+    def __init__(
+        self, moduleimpl: ModuleImpl, etudids: list[int], etudids_actifs: set[int]
+    ):
+        """
+        Args:
+        - etudids : liste des etudids, qui donne l'index du dataframe
+            (doit être tous les étudiants inscrits au semestre incluant les DEM et DEF)
+        - etudids_actifs l'ensemble des étudiants inscrits au semestre, non DEM/DEF.
+        """
         self.moduleimpl_id = moduleimpl.id
         self.module_id = moduleimpl.module.id
         self.etudids = None
@@ -105,14 +113,21 @@ class ModuleImplResults:
         """
         self.evals_etudids_sans_note = {}
         """dict: evaluation_id : set des etudids non notés dans cette eval, sans les démissions."""
-        self.load_notes()
+        self.load_notes(etudids, etudids_actifs)
         self.etuds_use_session2 = pd.Series(False, index=self.evals_notes.index)
         """1 bool par etud, indique si sa moyenne de module vient de la session2"""
         self.etuds_use_rattrapage = pd.Series(False, index=self.evals_notes.index)
         """1 bool par etud, indique si sa moyenne de module utilise la note de rattrapage"""
 
-    def load_notes(self):  # ré-écriture de df_load_modimpl_notes
+    def load_notes(
+        self, etudids: list[int], etudids_actifs: set[int]
+    ):  # ré-écriture de df_load_modimpl_notes
         """Charge toutes les notes de toutes les évaluations du module.
+        Args:
+        - etudids : liste des etudids, qui donne l'index du dataframe
+            (doit être tous les étudiants inscrits au semestre incluant les DEM et DEF)
+        - etudids_actifs l'ensemble des étudiants inscrits au semestre, non DEM/DEF.
+
         Dataframe evals_notes
             colonnes: le nom de la colonne est l'evaluation_id (int)
             index (lignes): etudid (int)
@@ -135,12 +150,12 @@ class ModuleImplResults:
         qui ont des notes ATT.
         """
         moduleimpl = db.session.get(ModuleImpl, self.moduleimpl_id)
-        self.etudids = self._etudids()
+        self.etudids = etudids
 
         # --- Calcul nombre d'inscrits pour déterminer les évaluations "completes":
         # on prend les inscrits au module ET au semestre (donc sans démissionnaires)
         inscrits_module = {ins.etud.id for ins in moduleimpl.inscriptions}.intersection(
-            moduleimpl.formsemestre.etudids_actifs
+            etudids_actifs
         )
         self.nb_inscrits_module = len(inscrits_module)
 
@@ -235,17 +250,6 @@ class ModuleImplResults:
         eval_df[str(evaluation.id)] = pd.to_numeric(eval_df[str(evaluation.id)])
         return eval_df
 
-    def _etudids(self):
-        """L'index du dataframe est la liste de tous les étudiants inscrits au semestre
-        (incluant les DEM et DEF)
-        """
-        return [
-            inscr.etudid
-            for inscr in db.session.get(
-                ModuleImpl, self.moduleimpl_id
-            ).formsemestre.inscriptions
-        ]
-
     def get_evaluations_coefs(self, modimpl: ModuleImpl) -> np.array:
         """Coefficients des évaluations.
         Les coefs des évals incomplètes, rattrapage, session 2, bonus sont forcés à zéro.
diff --git a/app/comp/moy_ue.py b/app/comp/moy_ue.py
index 6f8004188..7e9e83cbb 100644
--- a/app/comp/moy_ue.py
+++ b/app/comp/moy_ue.py
@@ -99,9 +99,11 @@ def df_load_module_coefs(formation_id: int, semestre_idx: int = None) -> pd.Data
     # 0 pour modules normaux, 1. pour bonus (car par défaut, on veut qu'un bonus agisse
     # sur toutes les UE)
     default_poids = {
-        mod.id: 1.0
-        if (mod.module_type == ModuleType.STANDARD) and (mod.ue.type == UE_SPORT)
-        else 0.0
+        mod.id: (
+            1.0
+            if (mod.module_type == ModuleType.STANDARD) and (mod.ue.type == UE_SPORT)
+            else 0.0
+        )
         for mod in modules
     }
 
@@ -148,10 +150,12 @@ def df_load_modimpl_coefs(
     # 0 pour modules normaux, 1. pour bonus (car par défaut, on veut qu'un bonus agisse
     # sur toutes les UE)
     default_poids = {
-        modimpl.id: 1.0
-        if (modimpl.module.module_type == ModuleType.STANDARD)
-        and (modimpl.module.ue.type == UE_SPORT)
-        else 0.0
+        modimpl.id: (
+            1.0
+            if (modimpl.module.module_type == ModuleType.STANDARD)
+            and (modimpl.module.ue.type == UE_SPORT)
+            else 0.0
+        )
         for modimpl in formsemestre.modimpls_sorted
     }
 
@@ -200,8 +204,9 @@ def notes_sem_load_cube(formsemestre: FormSemestre) -> tuple:
     modimpls_results = {}
     modimpls_evals_poids = {}
     modimpls_notes = []
+    etudids, etudids_actifs = formsemestre.etudids_actifs()
     for modimpl in formsemestre.modimpls_sorted:
-        mod_results = moy_mod.ModuleImplResultsAPC(modimpl)
+        mod_results = moy_mod.ModuleImplResultsAPC(modimpl, etudids, etudids_actifs)
         evals_poids, _ = moy_mod.load_evaluations_poids(modimpl.id)
         etuds_moy_module = mod_results.compute_module_moy(evals_poids)
         modimpls_results[modimpl.id] = mod_results
diff --git a/app/comp/res_classic.py b/app/comp/res_classic.py
index 673668b99..113faa0b2 100644
--- a/app/comp/res_classic.py
+++ b/app/comp/res_classic.py
@@ -256,8 +256,9 @@ def notes_sem_load_matrix(formsemestre: FormSemestre) -> tuple[np.ndarray, dict]
     """
     modimpls_results = {}
     modimpls_notes = []
+    etudids, etudids_actifs = formsemestre.etudids_actifs()
     for modimpl in formsemestre.modimpls_sorted:
-        mod_results = moy_mod.ModuleImplResultsClassic(modimpl)
+        mod_results = moy_mod.ModuleImplResultsClassic(modimpl, etudids, etudids_actifs)
         etuds_moy_module = mod_results.compute_module_moy()
         modimpls_results[modimpl.id] = mod_results
         modimpls_notes.append(etuds_moy_module)
diff --git a/app/models/formsemestre.py b/app/models/formsemestre.py
index 84ba4c373..98ecc093f 100644
--- a/app/models/formsemestre.py
+++ b/app/models/formsemestre.py
@@ -936,10 +936,14 @@ class FormSemestre(models.ScoDocModel):
             partitions += [p for p in self.partitions if p.partition_name is None]
         return partitions
 
-    @cached_property
-    def etudids_actifs(self) -> set:
-        "Set des etudids inscrits non démissionnaires et non défaillants"
-        return {ins.etudid for ins in self.inscriptions if ins.etat == scu.INSCRIT}
+    def etudids_actifs(self) -> tuple[list[int], set[int]]:
+        """Liste les etudids inscrits (incluant DEM et DEF),
+        qui ser al'index des dataframes de notes
+        et donne l'ensemble des inscrits non DEM ni DEF.
+        """
+        return [inscr.etudid for inscr in self.inscriptions], {
+            ins.etudid for ins in self.inscriptions if ins.etat == scu.INSCRIT
+        }
 
     @cached_property
     def etuds_inscriptions(self) -> dict:
diff --git a/app/scodoc/sco_liste_notes.py b/app/scodoc/sco_liste_notes.py
index 9e668c902..f2ca4f571 100644
--- a/app/scodoc/sco_liste_notes.py
+++ b/app/scodoc/sco_liste_notes.py
@@ -705,7 +705,8 @@ def _add_eval_columns(
     nb_att = 0
     sum_notes = 0
     notes = []  # liste des notes numeriques, pour calcul histogramme uniquement
-    inscrits = evaluation.moduleimpl.formsemestre.etudids_actifs  # set d'etudids
+    # actifs == inscrit au semestre, non DEM ni DEF:
+    _, etudids_actifs = evaluation.moduleimpl.formsemestre.etudids_actifs()
     notes_db = sco_evaluation_db.do_evaluation_get_all_notes(evaluation.id)
 
     if evaluation.date_debut:
@@ -734,7 +735,7 @@ def _add_eval_columns(
                 nb_att += 1
             # calcul moyenne SANS LES ABSENTS ni les DEMISSIONNAIRES
             if (
-                (etudid in inscrits)
+                (etudid in etudids_actifs)
                 and val is not None
                 and val != scu.NOTES_NEUTRALISE
                 and val != scu.NOTES_ATTENTE
@@ -758,7 +759,7 @@ def _add_eval_columns(
                 comment,
             )
         else:
-            if (etudid in inscrits) and evaluation.publish_incomplete:
+            if (etudid in etudids_actifs) and evaluation.publish_incomplete:
                 # Note manquante mais prise en compte immédiate: affiche ATT
                 val = scu.NOTES_ATTENTE
                 val_fmt = "ATT"
@@ -875,8 +876,7 @@ def _add_moymod_column(
     col_id = "moymod"
     formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
     nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
-    inscrits = formsemestre.etudids_actifs
-
+    _, etudids_actifs = formsemestre.etudids_actifs()
     nb_notes = 0
     sum_notes = 0
     notes = []  # liste des notes numeriques, pour calcul histogramme uniquement
@@ -885,7 +885,7 @@ def _add_moymod_column(
         val = nt.get_etud_mod_moy(moduleimpl_id, etudid)  # note sur 20, ou 'NA','NI'
         row[col_id] = scu.fmt_note(val, keep_numeric=keep_numeric)
         row["_" + col_id + "_td_attrs"] = ' class="moyenne" '
-        if etudid in inscrits and not isinstance(val, str):
+        if etudid in etudids_actifs and not isinstance(val, str):
             notes.append(val)
             if not np.isnan(val):
                 nb_notes = nb_notes + 1
@@ -928,7 +928,7 @@ def _add_apc_columns(
     # on va y ajouter une clé par UE du semestre
     nt: ResultatsSemestreBUT = res_sem.load_formsemestre_results(modimpl.formsemestre)
     modimpl_results: ModuleImplResults = nt.modimpls_results[modimpl.id]
-    inscrits = modimpl.formsemestre.etudids_actifs
+    _, etudids_actifs = modimpl.formsemestre.etudids_actifs()
     # les UE dans lesquelles ce module a un coef non nul:
     ues_with_coef = nt.modimpl_coefs_df[modimpl.id][
         nt.modimpl_coefs_df[modimpl.id] > 0
@@ -946,7 +946,7 @@ def _add_apc_columns(
                 if (
                     isinstance(moy_ue, float)
                     and not np.isnan(moy_ue)
-                    and row["etudid"] in inscrits
+                    and row["etudid"] in etudids_actifs
                 ):
                     sum_by_ue[ue.id] += moy_ue
                     nb_notes_by_ue[ue.id] += 1
diff --git a/app/scodoc/sco_saisie_notes.py b/app/scodoc/sco_saisie_notes.py
index d298570a6..fd5b51bfd 100644
--- a/app/scodoc/sco_saisie_notes.py
+++ b/app/scodoc/sco_saisie_notes.py
@@ -541,7 +541,7 @@ def notes_add(
         )
     }
     # Les étudiants inscrits au semestre ni DEM ni DEF
-    etudids_actifs = evaluation.moduleimpl.formsemestre.etudids_actifs
+    _, etudids_actifs = evaluation.moduleimpl.formsemestre.etudids_actifs()
     for etudid, value in notes:
         if check_inscription and (
             (etudid not in inscrits) or (etudid not in etudids_actifs)
diff --git a/tests/api/test_api_etudiants.py b/tests/api/test_api_etudiants.py
index 4ee134b40..29df9e96a 100644
--- a/tests/api/test_api_etudiants.py
+++ b/tests/api/test_api_etudiants.py
@@ -931,6 +931,9 @@ def test_etudiant_bulletin_semestre(api_headers):
     # /ScoDoc/api/etudiant/nip/12345/formsemestre/123/bulletin/long/pdf/nosi
     # TODO voir forme utilisée par ScoDoc en interne:
     # formsemestre_bulletinetud?formsemestre_id=1263&etudid=16387
+    formsemestre = POST_JSON(
+        f"/formsemestre/{1}/edit", {"bul_hide_xml": False}, headers=admin_header
+    )
 
 
 def test_etudiant_groups(api_headers):
diff --git a/tests/unit/test_but_modules.py b/tests/unit/test_but_modules.py
index fd1bc4306..e12e6009f 100644
--- a/tests/unit/test_but_modules.py
+++ b/tests/unit/test_but_modules.py
@@ -2,6 +2,7 @@
 Test modèles évaluations avec poids BUT
 et calcul moyennes modules
 """
+
 import datetime
 import numpy as np
 import pandas as pd
@@ -215,7 +216,8 @@ def test_module_moy(test_client):
     etud = G.create_etud(nom="test")
     G.inscrit_etudiant(formsemestre_id, etud)
     etudid = etud["etudid"]
-    evaluation1 = db.session.get(Evaluation, evaluation1_ids[0])
+    evaluation1: Evaluation = db.session.get(Evaluation, evaluation1_ids[0])
+    formsemestre = evaluation1.moduleimpl.formsemestre
     # Crée une deuxième évaluation dans le même moduleimpl:
     evaluation2_id = G.create_evaluation(
         moduleimpl_id=evaluation1.moduleimpl_id,
@@ -245,10 +247,10 @@ def test_module_moy(test_client):
         _ = sco_saisie_notes.notes_add(G.default_user, evaluation1.id, [(etudid, n1)])
         _ = sco_saisie_notes.notes_add(G.default_user, evaluation2.id, [(etudid, n2)])
         # Calcul de la moyenne du module
-        evals_poids, ues = moy_mod.load_evaluations_poids(moduleimpl_id)
+        evals_poids, _ = moy_mod.load_evaluations_poids(moduleimpl_id)
         assert evals_poids.shape == (nb_evals, nb_ues)
-
-        mod_results = moy_mod.ModuleImplResultsAPC(modimpl)
+        etudids, etudids_actifs = formsemestre.etudids_actifs()
+        mod_results = moy_mod.ModuleImplResultsAPC(modimpl, etudids, etudids_actifs)
         evals_notes = mod_results.evals_notes
         assert evals_notes[evaluation1.id].dtype == np.float64
 
-- 
GitLab