From fdc01a5c3b9377e17facfe610a30710d1cd65f2d Mon Sep 17 00:00:00 2001
From: Emmanuel Viennet <emmanuel.viennet@gmail.com>
Date: Sun, 20 Oct 2024 11:02:08 +0200
Subject: [PATCH] =?UTF-8?q?sco=5Fplacement:=20feuilles=20placement=20?=
 =?UTF-8?q?=C3=A9tudiants=20=C3=A9valuation:=20modernisation=20code.?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 app/forms/multiselect.py                |   8 +-
 app/scodoc/sco_placement.py             | 129 ++++++++++--------------
 app/templates/scodoc/forms/placement.j2 |   4 +
 sco_version.py                          |   2 +-
 4 files changed, 63 insertions(+), 80 deletions(-)

diff --git a/app/forms/multiselect.py b/app/forms/multiselect.py
index 77b13626..820718f9 100644
--- a/app/forms/multiselect.py
+++ b/app/forms/multiselect.py
@@ -26,7 +26,8 @@ class MultiSelect:
             HTML : group_ids="val1"&group_ids="val2"...
             JS : ["val1","val2", ...]
 
-        **kwargs: Arguments supplémentaires (appliqué au multiselect en HTML <multi-select key="value" ...>)
+        **kwargs: Arguments supplémentaires (appliqués au multiselect en
+            HTML <multi-select key="value" ...>)
     """
 
     def __init__(
@@ -61,7 +62,8 @@ class MultiSelect:
             for value in values:
                 selected = "selected" if value.get("selected", False) else ""
                 single = "single" if value.get("single", False) else ""
-                opt = f"<option value='{value.get('value')}' {selected} {single} >{value.get('label')}</option>"
+                opt = f"""<option value='{value.get('value')}' {selected} {single} >{
+                    value.get('label')}</option>"""
                 optgroup += opt
             optgroup += "</optgroup>"
             opts.append(optgroup)
@@ -70,7 +72,7 @@ class MultiSelect:
         js: str = "{" + self.js + "}"
         export: str = "{" + self.export + "}"
         return f"""
-        <multi-select 
+        <multi-select
             label="{self.label}"
             id="{self.html_id}"
             name="{self.name}"
diff --git a/app/scodoc/sco_placement.py b/app/scodoc/sco_placement.py
index 23eca4ae..3a4ac157 100644
--- a/app/scodoc/sco_placement.py
+++ b/app/scodoc/sco_placement.py
@@ -48,23 +48,16 @@ from wtforms import (
     HiddenField,
     SelectMultipleField,
 )
-from app.models import Evaluation, Module, ModuleImpl
+from app.models import Evaluation, Identite, ModuleImpl
 import app.scodoc.sco_utils as scu
-import app.scodoc.notesdb as ndb
 from app.scodoc import sco_preferences
 from app.scodoc import sco_evaluations
 from app.scodoc import sco_excel
 from app.scodoc.sco_excel import ScoExcelBook, COLORS
-from app.scodoc import sco_formsemestre
 from app.scodoc import sco_groups
-from app.scodoc import sco_moduleimpl
 from app.scodoc.gen_tables import GenTable
-from app.scodoc import sco_etud
 import sco_version
 
-_ = lambda x: x  # sans babel
-_l = _
-
 COORD = "Coordonnées"
 SEQ = "Continue"
 
@@ -83,11 +76,9 @@ def _get_group_info(evaluation_id):
         if partition not in groups_tree:
             groups_tree[partition] = {}
         groups_tree[partition][group_name] = group_id
-        if partition != TOUS:
-            has_groups = True
-        else:
-            has_groups = False
-    nb_groups = sum([len(groups_tree[p]) for p in groups_tree])
+        has_groups = partition != TOUS
+
+    nb_groups = sum(len(pg) for pg in groups_tree.values())
     return groups_tree, has_groups, nb_groups
 
 
@@ -102,9 +93,9 @@ class PlacementForm(FlaskForm):
             wtforms.validators.DataRequired("indiquez le format du fichier attendu"),
         ],
     )
-    surveillants = StringField("Surveillants", validators=[])
-    batiment = StringField("Batiment")
-    salle = StringField("Salle")
+    surveillants = StringField("Surveillants", validators=[], description="texte libre")
+    batiment = StringField("Bâtiment", description="texte libre")
+    salle = StringField("Salle", description="texte libre")
     nb_rangs = SelectField(
         "nb de places en largeur",
         coerce=int,
@@ -139,19 +130,21 @@ class PlacementForm(FlaskForm):
             evaluation_id
         )
         choices = []
-        for partition in self.groups_tree:
-            for groupe in self.groups_tree[partition]:
+        for partition, groupes in self.groups_tree.items():
+            for groupe in groupes:
                 if (
                     groupe == TOUS
                 ):  #  Affichage et valeur spécifique pour le groupe TOUS
-                    self.tous_id = str(self.groups_tree[partition][groupe])
+                    self.tous_id = str(groupes[groupe])
                     choices.append((TOUS, TOUS))
                 else:
                     groupe_id = str(self.groups_tree[partition][groupe])
-                    choices.append((groupe_id, "%s (%s)" % (str(groupe), partition)))
+                    choices.append((groupe_id, f"{str(groupe)} ({partition})"))
         self.groups.choices = choices
-        # self.groups.default = [TOUS]  # Ne fonctionnne pas... (ni dans la déclaration de PlaceForm.groups)
-        # la réponse [] est de toute façon transposée en [ self.tous_id ] lors du traitement (cas du groupe unique)
+        # self.groups.default = [TOUS]  # Ne fonctionnne pas...
+        # (ni dans la déclaration de PlaceForm.groups)
+        # la réponse [] est de toute façon transposée en [ self.tous_id ]
+        # lors du traitement (cas du groupe unique)
 
 
 class _DistributeurContinu:
@@ -227,48 +220,34 @@ class PlacementRunner:
         self.file_format = form["file_format"].data
         if len(form["groups"].data) == 0:
             self.groups_ids = [form.tous_id]
-        else:  # On remplace le mot-clé TOUS le l'identiant de ce groupe
+        else:  # On remplace le mot-clé TOUS par l'identifiant de ce groupe
             self.groups_ids = [
                 gid if gid != TOUS else form.tous_id for gid in form["groups"].data
             ]
-        self.evaluation = Evaluation.get_evaluation(self.evaluation_id)
+        evl = Evaluation.get_evaluation(self.evaluation_id)
+        self.evaluation = evl
         self.groups = sco_groups.listgroups(self.groups_ids)
         self.gr_title_filename = sco_groups.listgroups_filename(self.groups)
-        # gr_title = sco_groups.listgroups_abbrev(d['groups'])
         self.current_user = current_user
-        self.moduleimpl_id = self.evaluation.moduleimpl_id
-        self.moduleimpl: ModuleImpl = ModuleImpl.query.get_or_404(self.moduleimpl_id)
-        # TODO: à revoir pour utiliser modèle ModuleImpl
-        self.moduleimpl_data = sco_moduleimpl.moduleimpl_list(
-            moduleimpl_id=self.moduleimpl_id
-        )[0]
-        self.module_data = Module.get_module(
-            self.moduleimpl_data["module_id"]
-        ).to_dict()
-        self.sem = sco_formsemestre.get_formsemestre(
-            self.moduleimpl_data["formsemestre_id"]
-        )
-        self.evalname = "%s-%s" % (
-            self.module_data["code"] or "?",
-            (
-                self.evaluation.date_debut.strftime("%Y-%m-%d_%Hh%M")
-                if self.evaluation.date_debut
-                else ""
-            ),
-        )
-        if self.evaluation.description:
+        self.moduleimpl_id = evl.moduleimpl_id
+        self.moduleimpl: ModuleImpl = evl.moduleimpl
+        self.moduleimpl_data = self.moduleimpl.to_dict()
+        mod = evl.moduleimpl.module
+        self.module_data = mod.to_dict()
+        self.evalname = f"""{mod.code or "?"}-{
+            evl.date_debut.strftime("%Y-%m-%d_%Hh%M") if evl.date_debut else ""}"""
+        if evl.description:
             self.evaltitre = self.evaluation.description
         else:
             self.evaltitre = f"""évaluation{
-                self.evaluation.date_debut.strftime(' du %d/%m/%Y à %Hh%M')
-                if self.evaluation.date_debut else ''}"""
+                evl.date_debut.strftime(' du ' + scu.DATEATIME_FMT)
+                if evl.date_debut else ''}"""
         self.desceval = [  # une liste de chaines: description de l'evaluation
-            self.sem["titreannee"],
-            "Module : %s - %s"
-            % (self.module_data["code"] or "?", self.module_data["abbrev"] or ""),
-            "Surveillants : %s" % self.surveillants,
-            "Batiment : %(batiment)s - Salle : %(salle)s" % self.__dict__,
-            "Controle : %s (coef. %g)" % (self.evaltitre, self.evaluation.coefficient),
+            evl.moduleimpl.formsemestre.titre_annee(),
+            f"""Module : {mod.code or "?"} - {evl.moduleimpl.module.abbrev or ""}""",
+            f"Surveillants : {self.surveillants}",
+            f"Bâtiment : {self.batiment} - Salle : {self.salle}",
+            f"Évaluation : {self.evaltitre} (coef. {evl.coefficient:g})",
         ]
         self.styles = None
         self.plan = None
@@ -295,26 +274,26 @@ class PlacementRunner:
         self.listetud = self._build_listetud()
         self.plan = self._affectation_places()
 
-    def _build_listetud(self):
-        get_all_students = None in [
-            g["group_name"] for g in self.groups
-        ]  # tous les etudiants
+    def _build_listetud(self) -> list[tuple[str, str, int]]:
+        # tous les etudiants ?
+        get_all_students = None in [g["group_name"] for g in self.groups]
         etudid_etats = sco_groups.do_evaluation_listeetuds_groups(
             self.evaluation_id,
             self.groups,
             getallstudents=get_all_students,
             include_demdef=True,
         )
-        listetud = []  # liste de couples (nom,prenom)
+        listetud = []  # liste de tuples (nom, prenom, etudid)
         for etudid, etat in etudid_etats:
-            # infos identite etudiant (xxx sous-optimal: 1/select par etudiant)
-            ident = sco_etud.etudident_list(ndb.GetDBConnexion(), {"etudid": etudid})[0]
-            if etat != "D":
-                nom = ident["nom"].upper()
-                prenom = ident["prenom"].lower().capitalize()
-                etudid = ident["etudid"]
-                listetud.append((nom, prenom, etudid))
+            if etat != scu.DEMISSION:
+                # infos identite etudiant
+                etud = Identite.get_etud(etudid)
+                nom = etud.nom.upper()
+                prenom = etud.prenom.lower().capitalize()
+                listetud.append((nom, prenom, etud.id))
+
         random.shuffle(listetud)
+
         return listetud
 
     def _affectation_places(self):
@@ -328,7 +307,7 @@ class PlacementRunner:
         return plan
 
     def _production_xls(self):
-        filename = "placement_%s_%s" % (self.evalname, self.gr_title_filename)
+        filename = f"placement_{self.evalname}_{self.gr_title_filename}"
         xls = self._excel_feuille_placement()
         return scu.send_file(xls, filename, scu.XLSX_SUFFIX, mime=scu.XLSX_MIMETYPE)
 
@@ -338,10 +317,10 @@ class PlacementRunner:
                 if self.evaluation.date_debut else '-'
                 } - Horaire : {self.evaluation.heure_debut()} à {self.evaluation.heure_fin()
             }"""
-        filename = "placement_%(evalname)s_%(gr_title_filename)s" % self.__dict__
+        filename = f"placement_{self.evalname}_{self.gr_title_filename}"
         titles = {
             "nom": "Nom",
-            "prenom": "Prenom",
+            "prenom": "Prénom",
             "colonne": "Colonne",
             "ligne": "Ligne",
             "place": "Place",
@@ -369,9 +348,7 @@ class PlacementRunner:
             columns_ids=columns_ids,
             rows=rows,
             filename=filename,
-            origin="Généré par %s le " % sco_version.SCONAME
-            + scu.timedate_human_repr()
-            + "",
+            origin=f"Généré par {sco_version.SCONAME} le {scu.timedate_human_repr()}",
             pdf_title=pdf_title,
             # pdf_shorttitle = '',
             preferences=sco_preferences.SemPreferences(
@@ -476,9 +453,9 @@ class PlacementRunner:
         }
 
     def _titres(self, worksheet):
-        datetime = time.strftime("%d/%m/%Y a %Hh%M")
+        date_time = time.strftime(scu.DATEATIME_FMT)
         worksheet.append_single_cell_row(
-            "Feuille placement etudiants éditée le %s" % datetime, self.styles["titres"]
+            f"Feuille placement etudiants éditée le {date_time}", self.styles["titres"]
         )
         for line, desceval in enumerate(self.desceval):
             if line in [1, 4, 7]:
@@ -497,7 +474,7 @@ class PlacementRunner:
         # entetes colonnes - feuille0
         cells = [ws0.make_cell()]
         for col in range(self.nb_rangs):
-            cells.append(ws0.make_cell("colonne %s" % (col + 1), self.styles["2b"]))
+            cells.append(ws0.make_cell(f"colonne {col + 1}", self.styles["2b"]))
         ws0.append_row(cells)
 
         # etudiants - feuille0
@@ -517,7 +494,7 @@ class PlacementRunner:
             if self.etiquetage == COORD:
                 cell_c = ws0.make_cell("", self.styles["1bb"])
             else:
-                cell_c = ws0.make_cell("place %s" % place, self.styles["1bb"])
+                cell_c = ws0.make_cell(f"place {place}", self.styles["1bb"])
                 place = place + 1
             cells_c.append(cell_c)
             ws0.set_row_dimension_height(row, space / 25)
diff --git a/app/templates/scodoc/forms/placement.j2 b/app/templates/scodoc/forms/placement.j2
index ecfe3664..b7e6e582 100644
--- a/app/templates/scodoc/forms/placement.j2
+++ b/app/templates/scodoc/forms/placement.j2
@@ -13,6 +13,7 @@
             {% endfor %}
         </ul>
         {% endif %}
+        <span class="help">{{field.description}}</span>
     </td>
 </tr>
 {% endmacro %}
@@ -60,6 +61,8 @@
             <input id="gr_cancel" type=submit value="Annuler">
             </script>
     </form>
+
+    <div class="help">
     <h3>Explications</h3>
     <ul>
         <li>préciser les surveillants et la localisation (bâtiment et salle) et indiquer la largeur de la salle (nombre
@@ -85,5 +88,6 @@
             </ul>
         </li>
     </ul>
+    </div>
 </div>
 {% endblock %}
\ No newline at end of file
diff --git a/sco_version.py b/sco_version.py
index d6221591..1dae3244 100644
--- a/sco_version.py
+++ b/sco_version.py
@@ -3,7 +3,7 @@
 
 "Infos sur version ScoDoc"
 
-SCOVERSION = "9.7.31"
+SCOVERSION = "9.7.32"
 
 SCONAME = "ScoDoc"
 
-- 
GitLab