From a59f5136a4a261dee1e7b88e68e7790359b8fe37 Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Fri, 2 Sep 2022 15:07:59 +0200 Subject: [PATCH] =?UTF-8?q?Edition=20des=20groupes:=20am=C3=A9lioration=20?= =?UTF-8?q?traitement=20erreurs,=20empeche=20edition=20des=20parcours?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/api/partitions.py | 3 +- app/scodoc/sco_formsemestre.py | 2 +- app/scodoc/sco_groups.py | 138 ++++++++---------- app/scodoc/sco_groups_copy.py | 8 +- app/static/icons/scologo_img.png | Bin 35044 -> 1712 bytes app/static/js/groupmgr.js | 29 ++-- app/templates/scolar/affect_groups.html | 4 +- app/views/scolar.py | 21 +-- .../fakedatabase/create_test_api_database.py | 2 +- 9 files changed, 85 insertions(+), 122 deletions(-) diff --git a/app/api/partitions.py b/app/api/partitions.py index a1229387b..aa080f48b 100644 --- a/app/api/partitions.py +++ b/app/api/partitions.py @@ -323,9 +323,10 @@ def group_edit(group_id: int): data = request.get_json(force=True) # may raise 400 Bad Request group_name = data.get("group_name") if group_name is not None: + group_name = group_name.strip() if not GroupDescr.check_name(group.partition, group_name, existing=True): return json_error(404, "invalid group_name") - group.group_name = group_name.strip() + group.group_name = group_name db.session.add(group) db.session.commit() log(f"modified {group}") diff --git a/app/scodoc/sco_formsemestre.py b/app/scodoc/sco_formsemestre.py index a4f9ad500..c16eb1db3 100644 --- a/app/scodoc/sco_formsemestre.py +++ b/app/scodoc/sco_formsemestre.py @@ -256,7 +256,7 @@ def do_formsemestre_create(args, silent=False): redirect=0, numero=1000000, # à la fin ) - _group_id = sco_groups.create_group(partition_id, default=True) + _ = sco_groups.create_group(partition_id, default=True) # news if "titre" not in args: diff --git a/app/scodoc/sco_groups.py b/app/scodoc/sco_groups.py index ae8f57c8e..737c95e13 100644 --- a/app/scodoc/sco_groups.py +++ b/app/scodoc/sco_groups.py @@ -93,7 +93,7 @@ groupEditor = ndb.EditableTable( group_list = groupEditor.list -def get_group(group_id: int): +def get_group(group_id: int) -> dict: """Returns group object, with partition""" r = ndb.SimpleDictFetch( """SELECT gd.id AS group_id, gd.*, p.id AS partition_id, p.* @@ -238,8 +238,8 @@ def get_default_group(formsemestre_id, fix_if_missing=False): partition_id = partition_create( formsemestre_id, default=True, redirect=False ) - group_id = create_group(partition_id, default=True) - return group_id + group = create_group(partition_id, default=True) + return group.id # debug check if len(r) != 1: raise ScoException("invalid group structure for %s" % formsemestre_id) @@ -292,34 +292,6 @@ def get_group_members(group_id, etat=None): return r -def check_group_name(group_name, partition, raiser=False): - """If groupe name exists in partition : if raiser -> Raise ScoValueError else-> return true""" - exists = group_name in [g["group_name"] for g in get_partition_groups(partition)] - if exists: - if raiser: - raise ScoValueError("Le nom de groupe existe déjà dans la partition") - else: - return True - return False - - -# obsolete: sco_groups_view.DisplayedGroupsInfos -# def get_groups_members(group_ids, etat=None): -# """Liste les étudiants d'une liste de groupes -# chaque étudiant n'apparait qu'une seule fois dans le résultat. -# La liste est triée par nom / prenom -# """ -# D = {} # { etudid : etud } -# for group_id in group_ids: -# members = get_group_members(group_id, etat=etat) -# for m in members: -# D[m['etudid']] = m -# r = D.values() -# r.sort(key=operator.itemgetter('nom_disp', 'prenom')) # tri selon nom_usuel ou nom - -# return r - - def get_group_infos(group_id, etat=None): # was _getlisteetud """legacy code: used by group_list and trombino""" from app.scodoc import sco_formsemestre @@ -565,7 +537,7 @@ def XMLgetGroupsInPartition(partition_id): # was XMLgetGroupesTD """ Deprecated: use group_list Liste des étudiants dans chaque groupe de cette partition. - + @@ -588,6 +560,7 @@ def XMLgetGroupsInPartition(partition_id): # was XMLgetGroupesTD "group", partition_id=str(partition_id), partition_name=partition["partition_name"], + groups_editable=str(int(partition["groups_editable"])), group_id=str(group["group_id"]), group_name=group["group_name"], ) @@ -614,6 +587,7 @@ def XMLgetGroupsInPartition(partition_id): # was XMLgetGroupesTD "group", partition_id=str(partition_id), partition_name=partition["partition_name"], + groups_editable=str(int(partition["groups_editable"])), group_id="_none_", group_name="", ) @@ -744,7 +718,7 @@ def setGroups( groupsToCreate="", # name and members of new groups groupsToDelete="", # groups to delete ): - """Affect groups (Ajax request): renvoie du XML + """Affect groups (Ajax POST request): renvoie du XML groupsLists: lignes de la forme "group_id;etudid;...\n" groupsToCreate: lignes "group_name;etudid;...\n" groupsToDelete: group_id;group_id;... @@ -833,10 +807,14 @@ def setGroups( group_name = fs[0].strip() if not group_name: continue - group_id = create_group(partition_id, group_name) + try: + group = create_group(partition_id, group_name) + except ScoValueError as exc: + msg = exc.args[0] if len(exc.args) > 0 else "erreur inconnue" + return xml_error(msg, code=404) # Place dans ce groupe les etudiants indiqués: for etudid in fs[1:-1]: - change_etud_group_in_partition(etudid, group_id, partition) + change_etud_group_in_partition(etudid, group.id, partition) # Update parcours formsemestre = FormSemestre.query.get(formsemestre_id) @@ -850,29 +828,29 @@ def setGroups( return response -def create_group(partition_id, group_name="", default=False) -> int: +def create_group(partition_id, group_name="", default=False) -> GroupDescr: """Create a new group in this partition""" - partition = get_partition(partition_id) - formsemestre_id = partition["formsemestre_id"] - if not sco_permissions_check.can_change_groups(formsemestre_id): + partition = Partition.query.get_or_404(partition_id) + if not sco_permissions_check.can_change_groups(partition.formsemestre_id): raise AccessDenied("Vous n'avez pas le droit d'effectuer cette opération !") # if group_name: group_name = group_name.strip() if not group_name and not default: raise ValueError("invalid group name: ()") - # checkGroupName(group_name) - if check_group_name(group_name, partition): - raise ScoValueError( - f"group_name {group_name} already exists in partition" - ) # XXX FIX: incorrect error handling (in AJAX) - cnx = ndb.GetDBConnexion() - group_id = groupEditor.create( - cnx, {"partition_id": partition_id, "group_name": group_name} + + if not GroupDescr.check_name(partition, group_name): + raise ScoValueError(f"Le groupe {group_name} existe déjà dans cette partition") + + new_numero = ( + max([g.numero if g.numero is not None else 0 for g in partition.groups]) + 1 ) - log("create_group: created group_id={group_id}") + group = GroupDescr(partition=partition, group_name=group_name, numero=new_numero) + db.session.add(group) + db.session.commit() + log("create_group: created group_id={group.id}") # - return group_id + return group def delete_group(group_id, partition_id=None): @@ -1330,40 +1308,43 @@ def partition_set_name(partition_id, partition_name, redirect=1): ) -def group_set_name(group_id, group_name, redirect=True): +def group_set_name(group: GroupDescr, group_name: str, redirect=True): """Set group name""" + if not sco_permissions_check.can_change_groups(group.partition.formsemestre.id): + raise AccessDenied("Vous n'avez pas le droit d'effectuer cette opération !") + if group.group_name is None: + raise ValueError("can't set a name to default group") + destination = url_for( + "scolar.affect_groups", + scodoc_dept=g.scodoc_dept, + partition_id=group.partition_id, + ) if group_name: group_name = group_name.strip() if not group_name: - raise ScoValueError("nom de groupe vide !") - group = get_group(group_id) - if group["group_name"] is None: - raise ValueError("can't set a name to default group") - formsemestre_id = group["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 !") + raise ScoValueError("nom de groupe vide !", dest_url=destination) + if not GroupDescr.check_name(group.partition, group_name): + raise ScoValueError( + "Le nom de groupe existe déjà dans la partition", dest_url=destination + ) + redirect = int(redirect) - cnx = ndb.GetDBConnexion() - groupEditor.edit(cnx, {"group_id": group_id, "group_name": group_name}) - check_group_name(group_name, get_partition(group["partition_id"]), True) + group.group_name = group_name + db.session.add(group) + db.session.commit() + # redirect to partition edit page: if redirect: - return flask.redirect( - url_for( - "scolar.affect_groups", - scodoc_dept=g.scodoc_dept, - partition_id=group["partition_id"], - ) - ) + return flask.redirect(destination) def group_rename(group_id): """Form to rename a group""" - group = get_group(group_id) - formsemestre_id = group["formsemestre_id"] + group = GroupDescr.query.get_or_404(group_id) + formsemestre_id = group.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 !") - H = ["

Renommer un groupe de %s

" % group["partition_name"]] + H = [f"

Renommer un groupe de {group.partition.partition_name or '-'}

"] tf = TrivialFormulator( request.base_url, scu.get_request_args(), @@ -1373,7 +1354,7 @@ def group_rename(group_id): "group_name", { "title": "Nouveau nom", - "default": group["group_name"], + "default": group.group_name, "size": 12, "allow_null": False, "validator": lambda val, _: len(val) < GROUPNAME_STR_LEN, @@ -1396,12 +1377,12 @@ def group_rename(group_id): url_for( "scolar.affect_groups", scodoc_dept=g.scodoc_dept, - partition_id=group["partition_id"], + partition_id=group.partition_id, ) ) else: # form submission - return group_set_name(group_id, tf[2]["group_name"]) + return group_set_name(group, tf[2]["group_name"]) def groups_auto_repartition(partition_id=None): @@ -1473,12 +1454,7 @@ def groups_auto_repartition(partition_id=None): # Crée les nouveaux groupes group_ids = [] for group_name in group_names: - # try: - # checkGroupName(group_name) - # except: - # H.append('

Nom de groupe invalide: %s

'%group_name) - # return '\n'.join(H) + tf[1] + html_sco_header.sco_footer() - group_ids.append(create_group(partition_id, group_name)) + group_ids.append(create_group(partition_id, group_name).id) # nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre) identdict = nt.identdict @@ -1562,8 +1538,8 @@ def create_etapes_partition(formsemestre_id, partition_name="apo_etapes"): groups_by_names = {g["group_name"]: g for g in groups} for etape in etapes: if not (etape in groups_by_names): - gid = create_group(pid, etape) - g = get_group(gid) + new_group = create_group(pid, etape) + g = get_group(new_group.id) # XXX transition: recupere old style dict groups_by_names[etape] = g # Place les etudiants dans les groupes for i in ins: diff --git a/app/scodoc/sco_groups_copy.py b/app/scodoc/sco_groups_copy.py index 77d6e1d02..d5fc14fc9 100644 --- a/app/scodoc/sco_groups_copy.py +++ b/app/scodoc/sco_groups_copy.py @@ -35,15 +35,15 @@ def clone_partitions_and_groups( for (new_partition_id, list_groups) in list_groups_per_part: if newpart["partition_id"] == new_partition_id: for group in list_groups: - new_group_id = sco_groups.create_group( + new_group = sco_groups.create_group( new_partition_id, group_name=group["group_name"] ) - groups_old2new[group["group_id"]] = new_group_id + groups_old2new[group["group_id"]] = new_group.id # if inscrit_etuds: cnx = ndb.GetDBConnexion() cursor = cnx.cursor() - for old_group_id, new_group_id in groups_old2new.items(): + for old_group_id, new_group.id in groups_old2new.items(): cursor.execute( """ WITH etuds AS ( @@ -60,7 +60,7 @@ def clone_partitions_and_groups( { "orig_formsemestre_id": orig_formsemestre_id, "old_group_id": old_group_id, - "new_group_id": new_group_id, + "new_group_id": new_group.id, }, ) cnx.commit() diff --git a/app/static/icons/scologo_img.png b/app/static/icons/scologo_img.png index 06746f952888727d60d600bcb7860c9da807cbe0..04a2c5c1c38370c753153303c581bfda51badbed 100644 GIT binary patch literal 1712 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 z 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�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 m8NYv~?_YuUufTin3jBYr*JFkTHAL=@qw diff --git a/app/static/js/groupmgr.js b/app/static/js/groupmgr.js index 690b7f522..fa7d66bad 100644 --- a/app/static/js/groupmgr.js +++ b/app/static/js/groupmgr.js @@ -44,10 +44,10 @@ function loadGroupes() { 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); + var gbox = new CGroupBox(group_id, group_name, groups_editable); var etuds = node.getElementsByTagName('etud'); var x = ''; gbox.sorting = false; // disable to speedup @@ -72,7 +72,7 @@ function populateGroup(node) { 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) { +var CGroupBox = function (group_id, group_name, groups_editable) { group_id = $.trim(group_id); var regex = /^\w+$/; if (!regex.test(group_id)) { @@ -86,6 +86,7 @@ var CGroupBox = function (group_id, group_name) { 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 @@ -131,10 +132,10 @@ var CGroupBox = function (group_id, group_name) { $.extend(CGroupBox.prototype, { // menu for group title groupTitle: function () { - var menuSpan = document.createElement("span"); + let menuSpan = document.createElement("span"); menuSpan.className = "barrenav"; - var h = "
  • menu
      "; - if (this.group_id != '_none_') { + let h = "
      • menu
          "; + if (this.groups_editable && (this.group_id != '_none_')) { h += "
        • Supprimer
        • "; h += "
        • Renommer
        • "; } @@ -207,7 +208,7 @@ 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'); + 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; @@ -271,7 +272,7 @@ function createGroup() { } var group_id = newGroupId(); groups_unsaved = true; - var gbox = new CGroupBox(group_id, group_name); + var gbox = new CGroupBox(group_id, group_name, true); gbox.isNew = true; gbox.updateTitle(); return true; @@ -387,8 +388,8 @@ function submitGroups() { groupsToDelete = new Object(); // empty var partition_id = document.formGroup.partition_id.value; // Send to server - $.get(url, { - groupsLists: groupsLists, // encodeURIComponent + $.post(url, { + groupsLists: groupsLists, partition_id: partition_id, groupsToDelete: todel, groupsToCreate: groupsToCreate @@ -396,8 +397,12 @@ function submitGroups() { .done(function (data) { processResponse(data); }) - .fail(function () { - handleError("Erreur lors de l'enregistrement de groupes"); + .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); }); } diff --git a/app/templates/scolar/affect_groups.html b/app/templates/scolar/affect_groups.html index 1f1dd908b..86f925af9 100644 --- a/app/templates/scolar/affect_groups.html +++ b/app/templates/scolar/affect_groups.html @@ -4,8 +4,8 @@

          Faites glisser les étudiants d'un groupe à l'autre. Les modifications ne sont enregistrées que lorsque vous cliquez sur le bouton "Enregistrer ces groupes". -Vous pouvez créer de nouveaux groupes. Pour supprimer un groupe, utiliser le lien -"suppr." en haut à droite de sa boite. +Vous pouvez créer de nouveaux groupes. Pour supprimer ou renommer +un groupe, utiliser le menu en haut à droite de sa boite. Vous pouvez aussi répartir automatiquement les groupes. diff --git a/app/views/scolar.py b/app/views/scolar.py index e1f3da345..f558c0ed9 100644 --- a/app/views/scolar.py +++ b/app/views/scolar.py @@ -827,27 +827,8 @@ sco_publish( Permission.ScoView, ) -sco_publish("/setGroups", sco_groups.setGroups, Permission.ScoView) +sco_publish("/setGroups", sco_groups.setGroups, Permission.ScoView, methods=["POST"]) -sco_publish("/create_group", sco_groups.create_group, Permission.ScoView) - - -@bp.route("/suppressGroup") # backward compat (ScoDoc7 API) -@bp.route("/delete_group") -@scodoc -@permission_required(Permission.ScoView) -@scodoc7func -def delete_group(group_id, partition_id): - sco_groups.delete_group(group_id=group_id, partition_id=partition_id) - return "", 204 - - -sco_publish( - "/group_set_name", - sco_groups.group_set_name, - Permission.ScoView, - methods=["GET", "POST"], -) sco_publish( "/group_rename", diff --git a/tools/fakedatabase/create_test_api_database.py b/tools/fakedatabase/create_test_api_database.py index bd4bb0919..6b345b435 100644 --- a/tools/fakedatabase/create_test_api_database.py +++ b/tools/fakedatabase/create_test_api_database.py @@ -209,7 +209,7 @@ def create_formsemestre( partition_id = sco_groups.partition_create( formsemestre.id, default=True, redirect=False ) - _group_id = sco_groups.create_group(partition_id, default=True) + group = sco_groups.create_group(partition_id, default=True) return formsemestre