From a58919d8b4dac26d8168fcae5ab38459e4b39928 Mon Sep 17 00:00:00 2001
From: Emmanuel Viennet <emmanuel.viennet@gmail.com>
Date: Fri, 21 Jul 2023 09:25:39 +0200
Subject: [PATCH] =?UTF-8?q?Utilisation=20syst=C3=A9matique=20du=20nouvel?=
 =?UTF-8?q?=20=C3=A9diteur=20de=20partition?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 app/scodoc/sco_formsemestre_status.py    |   7 +-
 app/scodoc/sco_inscr_passage.py          |   8 +-
 app/scodoc/sco_synchro_etuds.py          |  11 +-
 app/static/js/groupmgr.js                | 774 ++++++++++++-----------
 app/templates/scolar/affect_groups.j2    |   7 +
 app/templates/scolar/partition_editor.j2 |   3 +
 app/views/scolar.py                      |   7 +-
 7 files changed, 431 insertions(+), 386 deletions(-)

diff --git a/app/scodoc/sco_formsemestre_status.py b/app/scodoc/sco_formsemestre_status.py
index c1d15bfe3..4e4be8677 100644
--- a/app/scodoc/sco_formsemestre_status.py
+++ b/app/scodoc/sco_formsemestre_status.py
@@ -926,9 +926,10 @@ def _make_listes_sem(formsemestre: FormSemestre, with_absences=True):
             H.append('<p class="help indent">Aucun groupe peuplé dans cette partition')
             if sco_groups.sco_permissions_check.can_change_groups(formsemestre.id):
                 H.append(
-                    f""" (<a href="{url_for("scolar.affect_groups",
-                    scodoc_dept=g.scodoc_dept,
-                    partition_id=partition["partition_id"])
+                    f""" (<a href="{url_for("scolar.partition_editor",
+                        scodoc_dept=g.scodoc_dept, 
+                        formsemestre_id=formsemestre.id, 
+                        edit_partition=1)
                     }" class="stdlink">créer</a>)"""
                 )
             H.append("</p>")
diff --git a/app/scodoc/sco_inscr_passage.py b/app/scodoc/sco_inscr_passage.py
index 854e8c22a..68d838b5a 100644
--- a/app/scodoc/sco_inscr_passage.py
+++ b/app/scodoc/sco_inscr_passage.py
@@ -415,10 +415,10 @@ def formsemestre_inscr_passage(
             ):  # il y a au moins une vraie partition
                 H.append(
                     f"""<li><a class="stdlink" href="{
-                        url_for("scolar.affect_groups",
-                            scodoc_dept=g.scodoc_dept, partition_id=partition["partition_id"])
-                }">Répartir les groupes de {partition["partition_name"]}</a></li>
-                """
+                        url_for("scolar.partition_editor", scodoc_dept=g.scodoc_dept,
+                                formsemestre_id=formsemestre_id)
+                    }">Répartir les groupes de {partition["partition_name"]}</a></li>
+                    """
                 )
 
     #
diff --git a/app/scodoc/sco_synchro_etuds.py b/app/scodoc/sco_synchro_etuds.py
index 764d889f4..6b1d8908a 100644
--- a/app/scodoc/sco_synchro_etuds.py
+++ b/app/scodoc/sco_synchro_etuds.py
@@ -55,6 +55,7 @@ EKEY_APO = "nip"
 EKEY_SCO = "code_nip"
 EKEY_NAME = "code NIP"
 
+
 # view:
 def formsemestre_synchro_etuds(
     formsemestre_id,
@@ -270,11 +271,10 @@ def formsemestre_synchro_etuds(
             if partitions:  # il y a au moins une vraie partition
                 H.append(
                     f"""<li><a class="stdlink" href="{
-                        url_for("scolar.affect_groups",
-                scodoc_dept=g.scodoc_dept,
-                partition_id=partitions[0]["partition_id"]
-                )}">Répartir les groupes de {partitions[0]["partition_name"]}</a></li>
-                """
+                        url_for("scolar.partition_editor",
+                            scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre_id)
+                    }">Répartir les groupes de {partitions[0]["partition_name"]}</a></li>
+                    """
                 )
 
     H.append(footer)
@@ -407,6 +407,7 @@ def list_synch(sem, anneeapogee=None):
     )
     #
     cnx = ndb.GetDBConnexion()
+
     # Tri listes
     def set_to_sorted_list(etudset, etud_apo=False, is_inscrit=False):
         def key2etud(key, etud_apo=False):
diff --git a/app/static/js/groupmgr.js b/app/static/js/groupmgr.js
index 14b074e0e..659027648 100644
--- a/app/static/js/groupmgr.js
+++ b/app/static/js/groupmgr.js
@@ -1,10 +1,9 @@
 /* -*- mode: javascript -*-
  *
  * ScoDoc: Affectation des groupes de TD
- * re-ecriture utilisant jQuery de l'ancien code
+ *  OBSOLETE: Page ScoDoc 7 avec jQuery, remplacée par partition_editor
  */
 
-
 /* --- Globals ---- */
 var EtudColors = ["#E8EEF7", "#ffffff"]; // [ "#E8EEF7", "#E0ECFF", "#E5E6BE", "#F3EAE2", "#E3EAE1" ];
 var EtudColorsIdx = 0;
@@ -16,442 +15,471 @@ var groups_unsaved = false;
 var groups = new Object(); // Liste des groupes
 
 function loadGroupes() {
-    $("#gmsg")[0].innerHTML = 'Chargement des groupes en cours...';
-    $("#gmsg")[0].style.display = "block";
-    var partition_id = document.formGroup.partition_id.value;
-
-    $.get(SCO_URL + '/XMLgetGroupsInPartition', { partition_id: partition_id })
-        .done(
-            function (data) {
-                var nodes = data.getElementsByTagName('group');
-                if (nodes) {
-                    var nbgroups = nodes.length;
-                    // put last group at first (etudiants sans groupes)
-                    if (nodes.length > 1 && nodes[nbgroups - 1].attributes.getNamedItem("group_id").value == '_none_') {
-                        populateGroup(nodes[nodes.length - 1]);
-                        nbgroups -= 1;
-                    }
-                    // then standard groups
-                    for (var i = 0; i < nbgroups; i++) {
-                        populateGroup(nodes[i]);
-                    }
-                }
-                $("#gmsg")[0].innerHTML = '';
-                $("#gmsg")[0].style.display = "none";
-                updateginfo();
-            }
-        )
+  $("#gmsg")[0].innerHTML = "Chargement des groupes en cours...";
+  $("#gmsg")[0].style.display = "block";
+  var partition_id = document.formGroup.partition_id.value;
+
+  $.get(SCO_URL + "/XMLgetGroupsInPartition", {
+    partition_id: partition_id,
+  }).done(function (data) {
+    var nodes = data.getElementsByTagName("group");
+    if (nodes) {
+      var nbgroups = nodes.length;
+      // put last group at first (etudiants sans groupes)
+      if (
+        nodes.length > 1 &&
+        nodes[nbgroups - 1].attributes.getNamedItem("group_id").value ==
+          "_none_"
+      ) {
+        populateGroup(nodes[nodes.length - 1]);
+        nbgroups -= 1;
+      }
+      // then standard groups
+      for (var i = 0; i < nbgroups; i++) {
+        populateGroup(nodes[i]);
+      }
+    }
+    $("#gmsg")[0].innerHTML = "";
+    $("#gmsg")[0].style.display = "none";
+    updateginfo();
+  });
 }
 
 function populateGroup(node) {
-    var group_id = node.attributes.getNamedItem("group_id").value;
-    var group_name = node.attributes.getNamedItem("group_name").value;
-    var groups_editable = Boolean(parseInt(node.attributes.getNamedItem("groups_editable").value));
-    // CREE LA BOITE POUR CE GROUPE
-    if (group_id) {
-        var gbox = new CGroupBox(group_id, group_name, groups_editable);
-        var etuds = node.getElementsByTagName('etud');
-        var x = '';
-        gbox.sorting = false; // disable to speedup
-        EtudColorsIdx = 0; // repart de la premiere couleur
-        for (var j = 0; j < etuds.length; j++) {
-            var nom = etuds[j].attributes.getNamedItem("nom").value;
-            var prenom = etuds[j].attributes.getNamedItem("prenom").value;
-            var sexe = etuds[j].attributes.getNamedItem("sexe").value;
-            var etudid = etuds[j].attributes.getNamedItem("etudid").value;
-            var origin = etuds[j].attributes.getNamedItem("origin").value;
-            var etud = new CDraggableEtud(nom, prenom, sexe, origin, etudid);
-            gbox.createEtudInGroup(etud, group_id);
-        }
-        gbox.sorting = true;
-        gbox.updateTitle(); // sort 
+  var group_id = node.attributes.getNamedItem("group_id").value;
+  var group_name = node.attributes.getNamedItem("group_name").value;
+  var groups_editable = Boolean(
+    parseInt(node.attributes.getNamedItem("groups_editable").value)
+  );
+  // CREE LA BOITE POUR CE GROUPE
+  if (group_id) {
+    var gbox = new CGroupBox(group_id, group_name, groups_editable);
+    var etuds = node.getElementsByTagName("etud");
+    var x = "";
+    gbox.sorting = false; // disable to speedup
+    EtudColorsIdx = 0; // repart de la premiere couleur
+    for (var j = 0; j < etuds.length; j++) {
+      var nom = etuds[j].attributes.getNamedItem("nom").value;
+      var prenom = etuds[j].attributes.getNamedItem("prenom").value;
+      var sexe = etuds[j].attributes.getNamedItem("sexe").value;
+      var etudid = etuds[j].attributes.getNamedItem("etudid").value;
+      var origin = etuds[j].attributes.getNamedItem("origin").value;
+      var etud = new CDraggableEtud(nom, prenom, sexe, origin, etudid);
+      gbox.createEtudInGroup(etud, group_id);
     }
+    gbox.sorting = true;
+    gbox.updateTitle(); // sort
+  }
 }
 
-
 /* --- Boite pour un groupe --- */
 
 var groupBoxes = new Object(); // assoc group_id : groupBox
 var groupsToDelete = new Object(); // list of group_id to be supressed
 
 var CGroupBox = function (group_id, group_name, groups_editable) {
-    group_id = $.trim(group_id);
-    var regex = /^\w+$/;
-    if (!regex.test(group_id)) {
-        alert("Id de groupe invalide");
-        return;
-    }
-    if (group_id in groups) {
-        alert("Le groupe " + group_id + " existe déjà !");
-        return;
-    }
-    groups[group_id] = 1;
-    this.group_id = group_id;
-    this.group_name = group_name;
-    this.groups_editable = groups_editable;
-    this.etuds = new Object();
-    this.nbetuds = 0;
-    this.isNew = false; // true for newly user-created groups
-    this.sorting = true; // false to disable sorting
-
-    this.groupBox = document.createElement("div");
-    this.groupBox.className = "simpleDropPanel";
-    this.groupBox.id = group_id;
-    var titleDiv = document.createElement("div");
-    titleDiv.className = "groupTitle0";
-    titleDiv.appendChild(this.groupTitle());
-    this.groupBox.appendChild(titleDiv);
-    var gdiv = document.getElementById('groups');
-    gdiv.appendChild(this.groupBox);
-    this.updateTitle();
-    $(this.groupBox).droppable(
-        {
-            accept: ".box",
-            activeClass: "activatedPanel",
-            drop: function (event, ui) {
-                // alert("drop on " + this.group_name);
-                var etudid = ui.draggable[0].id;
-                var etud = ETUDS[etudid];
-                var newGroupName = this.id;
-                var oldGroupName = ETUD_GROUP[etudid];
-                $(groupBoxes[newGroupName].groupBox).append(ui.draggable)
-                ui.draggable[0].style.left = ""; // fix style (?)
-                ui.draggable[0].style.top = "";
-                etud.changeGroup(oldGroupName, newGroupName);
-                etud.htmlElement.style.fontStyle = 'italic'; // italic pour les etudiants deplaces                
-            }
-        });
-    /* On peut s'amuser a deplacer tout un groupe (visuellement: pas droppable) */
-    $(this.groupBox).draggable({
-        cursor: 'move',
-        containment: '#groups'
-    });
+  group_id = $.trim(group_id);
+  var regex = /^\w+$/;
+  if (!regex.test(group_id)) {
+    alert("Id de groupe invalide");
+    return;
+  }
+  if (group_id in groups) {
+    alert("Le groupe " + group_id + " existe déjà !");
+    return;
+  }
+  groups[group_id] = 1;
+  this.group_id = group_id;
+  this.group_name = group_name;
+  this.groups_editable = groups_editable;
+  this.etuds = new Object();
+  this.nbetuds = 0;
+  this.isNew = false; // true for newly user-created groups
+  this.sorting = true; // false to disable sorting
+
+  this.groupBox = document.createElement("div");
+  this.groupBox.className = "simpleDropPanel";
+  this.groupBox.id = group_id;
+  var titleDiv = document.createElement("div");
+  titleDiv.className = "groupTitle0";
+  titleDiv.appendChild(this.groupTitle());
+  this.groupBox.appendChild(titleDiv);
+  var gdiv = document.getElementById("groups");
+  gdiv.appendChild(this.groupBox);
+  this.updateTitle();
+  $(this.groupBox).droppable({
+    accept: ".box",
+    activeClass: "activatedPanel",
+    drop: function (event, ui) {
+      // alert("drop on " + this.group_name);
+      var etudid = ui.draggable[0].id;
+      var etud = ETUDS[etudid];
+      var newGroupName = this.id;
+      var oldGroupName = ETUD_GROUP[etudid];
+      $(groupBoxes[newGroupName].groupBox).append(ui.draggable);
+      ui.draggable[0].style.left = ""; // fix style (?)
+      ui.draggable[0].style.top = "";
+      etud.changeGroup(oldGroupName, newGroupName);
+      etud.htmlElement.style.fontStyle = "italic"; // italic pour les etudiants deplaces
+    },
+  });
+  /* On peut s'amuser a deplacer tout un groupe (visuellement: pas droppable) */
+  $(this.groupBox).draggable({
+    cursor: "move",
+    containment: "#groups",
+  });
 
-    groupBoxes[group_id] = this; // register
-    updateginfo();
-}
+  groupBoxes[group_id] = this; // register
+  updateginfo();
+};
 
 $.extend(CGroupBox.prototype, {
-    // menu for group title
-    groupTitle: function () {
-        let menuSpan = document.createElement("span");
-        menuSpan.className = "barrenav";
-        let h = "<table><tr><td><ul class=\"nav\"><li onmouseover=\"MenuDisplay(this)\" onmouseout=\"MenuHide(this)\"><a href=\"#\" class=\"menu custommenu\"><span id=\"titleSpan" + this.group_id + "\" class=\"groupTitle\">menu</span></a><ul>";
-        if (this.groups_editable && (this.group_id != '_none_')) {
-            h += "<li><a href=\"#\" onClick=\"suppressGroup('" + this.group_id + "');\">Supprimer</a></li>";
-            h += "<li><a href=\"#\" onClick=\"renameGroup('" + this.group_id + "');\">Renommer</a></li>";
-        }
-        h += "</ul></li></ul></td></tr></table>";
-        menuSpan.innerHTML = h;
-        return menuSpan;
-    },
-    // add etud to group, attach to DOM 
-    createEtudInGroup: function (etud) {
-        this.addEtudToGroup(etud);
-        this.groupBox.appendChild(etud.htmlElement);
-    },
-    // add existing etud to group (does not affect DOM)
-    addEtudToGroup: function (etud) {
-        etud.group_id = this.group_id;
-        this.etuds[etud.etudid] = etud;
-        this.nbetuds++;
-        ETUD_GROUP[etud.etudid] = this.group_id;
-        this.updateTitle();
-    },
-    // remove etud
-    removeEtud: function (etud) {
-        delete this.etuds[etud.etudid];
-        this.nbetuds--;
-        this.updateTitle();
-    },
-    // Update counter display
-    updateTitle: function () {
-        var tclass = '';
-        if (this.isNew) {
-            tclass = ' class="newgroup"'
-        }
-        var titleSpan = document.getElementById('titleSpan' + this.group_id);
-        if (this.group_id != '_none_')
-            titleSpan.innerHTML = '<span' + tclass + '>Groupe ' + this.group_name + ' (' + this.nbetuds + ')</span>';
-        else
-            titleSpan.innerHTML = '<span' + tclass + '>Etudiants sans groupe' + ' (' + this.nbetuds + ')</span>';
-        this.sortList(); // maintient toujours la liste triee
-    },
-    // Tri de la boite par nom
-    sortList: function () {
-        if (!this.sorting)
-            return;
-        var newRows = new Array();
-        for (var i = 1; i < this.groupBox.childNodes.length; i++) { // 1 car div titre
-            newRows[i - 1] = this.groupBox.childNodes[i];
-        }
-        var sortfn = function (a, b) {
-            // recupere les noms qui sont dans un span
-            var nom_a = a.childNodes[1].childNodes[0].nodeValue;
-            var nom_b = b.childNodes[1].childNodes[0].nodeValue;
-            // console.log( 'comp( %s, %s )', nom_a, nom_b );
-            if (nom_a == nom_b)
-                return 0;
-            if (nom_a < nom_b)
-                return -1;
-            return 1;
-        };
-        newRows.sort(sortfn);
-        for (var i = 0; i < newRows.length; i++) {
-            this.groupBox.appendChild(newRows[i]);
-            newRows[i].style.backgroundColor = EtudColors[EtudColorsIdx];
-            EtudColorsIdx = (EtudColorsIdx + 1) % EtudColors.length;
-        }
+  // menu for group title
+  groupTitle: function () {
+    let menuSpan = document.createElement("span");
+    menuSpan.className = "barrenav";
+    let h =
+      '<table><tr><td><ul class="nav"><li onmouseover="MenuDisplay(this)" onmouseout="MenuHide(this)"><a href="#" class="menu custommenu"><span id="titleSpan' +
+      this.group_id +
+      '" class="groupTitle">menu</span></a><ul>';
+    if (this.groups_editable && this.group_id != "_none_") {
+      h +=
+        '<li><a href="#" onClick="suppressGroup(\'' +
+        this.group_id +
+        "');\">Supprimer</a></li>";
+      h +=
+        '<li><a href="#" onClick="renameGroup(\'' +
+        this.group_id +
+        "');\">Renommer</a></li>";
     }
+    h += "</ul></li></ul></td></tr></table>";
+    menuSpan.innerHTML = h;
+    return menuSpan;
+  },
+  // add etud to group, attach to DOM
+  createEtudInGroup: function (etud) {
+    this.addEtudToGroup(etud);
+    this.groupBox.appendChild(etud.htmlElement);
+  },
+  // add existing etud to group (does not affect DOM)
+  addEtudToGroup: function (etud) {
+    etud.group_id = this.group_id;
+    this.etuds[etud.etudid] = etud;
+    this.nbetuds++;
+    ETUD_GROUP[etud.etudid] = this.group_id;
+    this.updateTitle();
+  },
+  // remove etud
+  removeEtud: function (etud) {
+    delete this.etuds[etud.etudid];
+    this.nbetuds--;
+    this.updateTitle();
+  },
+  // Update counter display
+  updateTitle: function () {
+    var tclass = "";
+    if (this.isNew) {
+      tclass = ' class="newgroup"';
+    }
+    var titleSpan = document.getElementById("titleSpan" + this.group_id);
+    if (this.group_id != "_none_")
+      titleSpan.innerHTML =
+        "<span" +
+        tclass +
+        ">Groupe " +
+        this.group_name +
+        " (" +
+        this.nbetuds +
+        ")</span>";
+    else
+      titleSpan.innerHTML =
+        "<span" +
+        tclass +
+        ">Etudiants sans groupe" +
+        " (" +
+        this.nbetuds +
+        ")</span>";
+    this.sortList(); // maintient toujours la liste triee
+  },
+  // Tri de la boite par nom
+  sortList: function () {
+    if (!this.sorting) return;
+    var newRows = new Array();
+    for (var i = 1; i < this.groupBox.childNodes.length; i++) {
+      // 1 car div titre
+      newRows[i - 1] = this.groupBox.childNodes[i];
+    }
+    var sortfn = function (a, b) {
+      // recupere les noms qui sont dans un span
+      var nom_a = a.childNodes[1].childNodes[0].nodeValue;
+      var nom_b = b.childNodes[1].childNodes[0].nodeValue;
+      // console.log( 'comp( %s, %s )', nom_a, nom_b );
+      if (nom_a == nom_b) return 0;
+      if (nom_a < nom_b) return -1;
+      return 1;
+    };
+    newRows.sort(sortfn);
+    for (var i = 0; i < newRows.length; i++) {
+      this.groupBox.appendChild(newRows[i]);
+      newRows[i].style.backgroundColor = EtudColors[EtudColorsIdx];
+      EtudColorsIdx = (EtudColorsIdx + 1) % EtudColors.length;
+    }
+  },
 });
 
-
 function suppressGroup(group_id) {
-    // 1- associate all members to group _none_
-    if (!groupBoxes['_none_']) {
-        // create group _none_
-        var gbox = new CGroupBox('_none_', 'Etudiants sans groupe', true);
-    }
-    var dst_group_id = groupBoxes['_none_'].group_id;
-    var src_box_etuds = groupBoxes[group_id].etuds;
-    for (var etudid in src_box_etuds) {
-        var etud = src_box_etuds[etudid];
-        etud.changeGroup(group_id, dst_group_id);
-        groupBoxes['_none_'].groupBox.appendChild(etud.htmlElement);
-    }
-    groupBoxes['_none_'].updateTitle();
-    // 2- add group to list of groups to be removed (unless it's a new group)
-    if (!groupBoxes[group_id].isNew)
-        groupsToDelete[group_id] = true;
-    // 3- delete objects and remove from DOM
-    var div = document.getElementById(group_id);
-    div.remove();
-    delete groupBoxes[group_id];
-    groups_unsaved = true;
-    updateginfo();
+  // 1- associate all members to group _none_
+  if (!groupBoxes["_none_"]) {
+    // create group _none_
+    var gbox = new CGroupBox("_none_", "Etudiants sans groupe", true);
+  }
+  var dst_group_id = groupBoxes["_none_"].group_id;
+  var src_box_etuds = groupBoxes[group_id].etuds;
+  for (var etudid in src_box_etuds) {
+    var etud = src_box_etuds[etudid];
+    etud.changeGroup(group_id, dst_group_id);
+    groupBoxes["_none_"].groupBox.appendChild(etud.htmlElement);
+  }
+  groupBoxes["_none_"].updateTitle();
+  // 2- add group to list of groups to be removed (unless it's a new group)
+  if (!groupBoxes[group_id].isNew) groupsToDelete[group_id] = true;
+  // 3- delete objects and remove from DOM
+  var div = document.getElementById(group_id);
+  div.remove();
+  delete groupBoxes[group_id];
+  groups_unsaved = true;
+  updateginfo();
 }
 
-
 function renameGroup(group_id) {
-    // 1-- save modifications
-    if (groups_unsaved) {
-        alert("Enregistrez ou annulez vos changement avant !");
-    } else {
-        // 2- form rename
-        document.location = 'group_rename?group_id=' + group_id;
-    }
+  // 1-- save modifications
+  if (groups_unsaved) {
+    alert("Enregistrez ou annulez vos changement avant !");
+  } else {
+    // 2- form rename
+    document.location = "group_rename?group_id=" + group_id;
+  }
 }
 
 var createdGroupId = 0;
 function newGroupId() {
-    var gid;
-    do {
-        gid = 'NG' + createdGroupId.toString();
-        createdGroupId += 1;
-    } while (gid in groupBoxes);
-    return gid;
+  var gid;
+  do {
+    gid = "NG" + createdGroupId.toString();
+    createdGroupId += 1;
+  } while (gid in groupBoxes);
+  return gid;
 }
 
 // Creation d'un groupe
 function createGroup() {
-    var group_name = document.formGroup.groupName.value.trim();
-    if (!group_name) {
-        alert("Nom de groupe vide !");
+  var group_name = document.formGroup.groupName.value.trim();
+  if (!group_name) {
+    alert("Nom de groupe vide !");
+    return false;
+  }
+  if (group_name.length >= 32) {
+    // SHORT_STR_LEN
+    alert("Nom de groupe trop long !");
+    return false;
+  }
+  // check name:
+  for (var group_id in groupBoxes) {
+    if (group_id != "extend") {
+      if (groupBoxes[group_id].group_name == group_name) {
+        alert("Nom de groupe déja existant !");
         return false;
+      }
     }
-    if (group_name.length >= 32) { // SHORT_STR_LEN
-        alert("Nom de groupe trop long !");
-        return false;
-    }
-    // check name:
-    for (var group_id in groupBoxes) {
-        if (group_id != 'extend') {
-            if (groupBoxes[group_id].group_name == group_name) {
-                alert("Nom de groupe déja existant !");
-                return false;
-            }
-        }
-    }
-    var group_id = newGroupId();
-    groups_unsaved = true;
-    var gbox = new CGroupBox(group_id, group_name, true);
-    gbox.isNew = true;
-    gbox.updateTitle();
-    return true;
+  }
+  var group_id = newGroupId();
+  groups_unsaved = true;
+  var gbox = new CGroupBox(group_id, group_name, true);
+  gbox.isNew = true;
+  gbox.updateTitle();
+  return true;
 }
 
 /* --- Etudiant draggable --- */
 var CDraggableEtud = function (nom, prenom, sexe, origin, etudid) {
-    this.type = 'Custom';
-    this.name = etudid;
-    this.etudid = etudid;
-    this.nom = nom;
-    this.prenom = prenom;
-    this.sexe = sexe;
-    this.origin = origin;
-    this.createNode();
-    ETUDS[etudid] = this;
-    NbEtuds++;
-}
+  this.type = "Custom";
+  this.name = etudid;
+  this.etudid = etudid;
+  this.nom = nom;
+  this.prenom = prenom;
+  this.sexe = sexe;
+  this.origin = origin;
+  this.createNode();
+  ETUDS[etudid] = this;
+  NbEtuds++;
+};
 
 $.extend(CDraggableEtud.prototype, {
-    repr: function () {
-        return this.sexe + ' ' + this.prenom + ' <span class="nom">' + this.nom + '</span> ' + '<b>' + this.origin + '</b>';
-    },
-    createNode: function () {
-        // Create DOM element for student
-        var e = document.createElement("div");
-        this.htmlElement = e;
-        e.className = "box";
-        e.id = this.etudid;
-        // e.style.backgroundColor = EtudColors[EtudColorsIdx];
-        // EtudColorsIdx = (EtudColorsIdx + 1) % EtudColors.length;
-        //var txtNode = document.createTextNode( this.repr() );
-        //e.appendChild(txtNode);
-        e.innerHTML = this.repr();
-        // declare as draggable
-        $(e).draggable({
-            cursor: 'move',
-            stack: '#groups div',
-            containment: '#groups',
-            revert: 'invalid'
-        });
-    },
-    endDrag: function () {
-        var el = this.htmlElement;
-        var p = el.parentNode;
-        // alert("endDrag: [" + this.name +"] " + p.id );
-        this.changeGroup(this.group_id, p.id);
-        this.htmlElement.style.fontStyle = 'italic'; // italic pour les etudiants deplaces
-    },
-    // Move a student from a group to another
-    changeGroup: function (oldGroupName, newGroupName) {
-        if (oldGroupName == newGroupName) {
-            // drop on original group, just sort
-            groupBoxes[oldGroupName].updateTitle();
-            return;
-        }
-        var oldGroupBox = null;
-        if (oldGroupName) {
-            oldGroupBox = groupBoxes[oldGroupName];
-        }
-        var newGroupBox = groupBoxes[newGroupName];
-        newGroupBox.addEtudToGroup(this);
-        if (oldGroupBox)
-            oldGroupBox.removeEtud(this);
-        groups_unsaved = true;
-        updatesavedinfo();
+  repr: function () {
+    return (
+      this.sexe +
+      " " +
+      this.prenom +
+      ' <span class="nom">' +
+      this.nom +
+      "</span> " +
+      "<b>" +
+      this.origin +
+      "</b>"
+    );
+  },
+  createNode: function () {
+    // Create DOM element for student
+    var e = document.createElement("div");
+    this.htmlElement = e;
+    e.className = "box";
+    e.id = this.etudid;
+    // e.style.backgroundColor = EtudColors[EtudColorsIdx];
+    // EtudColorsIdx = (EtudColorsIdx + 1) % EtudColors.length;
+    //var txtNode = document.createTextNode( this.repr() );
+    //e.appendChild(txtNode);
+    e.innerHTML = this.repr();
+    // declare as draggable
+    $(e).draggable({
+      cursor: "move",
+      stack: "#groups div",
+      containment: "#groups",
+      revert: "invalid",
+    });
+  },
+  endDrag: function () {
+    var el = this.htmlElement;
+    var p = el.parentNode;
+    // alert("endDrag: [" + this.name +"] " + p.id );
+    this.changeGroup(this.group_id, p.id);
+    this.htmlElement.style.fontStyle = "italic"; // italic pour les etudiants deplaces
+  },
+  // Move a student from a group to another
+  changeGroup: function (oldGroupName, newGroupName) {
+    if (oldGroupName == newGroupName) {
+      // drop on original group, just sort
+      groupBoxes[oldGroupName].updateTitle();
+      return;
     }
+    var oldGroupBox = null;
+    if (oldGroupName) {
+      oldGroupBox = groupBoxes[oldGroupName];
+    }
+    var newGroupBox = groupBoxes[newGroupName];
+    newGroupBox.addEtudToGroup(this);
+    if (oldGroupBox) oldGroupBox.removeEtud(this);
+    groups_unsaved = true;
+    updatesavedinfo();
+  },
 });
 
-
 /* --- Upload du resultat --- */
 function processResponse(value) {
-    location.reload(); // necessaire pour reinitialiser les id des groupes créés
+  location.reload(); // necessaire pour reinitialiser les id des groupes créés
 }
 
 function handleError(msg) {
-    alert('Error: ' + msg);
-    console.log('Error: ' + msg);
+  alert("Error: " + msg);
+  console.log("Error: " + msg);
 }
 
 function submitGroups() {
-    var url = SCO_URL + '/setGroups';
-    // build post request body: groupname \n etudid; ...
-    var groupsLists = '';
-    var groupsToCreate = '';
-    for (var group_id in groupBoxes) {
-        if (group_id != 'extend') { // je ne sais pas ce dont il s'agit ???
-            if (group_id != '_none_') { // ne renvoie pas le groupe des sans-groupes
-                groupBox = groupBoxes[group_id];
-                if (groupBox.isNew) {
-                    groupsToCreate += groupBox.group_name + ';';
-                    for (var etudid in groupBox.etuds) {
-                        if (etudid != 'extend')
-                            groupsToCreate += etudid + ';';
-                    }
-                    groupsToCreate += '\n';
-                    groupBox.isNew = false; // is no more new !
-                } else {
-                    groupsLists += group_id + ';';
-                    for (var etudid in groupBox.etuds) {
-                        if (etudid != 'extend')
-                            groupsLists += etudid + ';';
-                    }
-                    groupsLists += '\n';
-                }
-            }
+  var url = SCO_URL + "/setGroups";
+  // build post request body: groupname \n etudid; ...
+  var groupsLists = "";
+  var groupsToCreate = "";
+  for (var group_id in groupBoxes) {
+    if (group_id != "extend") {
+      // je ne sais pas ce dont il s'agit ???
+      if (group_id != "_none_") {
+        // ne renvoie pas le groupe des sans-groupes
+        groupBox = groupBoxes[group_id];
+        if (groupBox.isNew) {
+          groupsToCreate += groupBox.group_name + ";";
+          for (var etudid in groupBox.etuds) {
+            if (etudid != "extend") groupsToCreate += etudid + ";";
+          }
+          groupsToCreate += "\n";
+          groupBox.isNew = false; // is no more new !
+        } else {
+          groupsLists += group_id + ";";
+          for (var etudid in groupBox.etuds) {
+            if (etudid != "extend") groupsLists += etudid + ";";
+          }
+          groupsLists += "\n";
         }
+      }
     }
-    var todel = '';
-    for (var group_id in groupsToDelete) {
-        todel += group_id + ';';
-    }
-    groupsToDelete = new Object(); // empty
-    var partition_id = document.formGroup.partition_id.value;
-    // Send to server
-    $.post(url, {
-        groupsLists: groupsLists,
-        partition_id: partition_id,
-        groupsToDelete: todel,
-        groupsToCreate: groupsToCreate
+  }
+  var todel = "";
+  for (var group_id in groupsToDelete) {
+    todel += group_id + ";";
+  }
+  groupsToDelete = new Object(); // empty
+  var partition_id = document.formGroup.partition_id.value;
+  // Send to server
+  $.post(url, {
+    groupsLists: groupsLists,
+    partition_id: partition_id,
+    groupsToDelete: todel,
+    groupsToCreate: groupsToCreate,
+  })
+    .done(function (data) {
+      processResponse(data);
     })
-        .done(function (data) {
-            processResponse(data);
-        })
-        .fail(function (xhr, status, error) {
-            let msg = "inconnue";
-            if (xhr.responseXML.childNodes.length > 0) {
-                msg = xhr.responseXML.childNodes[0].innerHTML;
-            }
-            handleError("Erreur lors de l'enregistrement de groupes: " + msg);
-        });
+    .fail(function (xhr, status, error) {
+      let msg = "inconnue";
+      if (xhr.responseXML.childNodes.length > 0) {
+        msg = xhr.responseXML.childNodes[0].innerHTML;
+      }
+      handleError("Erreur lors de l'enregistrement de groupes: " + msg);
+    });
 }
 
 // Move to another partition (specified by menu)
 function GotoAnother() {
-    if (groups_unsaved) {
-        alert("Enregistrez ou annulez vos changement avant !");
-    } else
-        document.location = SCO_URL + '/affect_groups?partition_id=' + document.formGroup.other_partition_id.value;
+  if (groups_unsaved) {
+    alert("Enregistrez ou annulez vos changement avant !");
+  } else
+    document.location =
+      SCO_URL +
+      "/affect_groups?partition_id=" +
+      document.formGroup.other_partition_id.value;
 }
 
-
-// Boite information haut de page 
+// Boite information haut de page
 function updateginfo() {
-    var g = document.getElementById('ginfo');
-    var group_names = new Array();
-    for (var group_id in groupBoxes) {
-        if ((group_id != 'extend') && (groupBoxes[group_id].group_name)) {
-            group_names.push(groupBoxes[group_id].group_name);
-        }
+  var g = document.getElementById("ginfo");
+  var group_names = new Array();
+  for (var group_id in groupBoxes) {
+    if (group_id != "extend" && groupBoxes[group_id].group_name) {
+      group_names.push(groupBoxes[group_id].group_name);
     }
-    g.innerHTML = '<b>Groupes définis: ' + group_names.join(', ') + '<br/>'
-        + "Nombre d'etudiants: " + NbEtuds + '</b>';
-
-    updatesavedinfo();
+  }
+  g.innerHTML =
+    "<b>Groupes définis: " +
+    group_names.join(", ") +
+    "<br/>" +
+    "Nombre d'etudiants: " +
+    NbEtuds +
+    "</b>";
+
+  updatesavedinfo();
 }
 
 // Boite indiquant si modifications non enregistrees ou non
 function updatesavedinfo() {
-    var g = document.getElementById('savedinfo');
-    if (groups_unsaved) {
-        g.innerHTML = 'modifications non enregistrées';
-        g.style.visibility = 'visible';
-    } else {
-        g.innerHTML = '';
-        g.style.visibility = 'hidden';
-    }
-    return true;
+  var g = document.getElementById("savedinfo");
+  if (groups_unsaved) {
+    g.innerHTML = "modifications non enregistrées";
+    g.style.visibility = "visible";
+  } else {
+    g.innerHTML = "";
+    g.style.visibility = "hidden";
+  }
+  return true;
 }
 
-
 $(function () {
-    loadGroupes();
+  loadGroupes();
 });
-
-/* debug:
-
-var g = new CGroupBox('G0', 'Toto');
-
-*/
\ No newline at end of file
diff --git a/app/templates/scolar/affect_groups.j2 b/app/templates/scolar/affect_groups.j2
index 1db702757..51d7f599b 100644
--- a/app/templates/scolar/affect_groups.j2
+++ b/app/templates/scolar/affect_groups.j2
@@ -2,6 +2,13 @@
 {{ sco_header|safe }}
 <h2 class="formsemestre">Affectation aux groupes de {{ partition.partition_name }}</h2>
 
+<p class="help">
+    👉💡 vous pourriez essayer <a href="{{
+        url_for('scolar.partition_editor',
+        scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre_id)
+    }}" class="stdlink">le nouvel éditeur</a>
+</p>
+
 <p>Faites glisser les étudiants d'un groupe à l'autre. Les modifications ne
     sont enregistrées que lorsque vous cliquez sur le bouton "<em>Enregistrer ces groupes</em>".
     Vous pouvez créer de nouveaux groupes. Pour <em>supprimer</em> ou <em>renommer</em>
diff --git a/app/templates/scolar/partition_editor.j2 b/app/templates/scolar/partition_editor.j2
index 98c8cf71c..d98d8dc19 100644
--- a/app/templates/scolar/partition_editor.j2
+++ b/app/templates/scolar/partition_editor.j2
@@ -59,6 +59,9 @@
 
 		document.querySelector("body").classList.add("loaded");
 		document.querySelector('.wait').style.display = "none";
+		{% if edit_partition %}
+		setEditMode();
+		{% endif %}
 	}
 
 	function fetchData(request) {
diff --git a/app/views/scolar.py b/app/views/scolar.py
index b8f103232..da978bd3a 100644
--- a/app/views/scolar.py
+++ b/app/views/scolar.py
@@ -907,8 +907,12 @@ sco_publish(
 @scodoc
 @permission_required(Permission.ScoView)
 @scodoc7func
-def partition_editor(formsemestre_id: int):
+def partition_editor(formsemestre_id: int, edit_partition=False):
+    """Page édition groupes et partitions
+    Si edit_partition, se met en mode édition des partitions.
+    """
     formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
+    edit_partition = bool(int(edit_partition)) if edit_partition else False
     formsemestre.setup_parcours_groups()
     H = [
         html_sco_header.sco_header(
@@ -928,6 +932,7 @@ def partition_editor(formsemestre_id: int):
             read_only=not sco_groups.sco_permissions_check.can_change_groups(
                 formsemestre_id
             ),
+            edit_partition=edit_partition,
         ),
         html_sco_header.sco_footer(),
     ]
-- 
GitLab