From ed0925419e400b760d1f09dbf1e571fc29eccf79 Mon Sep 17 00:00:00 2001
From: ilona <ilona@scodoc.org>
Date: Thu, 24 Oct 2024 12:11:10 +0200
Subject: [PATCH] Modernisation code ModuleImpl.

---
 app/formations/edit_ue.py                   |   2 +-
 app/scodoc/sco_formsemestre_edit.py         |  25 +++--
 app/scodoc/sco_formsemestre_inscriptions.py |  23 +++--
 app/scodoc/sco_groups_view.py               |   9 +-
 app/scodoc/sco_moduleimpl.py                | 107 --------------------
 app/views/notes.py                          |   7 +-
 tests/unit/test_formations.py               |  35 +++----
 7 files changed, 48 insertions(+), 160 deletions(-)

diff --git a/app/formations/edit_ue.py b/app/formations/edit_ue.py
index 483d9e1f..c1d82361 100644
--- a/app/formations/edit_ue.py
+++ b/app/formations/edit_ue.py
@@ -1121,7 +1121,7 @@ def _ue_table_ues(
             # Cas spécial: si l'UE externe a plus d'un module, c'est peut être une UE
             # qui a été déclarée externe par erreur (ou suite à un bug d'import/export xml)
             # Dans ce cas, propose de changer le type (même si verrouillée)
-            if len(sco_moduleimpl.moduleimpls_in_external_ue(ue.id)) > 1:
+            if ue.modules.count() > 1:
                 H.append('<span class="ue_is_external">')
                 if has_perm_change:
                     H.append(
diff --git a/app/scodoc/sco_formsemestre_edit.py b/app/scodoc/sco_formsemestre_edit.py
index 0060b9b0..48db1a13 100644
--- a/app/scodoc/sco_formsemestre_edit.py
+++ b/app/scodoc/sco_formsemestre_edit.py
@@ -959,19 +959,15 @@ def do_formsemestre_createwithmodules(edit=False, formsemestre: FormSemestre = N
                         modimpl.id}: aucun etudiant inscrit"""
                 )
         #
-        ok, diag = formsemestre_delete_moduleimpls(formsemestre.id, module_ids_todelete)
+        ok, diag = formsemestre_delete_moduleimpls(formsemestre, module_ids_todelete)
         msg += diag
         for module_id in module_ids_toedit:
-            moduleimpl_id = sco_moduleimpl.moduleimpl_list(
-                formsemestre_id=formsemestre.id, module_id=module_id
-            )[0]["moduleimpl_id"]
-            modargs = {
-                "moduleimpl_id": moduleimpl_id,
-                "module_id": module_id,
-                "formsemestre_id": formsemestre.id,
-                "responsable_id": tf[2]["MI" + str(module_id)],
-            }
-            sco_moduleimpl.do_moduleimpl_edit(modargs, formsemestre_id=formsemestre.id)
+            modimpl = formsemestre.modimpls.filter_by(module_id=module_id).first()
+            if not modimpl:
+                raise ScoValueError("Module introuvable ! Veuillez recharger la page.")
+            modimpl.responsable_id = tf[2]["MI" + str(module_id)]
+            db.session.add(modimpl)
+        db.session.commit()
     # --- Etapes
     _set_apo_etapes(formsemestre, tf[2], etapes)
     # --- id edt du groupe par défault
@@ -983,6 +979,7 @@ def do_formsemestre_createwithmodules(edit=False, formsemestre: FormSemestre = N
 
     db.session.add(formsemestre)
     db.session.commit()
+    sco_cache.invalidate_formsemestre(formsemestre_id=formsemestre.id)
 
     # --- Crée ou met à jour les groupes de parcours BUT
     formsemestre.setup_parcours_groups()
@@ -1109,7 +1106,9 @@ def _formsemestre_check_ue_bonus_unicity(module_ids):
         )
 
 
-def formsemestre_delete_moduleimpls(formsemestre_id, module_ids_to_del):
+def formsemestre_delete_moduleimpls(
+    formsemestre: FormSemestre, module_ids_to_del: list[int]
+) -> tuple[bool, list[str]]:
     """Delete moduleimpls
     module_ids_to_del: list of module_id (warning: not moduleimpl)
     Moduleimpls must have no associated evaluations.
@@ -1121,7 +1120,7 @@ def formsemestre_delete_moduleimpls(formsemestre_id, module_ids_to_del):
         if module is None:
             continue  # ignore invalid ids
         modimpls = ModuleImpl.query.filter_by(
-            formsemestre_id=formsemestre_id, module_id=module_id
+            formsemestre_id=formsemestre.id, module_id=module_id
         )
         for modimpl in modimpls:
             nb_evals = modimpl.evaluations.count()
diff --git a/app/scodoc/sco_formsemestre_inscriptions.py b/app/scodoc/sco_formsemestre_inscriptions.py
index e8ce67ce..33ffb4bd 100644
--- a/app/scodoc/sco_formsemestre_inscriptions.py
+++ b/app/scodoc/sco_formsemestre_inscriptions.py
@@ -35,7 +35,13 @@ from flask import flash, url_for, g, render_template, request
 from app import db
 from app.comp import res_sem
 from app.comp.res_compat import NotesTableCompat
-from app.models import Formation, FormSemestre, FormSemestreInscription, Scolog
+from app.models import (
+    Formation,
+    FormSemestre,
+    FormSemestreInscription,
+    ModuleImpl,
+    Scolog,
+)
 from app.models.etudiants import Identite
 from app.models.groups import Partition, GroupDescr
 from app.models.scolar_event import ScolarEvent
@@ -729,23 +735,22 @@ def do_moduleimpl_incription_options(
     # inscriptions
     for moduleimpl_id in a_inscrire:
         # verifie que ce module existe bien
-        mods = sco_moduleimpl.moduleimpl_list(moduleimpl_id=moduleimpl_id)
-        if len(mods) != 1:
+        modimpl = db.session.query(ModuleImpl).get(moduleimpl_id)
+        if modimpl is None:
             raise ScoValueError(f"inscription: invalid moduleimpl_id: {moduleimpl_id}")
-        mod = mods[0]
+
         sco_moduleimpl.do_moduleimpl_inscription_create(
             {"moduleimpl_id": moduleimpl_id, "etudid": etudid},
-            formsemestre_id=mod["formsemestre_id"],
+            formsemestre_id=modimpl.formsemestre_id,
         )
     # desinscriptions
     for moduleimpl_id in a_desinscrire:
         # verifie que ce module existe bien
-        mods = sco_moduleimpl.moduleimpl_list(moduleimpl_id=moduleimpl_id)
-        if len(mods) != 1:
+        modimpl = db.session.query(ModuleImpl).get(moduleimpl_id)
+        if modimpl is None:
             raise ScoValueError(
                 f"desinscription: invalid moduleimpl_id: {moduleimpl_id}"
             )
-        mod = mods[0]
         inscr = sco_moduleimpl.do_moduleimpl_inscription_list(
             moduleimpl_id=moduleimpl_id, etudid=etudid
         )
@@ -755,7 +760,7 @@ def do_moduleimpl_incription_options(
             )
         oid = inscr[0]["moduleimpl_inscription_id"]
         sco_moduleimpl.do_moduleimpl_inscription_delete(
-            oid, formsemestre_id=mod["formsemestre_id"]
+            oid, formsemestre_id=modimpl.formsemestre_id
         )
 
     H = [
diff --git a/app/scodoc/sco_groups_view.py b/app/scodoc/sco_groups_view.py
index 75f65e6e..f38c4cf9 100644
--- a/app/scodoc/sco_groups_view.py
+++ b/app/scodoc/sco_groups_view.py
@@ -40,13 +40,12 @@ from flask import url_for, g, render_template, request
 from flask_login import current_user
 
 from app import db, log
-from app.models import FormSemestre, Identite, ScolarEvent
+from app.models import FormSemestre, Identite, ModuleImpl, ScolarEvent
 import app.scodoc.sco_utils as scu
 from app.scodoc import sco_assiduites as scass
 from app.scodoc import sco_excel
 from app.scodoc import sco_formsemestre
 from app.scodoc import sco_groups
-from app.scodoc import sco_moduleimpl
 from app.scodoc import sco_cursus
 from app.scodoc import sco_portal_apogee
 from app.scodoc import sco_preferences
@@ -364,10 +363,10 @@ class DisplayedGroupsInfos:
                     "identifiant de groupe invalide (mettre à jour vos bookmarks ?)"
                 ) from exc
         if not formsemestre_id and moduleimpl_id:
-            mods = sco_moduleimpl.moduleimpl_list(moduleimpl_id=moduleimpl_id)
-            if len(mods) != 1:
+            modimpl = db.session.get(ModuleImpl, moduleimpl_id)
+            if modimpl is None:
                 raise ValueError("invalid moduleimpl_id")
-            formsemestre_id = mods[0]["formsemestre_id"]
+            formsemestre_id = modimpl.formsemestre_id
 
         if not group_ids:  # appel sans groupe (eg page accueil)
             if not formsemestre_id:
diff --git a/app/scodoc/sco_moduleimpl.py b/app/scodoc/sco_moduleimpl.py
index 6891f8d6..0aeaf898 100644
--- a/app/scodoc/sco_moduleimpl.py
+++ b/app/scodoc/sco_moduleimpl.py
@@ -35,81 +35,6 @@ from app.scodoc import sco_cache
 import app.scodoc.notesdb as ndb
 from app.scodoc.sco_exceptions import ScoValueError
 
-# --- Gestion des "Implémentations de Modules"
-# Un "moduleimpl" correspond a la mise en oeuvre d'un module
-# dans une formation spécifique, à une date spécifique.
-_moduleimplEditor = ndb.EditableTable(
-    "notes_moduleimpl",
-    "moduleimpl_id",
-    (
-        "moduleimpl_id",
-        "module_id",
-        "formsemestre_id",
-        "responsable_id",
-        "computation_expr",
-    ),
-)
-
-
-def do_moduleimpl_delete(oid, formsemestre_id=None):
-    "delete moduleimpl (desinscrit tous les etudiants)"
-    cnx = ndb.GetDBConnexion()
-    # --- desinscription des etudiants
-    cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
-    req = (
-        "DELETE FROM notes_moduleimpl_inscription WHERE moduleimpl_id=%(moduleimpl_id)s"
-    )
-    cursor.execute(req, {"moduleimpl_id": oid})
-    # --- suppression des enseignants
-    cursor.execute(
-        "DELETE FROM notes_modules_enseignants WHERE moduleimpl_id=%(moduleimpl_id)s",
-        {"moduleimpl_id": oid},
-    )
-    # --- suppression des references dans les absences
-    cursor.execute(
-        "UPDATE absences SET moduleimpl_id=NULL WHERE moduleimpl_id=%(moduleimpl_id)s",
-        {"moduleimpl_id": oid},
-    )
-    # --- destruction du moduleimpl
-    _moduleimplEditor.delete(cnx, oid)
-    sco_cache.invalidate_formsemestre(
-        formsemestre_id=formsemestre_id
-    )  # > moduleimpl_delete
-
-
-def moduleimpl_list(
-    moduleimpl_id=None, formsemestre_id=None, module_id=None
-) -> list[dict]:
-    "list moduleimpls"
-    args = locals()
-    cnx = ndb.GetDBConnexion()
-    modimpls = _moduleimplEditor.list(cnx, args)
-    return modimpls
-
-
-def do_moduleimpl_edit(args, formsemestre_id=None, cnx=None):
-    "edit a moduleimpl"
-    if not cnx:
-        cnx = ndb.GetDBConnexion()
-    _moduleimplEditor.edit(cnx, args)
-
-    sco_cache.invalidate_formsemestre(
-        formsemestre_id=formsemestre_id
-    )  # > modif moduleimpl
-
-
-def moduleimpls_in_external_ue(ue_id):
-    """List of modimpls in this ue"""
-    cursor = ndb.SimpleQuery(
-        """SELECT DISTINCT mi.*
-        FROM notes_ue u, notes_moduleimpl mi, notes_modules m
-        WHERE u.is_external is true
-        AND  mi.module_id = m.id AND m.ue_id = %(ue_id)s
-        """,
-        {"ue_id": ue_id},
-    )
-    return cursor.dictfetchall()
-
 
 def do_moduleimpl_inscription_list(moduleimpl_id=None, etudid=None):
     "list moduleimpl_inscriptions"
@@ -118,38 +43,6 @@ def do_moduleimpl_inscription_list(moduleimpl_id=None, etudid=None):
     return _moduleimpl_inscriptionEditor.list(cnx, args)
 
 
-def moduleimpl_listeetuds(moduleimpl_id):  # XXX OBSOLETE
-    "retourne liste des etudids inscrits a ce module"
-    req = """SELECT DISTINCT Im.etudid
-    FROM notes_moduleimpl_inscription Im,
-    notes_formsemestre_inscription Isem,
-    notes_moduleimpl M
-    WHERE Isem.etudid = Im.etudid
-    and Im.moduleimpl_id = M.id
-    and M.id = %(moduleimpl_id)s
-    """
-    cnx = ndb.GetDBConnexion()
-    cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
-    cursor.execute(req, {"moduleimpl_id": moduleimpl_id})
-    res = cursor.fetchall()
-    return [x[0] for x in res]
-
-
-def do_moduleimpl_inscrit_tout_semestre(moduleimpl_id, formsemestre_id):
-    "inscrit tous les etudiants inscrit au semestre a ce module"
-    # UNUSED
-    cnx = ndb.GetDBConnexion()
-    cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
-    req = """INSERT INTO notes_moduleimpl_inscription
-                            (moduleimpl_id, etudid)
-                SELECT %(moduleimpl_id)s, I.etudid
-                FROM  notes_formsemestre_inscription I
-                WHERE I.formsemestre_id=%(formsemestre_id)s
-    """
-    args = {"moduleimpl_id": moduleimpl_id, "formsemestre_id": formsemestre_id}
-    cursor.execute(req, args)
-
-
 # --- Inscriptions aux modules
 _moduleimpl_inscriptionEditor = ndb.EditableTable(
     "notes_moduleimpl_inscription",
diff --git a/app/views/notes.py b/app/views/notes.py
index 03a4c531..a01c3dde 100644
--- a/app/views/notes.py
+++ b/app/views/notes.py
@@ -2588,23 +2588,20 @@ def check_sem_integrity(formsemestre_id, fix=False):
     Check that ue and module formations are consistents
     """
     formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
-    modimpls = sco_moduleimpl.moduleimpl_list(formsemestre_id=formsemestre_id)
+    modimpls = formsemestre.modimpls.all()
     bad_ue = []
     bad_sem = []
     formations_set = set()  # les formations mentionnées dans les UE et modules
     for modimpl in modimpls:
-        mod = Module.get_instance(modimpl["module_id"])
+        mod = modimpl.module
         formations_set.add(mod.formation_id)
         ue = mod.ue
         ue_dict = ue.to_dict()
         formations_set.add(ue_dict["formation_id"])
         if ue_dict["formation_id"] != mod.formation_id:
-            modimpl["mod"] = mod.to_dict()
-            modimpl["ue"] = ue_dict
             bad_ue.append(modimpl)
         if formsemestre.formation_id != mod.formation_id:
             bad_sem.append(modimpl)
-            modimpl["mod"] = mod.to_dict()
 
     H = [
         f"""<p>formation_id={formsemestre.formation_id}""",
diff --git a/tests/unit/test_formations.py b/tests/unit/test_formations.py
index d047a070..5e193567 100644
--- a/tests/unit/test_formations.py
+++ b/tests/unit/test_formations.py
@@ -31,7 +31,6 @@
 # - create_moduleimpl
 # - formation_export
 # - formsemestre_list
-# - moduleimpl_list
 # - do_module_impl_with_module_list
 # - do_formsemestre_delete
 # - Module.delete
@@ -231,25 +230,18 @@ def test_formations(test_client):
 
     # --- Liste des modules
 
-    lim_sem1 = sco_moduleimpl.moduleimpl_list(formsemestre_id=sem1["formsemestre_id"])
-
-    assert len(lim_sem1) == 2
-    assert module_id in (
-        lim_sem1[0]["module_id"],
-        lim_sem1[1]["module_id"],
+    lim_sem1 = (
+        db.session.query(ModuleImpl)
+        .filter_by(formsemestre_id=sem1["formsemestre_id"])
+        .all()
     )
-    assert module_id2 in (
-        lim_sem1[0]["module_id"],
-        lim_sem1[1]["module_id"],
-    )
-
-    lim_modid = sco_moduleimpl.moduleimpl_list(module_id=module_id)
+    assert len(lim_sem1) == 2
+    assert module_id in (lim_sem1[0].module_id, lim_sem1[1].module_id)
+    assert module_id2 in (lim_sem1[0].module_id, lim_sem1[1].module_id)
 
+    lim_modid = db.session.query(ModuleImpl).filter_by(module_id=module_id).all()
     assert len(lim_modid) == 1
 
-    lim_modimpl_id = sco_moduleimpl.moduleimpl_list(moduleimpl_id=moduleimpl_id)
-    assert lim_modid == lim_modimpl_id
-
     # --- Suppression du module, matiere et ue test du semestre 2
 
     # on doit d'abord supprimer le semestre:
@@ -280,8 +272,11 @@ def test_formations(test_client):
         len(li_module2_after) == len(li_module2_before) - 2
     )  # verification de la suppression
 
-    lim_sem2 = sco_moduleimpl.moduleimpl_list(formsemestre_id=sem2["formsemestre_id"])
-
+    lim_sem2 = (
+        db.session.query(ModuleImpl)
+        .filter_by(formsemestre_id=sem2["formsemestre_id"])
+        .all()
+    )
     assert len(lim_sem2) == 0  # deuxieme vérification si le module s'est bien sup
 
     li_mat = Matiere.query.all()
@@ -344,8 +339,8 @@ def test_import_formation(test_client, filename="formation-exemple-1.xml"):
             module_id=mod.id,
             formsemestre_id=formsemestre_ids[mod.semestre_id - 1],
         )
-        mi = sco_moduleimpl.moduleimpl_list(moduleimpl_id=moduleimpl_id)[0]
-        assert mi["module_id"] == mod.id
+        mi = db.session.query(ModuleImpl).get(moduleimpl_id)
+        assert mi.module_id == mod.id
 
     # --- Export formation en XML
     doc1 = formation_io.formation_export(formation_id, fmt="xml").get_data(as_text=True)
-- 
GitLab