From 5af4b5bed6cceeb66b9e0eb1b667671321106290 Mon Sep 17 00:00:00 2001
From: Emmanuel Viennet <emmanuel.viennet@gmail.com>
Date: Thu, 26 May 2022 23:45:57 +0200
Subject: [PATCH] WIP: Partitions non editables (pour groupes de parcours)

---
 app/models/groups.py                          |  6 ++-
 app/scodoc/sco_formsemestre_inscriptions.py   | 27 ++++++----
 app/scodoc/sco_formsemestre_status.py         |  2 +-
 app/scodoc/sco_groups.py                      | 51 ++++++++++++++-----
 app/scodoc/sco_import_etuds.py                | 17 +++++--
 app/scodoc/sco_inscr_passage.py               | 11 ++--
 ...a2771105c21c_parcours_inscriptions_casc.py | 10 ++++
 7 files changed, 90 insertions(+), 34 deletions(-)

diff --git a/app/models/groups.py b/app/models/groups.py
index 1d24b60c0..27b763d11 100644
--- a/app/models/groups.py
+++ b/app/models/groups.py
@@ -23,7 +23,7 @@ class Partition(db.Model):
     )
     # "TD", "TP", ... (NULL for 'all')
     partition_name = db.Column(db.String(SHORT_STR_LEN))
-    # numero = ordre de presentation)
+    # Numero = ordre de presentation)
     numero = db.Column(db.Integer)
     # Calculer le rang ?
     bul_show_rank = db.Column(
@@ -33,6 +33,10 @@ class Partition(db.Model):
     show_in_lists = db.Column(
         db.Boolean(), nullable=False, default=True, server_default="true"
     )
+    # Editable ? (faux pour les groupes de parcours)
+    groups_editable = db.Column(
+        db.Boolean(), nullable=False, default=True, server_default="true"
+    )
     groups = db.relationship(
         "GroupDescr",
         backref=db.backref("partition", lazy=True),
diff --git a/app/scodoc/sco_formsemestre_inscriptions.py b/app/scodoc/sco_formsemestre_inscriptions.py
index b3fe98532..394af3be5 100644
--- a/app/scodoc/sco_formsemestre_inscriptions.py
+++ b/app/scodoc/sco_formsemestre_inscriptions.py
@@ -35,6 +35,7 @@ from flask import url_for, g, request
 from app.comp import res_sem
 from app.comp.res_compat import NotesTableCompat
 from app.models import FormSemestre
+from app.models.groups import GroupDescr, Partition
 import app.scodoc.sco_utils as scu
 from app import log
 from app.scodoc.scolog import logdb
@@ -263,8 +264,7 @@ def do_formsemestre_inscription_with_modules(
         args["etat"] = etat
     do_formsemestre_inscription_create(args, method=method)
     log(
-        "do_formsemestre_inscription_with_modules: etudid=%s formsemestre_id=%s"
-        % (etudid, formsemestre_id)
+        f"do_formsemestre_inscription_with_modules: etudid={etudid} formsemestre_id={formsemestre_id}"
     )
     # inscriptions aux groupes
     # 1- inscrit au groupe 'tous'
@@ -275,8 +275,14 @@ def do_formsemestre_inscription_with_modules(
     # 2- inscrit aux groupes
     for group_id in group_ids:
         if group_id and not group_id in gdone:
-            sco_groups.set_group(etudid, group_id)
-            gdone[group_id] = 1
+            group = GroupDescr.query.get_or_404(group_id)
+            if group.partition.groups_editable:
+                sco_groups.set_group(etudid, group_id)
+                gdone[group_id] = 1
+            else:
+                log(
+                    f"do_formsemestre_inscription_with_modules: group {group:r} belongs to non editable partition"
+                )
 
     # inscription a tous les modules de ce semestre
     modimpls = sco_moduleimpl.moduleimpl_withmodule_list(
@@ -534,11 +540,14 @@ def formsemestre_inscription_option(etudid, formsemestre_id):
         ue_status = nt.get_etud_ue_status(etudid, ue_id)
         if ue_status and ue_status["is_capitalized"]:
             sem_origin = sco_formsemestre.get_formsemestre(ue_status["formsemestre_id"])
-            ue_descr += ' <a class="discretelink" href="formsemestre_bulletinetud?formsemestre_id=%s&etudid=%s" title="%s">(capitalisée le %s)' % (
-                sem_origin["formsemestre_id"],
-                etudid,
-                sem_origin["titreannee"],
-                ndb.DateISOtoDMY(ue_status["event_date"]),
+            ue_descr += (
+                ' <a class="discretelink" href="formsemestre_bulletinetud?formsemestre_id=%s&etudid=%s" title="%s">(capitalisée le %s)'
+                % (
+                    sem_origin["formsemestre_id"],
+                    etudid,
+                    sem_origin["titreannee"],
+                    ndb.DateISOtoDMY(ue_status["event_date"]),
+                )
             )
         descr.append(
             (
diff --git a/app/scodoc/sco_formsemestre_status.py b/app/scodoc/sco_formsemestre_status.py
index 10e5dbe93..5cc5c47a6 100644
--- a/app/scodoc/sco_formsemestre_status.py
+++ b/app/scodoc/sco_formsemestre_status.py
@@ -346,7 +346,7 @@ def formsemestre_status_menubar(sem):
                 "title": "%s" % partition["partition_name"],
                 "endpoint": "scolar.affect_groups",
                 "args": {"partition_id": partition["partition_id"]},
-                "enabled": enabled,
+                "enabled": enabled and partition["groups_editable"],
             }
         )
     menuGroupes.append(
diff --git a/app/scodoc/sco_groups.py b/app/scodoc/sco_groups.py
index 8eae60e04..50e0e70ee 100644
--- a/app/scodoc/sco_groups.py
+++ b/app/scodoc/sco_groups.py
@@ -76,10 +76,12 @@ partitionEditor = ndb.EditableTable(
         "numero",
         "bul_show_rank",
         "show_in_lists",
+        "editable",
     ),
     input_formators={
         "bul_show_rank": bool,
         "show_in_lists": bool,
+        "editable": bool,
     },
 )
 
@@ -621,10 +623,12 @@ def comp_origin(etud, cur_sem):
         return ""  # parcours normal, ne le signale pas
 
 
-def set_group(etudid, group_id):
+def set_group(etudid: int, group_id: int) -> bool:
     """Inscrit l'étudiant au groupe.
     Return True if ok, False si deja inscrit.
-    Warning: don't check if group_id exists (the caller should check).
+    Warning:
+     - don't check if group_id exists (the caller should check).
+     - don't check if group's partition is editable
     """
     cnx = ndb.GetDBConnexion()
     cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
@@ -698,14 +702,28 @@ def setGroups(
     groupsToCreate="",  # name and members of new groups
     groupsToDelete="",  # groups to delete
 ):
-    """Affect groups (Ajax request)
+    """Affect groups (Ajax request): renvoie du XML
     groupsLists: lignes de la forme "group_id;etudid;...\n"
     groupsToCreate: lignes "group_name;etudid;...\n"
     groupsToDelete: group_id;group_id;...
+
+    Ne peux pas modifier les groupes des partitions non éditables.
     """
     from app.scodoc import sco_formsemestre
 
+    def xml_error(msg, code=404):
+        data = (
+            f'<?xml version="1.0" encoding="utf-8"?><response>Error: {msg}</response>'
+        )
+        response = make_response(data, code)
+        response.headers["Content-Type"] = scu.XML_MIMETYPE
+        return response
+
     partition = get_partition(partition_id)
+    if not partition["group_editable"]:
+        msg = "setGroups: partition non editable"
+        log(msg)
+        return xml_error(msg, code=403)
     formsemestre_id = partition["formsemestre_id"]
     if not sco_permissions_check.can_change_groups(formsemestre_id):
         raise AccessDenied("Vous n'avez pas le droit d'effectuer cette opération !")
@@ -727,8 +745,8 @@ def setGroups(
             continue
         try:
             group_id = int(group_id)
-        except ValueError as exc:
-            log("setGroups: ignoring invalid group_id={group_id}")
+        except ValueError:
+            log(f"setGroups: ignoring invalid group_id={group_id}")
             continue
         group = get_group(group_id)
         # Anciens membres du groupe:
@@ -967,14 +985,19 @@ def edit_partition_form(formsemestre_id=None):
                 for group in get_partition_groups(p)
             ]
             H.append(", ".join(lg))
-            H.append(
-                f"""</td><td><a class="stdlink" href="{
-                    url_for("scolar.affect_groups",
-                    scodoc_dept=g.scodoc_dept,
-                    partition_id=p["partition_id"])
-                }">répartir</a></td>
-                """
-            )
+            H.append("""</td><td>""")
+            if p["groups_editable"]:
+                H.append(
+                    f"""<a class="stdlink" href="{
+                        url_for("scolar.affect_groups",
+                        scodoc_dept=g.scodoc_dept,
+                        partition_id=p["partition_id"])
+                    }">répartir</a></td>
+                    """
+                )
+            else:
+                H.append("""non éditable""")
+            H.append("""</td>""")
             H.append(
                 '<td><a class="stdlink" href="partition_rename?partition_id=%s">renommer</a></td>'
                 % p["partition_id"]
@@ -1334,6 +1357,8 @@ def groups_auto_repartition(partition_id=None):
     from app.scodoc import sco_formsemestre
 
     partition = get_partition(partition_id)
+    if not partition["groups_editable"]:
+        raise AccessDenied("Partition non éditable")
     formsemestre_id = partition["formsemestre_id"]
     formsemestre = FormSemestre.query.get(formsemestre_id)
     # renvoie sur page édition groupes
diff --git a/app/scodoc/sco_import_etuds.py b/app/scodoc/sco_import_etuds.py
index 32b2530d4..fcad66228 100644
--- a/app/scodoc/sco_import_etuds.py
+++ b/app/scodoc/sco_import_etuds.py
@@ -33,14 +33,13 @@ import io
 import os
 import re
 import time
-from datetime import date
 
 from flask import g, url_for
 
 import app.scodoc.sco_utils as scu
 import app.scodoc.notesdb as ndb
 from app import log
-from app.models import ScolarNews
+from app.models import ScolarNews, GroupDescr
 
 from app.scodoc.sco_excel import COLORS
 from app.scodoc.sco_formsemestre_inscriptions import (
@@ -718,9 +717,17 @@ def scolars_import_admission(datafile, formsemestre_id=None, type_admission=None
                         )
 
                     for group_id in group_ids:
-                        sco_groups.change_etud_group_in_partition(
-                            args["etudid"], group_id
-                        )
+                        group = GroupDescr.query.get(group_id)
+                        if group.partition.groups_editable:
+                            sco_groups.change_etud_group_in_partition(
+                                args["etudid"], group_id
+                            )
+                        else:
+                            log("scolars_import_admission: partition non editable")
+                            diag.append(
+                                f"Attention: partition {group.partition} non editable (ignorée)"
+                            )
+
                 #
                 diag.append("import de %s" % (etud["nomprenom"]))
                 n_import += 1
diff --git a/app/scodoc/sco_inscr_passage.py b/app/scodoc/sco_inscr_passage.py
index 807792fb0..4f0146c1e 100644
--- a/app/scodoc/sco_inscr_passage.py
+++ b/app/scodoc/sco_inscr_passage.py
@@ -219,11 +219,12 @@ def do_inscrit(sem, etudids, inscrit_groupes=False):
 
             # inscrit aux groupes
             for partition_group in partition_groups:
-                sco_groups.change_etud_group_in_partition(
-                    etudid,
-                    partition_group["group_id"],
-                    partition_group,
-                )
+                if partition_group["groups_editable"]:
+                    sco_groups.change_etud_group_in_partition(
+                        etudid,
+                        partition_group["group_id"],
+                        partition_group,
+                    )
 
 
 def do_desinscrit(sem, etudids):
diff --git a/migrations/versions/a2771105c21c_parcours_inscriptions_casc.py b/migrations/versions/a2771105c21c_parcours_inscriptions_casc.py
index 55d194e02..74da8acb9 100644
--- a/migrations/versions/a2771105c21c_parcours_inscriptions_casc.py
+++ b/migrations/versions/a2771105c21c_parcours_inscriptions_casc.py
@@ -102,6 +102,13 @@ def upgrade():
         ["id"],
         ondelete="CASCADE",
     )
+    # GROUPES
+    op.add_column(
+        "partition",
+        sa.Column(
+            "groups_editable", sa.Boolean(), server_default="true", nullable=False
+        ),
+    )
     # INSCRIPTIONS
     op.drop_constraint(
         "notes_formsemestre_inscription_etudid_fkey",
@@ -192,6 +199,7 @@ def upgrade():
     # ### end Alembic commands ###
 
 
+# --------------------------------------------------------------
 def downgrade():
     # ### commands auto generated by Alembic - please adjust! ###
     op.drop_constraint(
@@ -232,6 +240,8 @@ def downgrade():
     op.create_foreign_key(
         "notes_notes_etudid_fkey", "notes_notes", "identite", ["etudid"], ["id"]
     )
+    # GROUPES
+    op.drop_column("partition", "groups_editable")
     # INSCRIPTIONS
     op.drop_constraint(
         "notes_formsemestre_inscription_etudid_fkey",
-- 
GitLab