From e963ca52f5d91a40f781deb260c66da7d935dd6f Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Tue, 4 Jul 2023 08:23:09 +0200 Subject: [PATCH 01/13] =?UTF-8?q?Fix:=20=C3=A9dition=20validations=20ant?= =?UTF-8?q?=C3=A9rieures=20lorsqu'il=20y=20a=20des=20validations=20sans=20?= =?UTF-8?q?semestres?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/but/jury_but.py | 2 +- app/scodoc/sco_formsemestre_validation.py | 7 +++++-- app/scodoc/sco_semset.py | 2 +- sco_version.py | 2 +- 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/app/but/jury_but.py b/app/but/jury_but.py index 1ec6eeeafe..02f18dffa1 100644 --- a/app/but/jury_but.py +++ b/app/but/jury_but.py @@ -355,7 +355,7 @@ class DecisionsProposeesAnnee(DecisionsProposees): # Reste à attribuer ADM, ADJ, PASD, PAS1NCI, RED, NAR plural = self.nb_validables > 1 explanation += f"""{self.nb_validables} niveau{"x" if plural else ""} validable{ - "s" if plural else ""} sur {self.nb_competences}""" + "s" if plural else ""} de droit sur {self.nb_competences}""" if self.admis: self.codes = [sco_codes.ADM] + self.codes # elif not self.jury_annuel: diff --git a/app/scodoc/sco_formsemestre_validation.py b/app/scodoc/sco_formsemestre_validation.py index f8ce984713..6460fb4f9c 100644 --- a/app/scodoc/sco_formsemestre_validation.py +++ b/app/scodoc/sco_formsemestre_validation.py @@ -31,6 +31,7 @@ import time import flask from flask import url_for, flash, g, request +from flask_login import current_user import sqlalchemy as sa from app.models.etudiants import Identite @@ -66,7 +67,7 @@ from app.scodoc.sco_cursus_dut import etud_est_inscrit_ue from app.scodoc import sco_photos from app.scodoc import sco_preferences from app.scodoc import sco_pv_dict - +from app.scodoc.sco_permissions import Permission # ------------------------------------------------------------------------------------ def formsemestre_validation_etud_form( @@ -1288,7 +1289,9 @@ def _get_etud_ue_cap_html(etud: Identite, formsemestre: FormSemestre) -> str: if validation.semestre_id is not None: origine += f" (S{validation.semestre_id})" H.append(f"""
  • {validation.html()}""") - if validation.formsemestre.can_edit_jury(): + if (validation.formsemestre and validation.formsemestre.can_edit_jury()) or ( + current_user and current_user.has_permission(Permission.ScoEtudInscrit) + ): H.append( f"""
    diff --git a/app/scodoc/sco_semset.py b/app/scodoc/sco_semset.py index f3d8c6d3a5..251d0468ba 100644 --- a/app/scodoc/sco_semset.py +++ b/app/scodoc/sco_semset.py @@ -378,7 +378,7 @@ class SemSet(dict): def html_diagnostic(self): """Affichage de la partie Effectifs et Liste des étudiants - (actif seulement si un portail est configuré) + (actif seulement si un portail est configuré) XXX pourquoi ?? """ if sco_portal_apogee.has_portal(): return self.bilan.html_diagnostic() diff --git a/sco_version.py b/sco_version.py index 832f5c16dc..0bd507e8e5 100644 --- a/sco_version.py +++ b/sco_version.py @@ -1,7 +1,7 @@ # -*- mode: python -*- # -*- coding: utf-8 -*- -SCOVERSION = "9.4.96" +SCOVERSION = "9.4.97" SCONAME = "ScoDoc" From cf0d3c06c41a5a0c5cc11720f17edf16aaea5d40 Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Tue, 4 Jul 2023 22:54:55 +0200 Subject: [PATCH 02/13] PDF: substitute - for HYPHEN (U+2010) --- app/scodoc/sco_pdf.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/scodoc/sco_pdf.py b/app/scodoc/sco_pdf.py index ffa11d2594..841f8b3fa5 100755 --- a/app/scodoc/sco_pdf.py +++ b/app/scodoc/sco_pdf.py @@ -84,6 +84,8 @@ def SU(s: str) -> str: s = html.unescape(s) # Remplace les
    par des
    s = re.sub(r"", "
    ", s) + # And substitute unicode characters not supported by ReportLab + s = s.replace("‐", "-") return s From a2c5be22cb154889f7660deed06cc41ef83a7bda Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Tue, 4 Jul 2023 23:26:14 +0200 Subject: [PATCH 03/13] =?UTF-8?q?Fix:=20enregistre=20moyenne=20des=20UEs?= =?UTF-8?q?=20ant=C3=A9rieures=20non=20ADM?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/scodoc/sco_cursus_dut.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/app/scodoc/sco_cursus_dut.py b/app/scodoc/sco_cursus_dut.py index 9eab3da6b7..007c602c5d 100644 --- a/app/scodoc/sco_cursus_dut.py +++ b/app/scodoc/sco_cursus_dut.py @@ -949,6 +949,7 @@ def do_formsemestre_validate_ue( "ue_id": ue_id, "semestre_id": semestre_id, "is_external": is_external, + "moy_ue": moy_ue, } if date: args["event_date"] = date @@ -965,12 +966,11 @@ def do_formsemestre_validate_ue( cursor.execute("delete from scolar_formsemestre_validation where " + cond, args) # insert args["code"] = code - if code == ADM: - if moy_ue is None: - # stocke la moyenne d'UE capitalisée: - ue_status = nt.get_etud_ue_status(etudid, ue_id) - moy_ue = ue_status["moy"] if ue_status else "" - args["moy_ue"] = moy_ue + if (code == ADM) and (moy_ue is None): + # stocke la moyenne d'UE capitalisée: + ue_status = nt.get_etud_ue_status(etudid, ue_id) + moy_ue = ue_status["moy"] if ue_status else "" + log("formsemestre_validate_ue: create %s" % args) if code is not None: scolar_formsemestre_validation_create(cnx, args) From 9b3df5febf5846955436c8a51a76dc7bbf6b41a5 Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Tue, 4 Jul 2023 23:31:44 +0200 Subject: [PATCH 04/13] Fix: creation semestre --- app/scodoc/sco_formsemestre_edit.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/scodoc/sco_formsemestre_edit.py b/app/scodoc/sco_formsemestre_edit.py index 67410b5c14..317e4936ce 100644 --- a/app/scodoc/sco_formsemestre_edit.py +++ b/app/scodoc/sco_formsemestre_edit.py @@ -797,7 +797,7 @@ def do_formsemestre_createwithmodules(edit=False, formsemestre: FormSemestre = N url_for( "notes.formsemestre_status", scodoc_dept=g.scodoc_dept, - formsemestre_id=formsemestre.id, + formsemestre_id=formsemestre.id if formsemestre else formsemestre_id, ) ) else: From 90c1454b2172dc6dfd95b34d39e623bde891dcad Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Tue, 4 Jul 2023 23:33:31 +0200 Subject: [PATCH 05/13] =?UTF-8?q?Robustifie=20code=20effacement=20d=C3=A9c?= =?UTF-8?q?ision?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/but/jury_but.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/app/but/jury_but.py b/app/but/jury_but.py index 02f18dffa1..fc4faf20e1 100644 --- a/app/but/jury_but.py +++ b/app/but/jury_but.py @@ -865,11 +865,16 @@ class DecisionsProposeesAnnee(DecisionsProposees): self.etud.id, self.formsemestre.id ) for dec_ue in self.decisions_ues.values(): - if dec_ue.formsemestre.id == self.formsemestre.id: + if ( + dec_ue + and self.formsemestre + and dec_ue.formsemestre.id == self.formsemestre.id + ): dec_ue.erase() else: for dec_ue in self.decisions_ues.values(): - dec_ue.erase() + if dec_ue: + dec_ue.erase() if self.formsemestre_impair: ScolarAutorisationInscription.delete_autorisation_etud( From 0824598aa4e7550b724fe5849bda1c01b9706a29 Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Wed, 5 Jul 2023 19:15:33 +0200 Subject: [PATCH 06/13] =?UTF-8?q?D=C3=A9but=20de=20travaux=20pour=20am?= =?UTF-8?q?=C3=A9liorer=20le=20backend=20groupes/partitions.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/api/partitions.py | 12 ++-- app/models/groups.py | 36 +++++++++++ app/scodoc/sco_edit_ue.py | 2 + app/scodoc/sco_groups.py | 104 ++++++++++++-------------------- app/scodoc/sco_import_etuds.py | 2 +- app/scodoc/sco_inscr_passage.py | 11 ++-- sco_version.py | 2 +- 7 files changed, 91 insertions(+), 78 deletions(-) diff --git a/app/api/partitions.py b/app/api/partitions.py index 1f5ef8a1e1..4d752b65a4 100644 --- a/app/api/partitions.py +++ b/app/api/partitions.py @@ -12,6 +12,7 @@ from operator import attrgetter from flask import g, request from flask_json import as_json from flask_login import login_required +from sqlalchemy.exc import IntegrityError import app from app import db, log @@ -23,6 +24,7 @@ from app.models import GroupDescr, Partition, Scolog from app.models.groups import group_membership from app.scodoc import sco_cache from app.scodoc import sco_groups +from app.scodoc.sco_exceptions import ScoValueError from app.scodoc.sco_permissions import Permission from app.scodoc import sco_utils as scu @@ -182,10 +184,12 @@ def set_etud_group(etudid: int, group_id: int): if etud.id not in {e.id for e in group.partition.formsemestre.etuds}: return json_error(404, "etud non inscrit au formsemestre du groupe") - sco_groups.change_etud_group_in_partition( - etudid, group_id, group.partition.to_dict() - ) - + try: + sco_groups.change_etud_group_in_partition(etudid, group) + except ScoValueError as exc: + return json_error(404, exc.args[0]) + except IntegrityError: + return json_error(404, "échec de l'enregistrement") return {"group_id": group_id, "etudid": etudid} diff --git a/app/models/groups.py b/app/models/groups.py index 1d07ed2147..35d22f8e9b 100644 --- a/app/models/groups.py +++ b/app/models/groups.py @@ -8,11 +8,13 @@ """ScoDoc models: Groups & partitions """ from operator import attrgetter +from sqlalchemy.exc import IntegrityError from app import db from app.models import SHORT_STR_LEN from app.models import GROUPNAME_STR_LEN from app.scodoc import sco_utils as scu +from app.scodoc.sco_exceptions import ScoValueError class Partition(db.Model): @@ -117,6 +119,40 @@ class Partition(db.Model): .first() ) + def set_etud_group(self, etudid: int, group: "GroupDescr"): + """Affect etudid to group_id in given partition. + Raises IntegrityError si conflit, + or ValueError si ce group_id n'est pas dans cette partition + ou que l'étudiant n'est pas inscrit au semestre. + """ + if not group.id in (g.id for g in self.groups): + raise ScoValueError( + f"""Le groupe {group.id} n'est pas dans la partition {self.partition_name or "tous"}""" + ) + if etudid not in (e.id for e in self.formsemestre.etuds): + raise ScoValueError( + f"etudiant {etudid} non inscrit au formsemestre du groupe {group.id}" + ) + try: + existing_row = ( + db.session.query(group_membership) + .filter_by(etudid=etudid) + .join(GroupDescr) + .filter_by(partition_id=self.id) + .first() + ) + if existing_row: + existing_row.update({"group_id": group.id}) + else: + new_row = group_membership.insert().values( + etudid=etudid, group_id=group.id + ) + db.session.execute(new_row) + db.session.commit() + except IntegrityError: + db.session.rollback() + raise + class GroupDescr(db.Model): """Description d'un groupe d'une partition""" diff --git a/app/scodoc/sco_edit_ue.py b/app/scodoc/sco_edit_ue.py index 90f01f04ee..4d7aa131b3 100644 --- a/app/scodoc/sco_edit_ue.py +++ b/app/scodoc/sco_edit_ue.py @@ -546,6 +546,8 @@ def ue_edit(ue_id=None, create=False, formation_id=None, default_semestre_idx=No ue = UniteEns.query.get(ue_id) flash(f"UE créée (code {ue.ue_code})") else: + if not tf[2]["numero"]: + tf[2]["numero"] = 0 do_ue_edit(tf[2]) flash("UE modifiée") diff --git a/app/scodoc/sco_groups.py b/app/scodoc/sco_groups.py index 8b1da28ad3..b02fd3379b 100644 --- a/app/scodoc/sco_groups.py +++ b/app/scodoc/sco_groups.py @@ -48,9 +48,9 @@ from sqlalchemy.sql import text from app import db from app.comp import res_sem from app.comp.res_compat import NotesTableCompat -from app.models import FormSemestre, Identite +from app.models import FormSemestre, Identite, Scolog from app.models import GROUPNAME_STR_LEN, SHORT_STR_LEN -from app.models.groups import GroupDescr, Partition +from app.models.groups import GroupDescr, Partition, group_membership import app.scodoc.sco_utils as scu import app.scodoc.notesdb as ndb from app import log, cache @@ -94,7 +94,7 @@ groupEditor = ndb.EditableTable( group_list = groupEditor.list -def get_group(group_id: int) -> dict: +def get_group(group_id: int) -> dict: # OBSOLETE ! """Returns group object, with partition""" r = ndb.SimpleDictFetch( """SELECT gd.id AS group_id, gd.*, p.id AS partition_id, p.* @@ -124,7 +124,7 @@ def group_delete(group_id: int): ) -def get_partition(partition_id): +def get_partition(partition_id): # OBSOLETE r = ndb.SimpleDictFetch( """SELECT p.id AS partition_id, p.* FROM partition p @@ -200,7 +200,7 @@ def get_formsemestre_etuds_groups(formsemestre_id: int) -> dict: return d -def get_partition_groups(partition): +def get_partition_groups(partition): # OBSOLETE ! """List of groups in this partition (list of dicts). Some groups may be empty.""" return ndb.SimpleDictFetch( @@ -637,7 +637,7 @@ def _comp_etud_origin(etud: dict, cur_formsemestre: FormSemestre): return "" # parcours normal, ne le signale pas -def set_group(etudid: int, group_id: int) -> bool: +def set_group(etudid: int, group_id: int) -> bool: # OBSOLETE ! """Inscrit l'étudiant au groupe. Return True if ok, False si deja inscrit. Warning: @@ -664,55 +664,31 @@ def set_group(etudid: int, group_id: int) -> bool: return True -def change_etud_group_in_partition(etudid: int, group_id: int, partition: dict = None): - """Inscrit etud au groupe de cette partition, - et le desinscrit d'autres groupes de cette partition. +def change_etud_group_in_partition(etudid: int, group: GroupDescr): + """Inscrit etud au groupe + (et le desinscrit d'autres groupes de cette partition.) """ - log("change_etud_group_in_partition: etudid=%s group_id=%s" % (etudid, group_id)) - # 0- La partition - group = get_group(group_id) - if partition: - # verifie que le groupe est bien dans cette partition: - if group["partition_id"] != partition["partition_id"]: - raise ValueError( - "inconsistent group/partition (group_id=%s, partition_id=%s)" - % (group_id, partition["partition_id"]) - ) - else: - partition = get_partition(group["partition_id"]) - # 1- Supprime membership dans cette partition - ndb.SimpleQuery( - """DELETE FROM group_membership gm - WHERE EXISTS - (SELECT 1 FROM group_descr gd - WHERE gm.etudid = %(etudid)s - AND gm.group_id = gd.id - AND gd.partition_id = %(partition_id)s) - """, - {"etudid": etudid, "partition_id": partition["partition_id"]}, - ) - # 2- associe au nouveau groupe - set_group(etudid, group_id) + log(f"change_etud_group_in_partition: etudid={etudid} group={group}") - # 3- log - formsemestre_id = partition["formsemestre_id"] - cnx = ndb.GetDBConnexion() - logdb( - cnx, + group.partition.set_etud_group(etudid, group) + + # - log + formsemestre: FormSemestre = group.partition.formsemestre + Scolog.logdb( method="changeGroup", etudid=etudid, - msg="formsemestre_id=%s,partition_name=%s, group_name=%s" - % (formsemestre_id, partition["partition_name"], group["group_name"]), + msg=f"""formsemestre_id={formsemestre.id}, partition_name={ + group.partition.partition_name or ""}, group_name={group.group_name or ""}""", + commit=True, ) - cnx.commit() - # 5- Update parcours - formsemestre: FormSemestre = FormSemestre.query.get(formsemestre_id) - formsemestre.update_inscriptions_parcours_from_groups() + # - Update parcours + if group.partition.partition_name == scu.PARTITION_PARCOURS: + formsemestre.update_inscriptions_parcours_from_groups() - # 6- invalidate cache + # - invalidate cache sco_cache.invalidate_formsemestre( - formsemestre_id=formsemestre_id + formsemestre_id=formsemestre.id ) # > change etud group @@ -769,7 +745,7 @@ def setGroups( except ValueError: log(f"setGroups: ignoring invalid group_id={group_id}") continue - group = get_group(group_id) + group: GroupDescr = GroupDescr.query.get(group_id) # Anciens membres du groupe: old_members = get_group_members(group_id) old_members_set = set([x["etudid"] for x in old_members]) @@ -783,7 +759,7 @@ def setGroups( if (etudid not in etud_groups) or ( group_id != etud_groups[etudid].get(partition_id, "") ): # pas le meme groupe qu'actuel - change_etud_group_in_partition(etudid, group_id, partition) + change_etud_group_in_partition(etudid, group) # Retire les anciens membres: cnx = ndb.GetDBConnexion() cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor) @@ -819,7 +795,7 @@ def setGroups( 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) # Update parcours formsemestre.update_inscriptions_parcours_from_groups() @@ -1460,10 +1436,10 @@ def groups_auto_repartition(partition_id=None): for old_group in get_partition_groups(partition): group_delete(old_group["group_id"]) # Crée les nouveaux groupes - group_ids = [] + groups = [] for group_name in group_names: if group_name.strip(): - group_ids.append(create_group(partition_id, group_name).id) + groups.append(create_group(partition_id, group_name)) # nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre) identdict = nt.identdict @@ -1481,16 +1457,16 @@ def groups_auto_repartition(partition_id=None): # affect aux groupes: n = len(identdict) igroup = 0 - nbgroups = len(group_ids) + nbgroups = len(groups) while n > 0: for civilite in civilites: if len(listes[civilite]): n -= 1 etudid = listes[civilite].pop()[1] - group_id = group_ids[igroup] + group = groups[igroup] igroup = (igroup + 1) % nbgroups - change_etud_group_in_partition(etudid, group_id, partition) - log("%s in group %s" % (etudid, group_id)) + change_etud_group_in_partition(etudid, group) + log("%s in group %s" % (etudid, group.id)) return flask.redirect(dest_url) @@ -1520,10 +1496,11 @@ def create_etapes_partition(formsemestre_id, partition_name="apo_etapes"): Si la partition existe déjà, ses groupes sont mis à jour (les groupes devenant vides ne sont pas supprimés). """ + # A RE-ECRIRE pour utiliser les modèles. from app.scodoc import sco_formsemestre_inscriptions partition_name = str(partition_name) - log("create_etapes_partition(%s)" % formsemestre_id) + log(f"create_etapes_partition({formsemestre_id})") ins = sco_formsemestre_inscriptions.do_formsemestre_inscription_list( args={"formsemestre_id": formsemestre_id} ) @@ -1542,20 +1519,17 @@ def create_etapes_partition(formsemestre_id, partition_name="apo_etapes"): pid = partition_create( formsemestre_id, partition_name=partition_name, redirect=False ) - partition = get_partition(pid) - groups = get_partition_groups(partition) + partition: Partition = Partition.query.get(pid) + groups = partition.groups groups_by_names = {g["group_name"]: g for g in groups} for etape in etapes: - if not (etape in groups_by_names): + if etape not in groups_by_names: new_group = create_group(pid, etape) - g = get_group(new_group.id) # XXX transition: recupere old style dict - groups_by_names[etape] = g + groups_by_names[etape] = new_group # Place les etudiants dans les groupes for i in ins: if i["etape"]: - change_etud_group_in_partition( - i["etudid"], groups_by_names[i["etape"]]["group_id"], partition - ) + change_etud_group_in_partition(i["etudid"], groups_by_names[i["etape"]]) def do_evaluation_listeetuds_groups( diff --git a/app/scodoc/sco_import_etuds.py b/app/scodoc/sco_import_etuds.py index e7eb8dceb3..5bf61fdaed 100644 --- a/app/scodoc/sco_import_etuds.py +++ b/app/scodoc/sco_import_etuds.py @@ -723,7 +723,7 @@ def scolars_import_admission(datafile, formsemestre_id=None, type_admission=None group = GroupDescr.query.get(group_id) if group.partition.groups_editable: sco_groups.change_etud_group_in_partition( - args["etudid"], group_id + args["etudid"], group ) else: log("scolars_import_admission: partition non editable") diff --git a/app/scodoc/sco_inscr_passage.py b/app/scodoc/sco_inscr_passage.py index 1ebf679fb1..50260e2704 100644 --- a/app/scodoc/sco_inscr_passage.py +++ b/app/scodoc/sco_inscr_passage.py @@ -36,13 +36,12 @@ from flask import url_for, g, request import app.scodoc.notesdb as ndb import app.scodoc.sco_utils as scu from app import log -from app.models import Formation, FormSemestre +from app.models import Formation, FormSemestre, GroupDescr from app.scodoc.gen_tables import GenTable from app.scodoc import html_sco_header from app.scodoc import sco_cache from app.scodoc import codes_cursus from app.scodoc import sco_etud -from app.scodoc import sco_formations from app.scodoc import sco_formsemestre from app.scodoc import sco_formsemestre_inscriptions from app.scodoc import sco_groups @@ -177,6 +176,7 @@ def do_inscrit(sem, etudids, inscrit_groupes=False): (la liste doit avoir été vérifiée au préalable) En option: inscrit aux mêmes groupes que dans le semestre origine """ + # TODO à ré-écrire pour utiliser le smodèle, notamment GroupDescr formsemestre: FormSemestre = FormSemestre.query.get(sem["formsemestre_id"]) formsemestre.setup_parcours_groups() log(f"do_inscrit (inscrit_groupes={inscrit_groupes}): {etudids}") @@ -220,11 +220,8 @@ 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, - ) + group: GroupDescr = GroupDescr.query.get(partition_group["group_id"]) + sco_groups.change_etud_group_in_partition(etudid, group) def do_desinscrit(sem, etudids): diff --git a/sco_version.py b/sco_version.py index 0bd507e8e5..95fb08eb18 100644 --- a/sco_version.py +++ b/sco_version.py @@ -1,7 +1,7 @@ # -*- mode: python -*- # -*- coding: utf-8 -*- -SCOVERSION = "9.4.97" +SCOVERSION = "9.4.98" SCONAME = "ScoDoc" From c5a702e6d156ca49687181b305574825ade896c2 Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Wed, 5 Jul 2023 19:18:41 +0200 Subject: [PATCH 07/13] Fix: creation semestre-> renvoi sur annulation --- app/scodoc/sco_formsemestre_edit.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/app/scodoc/sco_formsemestre_edit.py b/app/scodoc/sco_formsemestre_edit.py index 317e4936ce..9c8466ed21 100644 --- a/app/scodoc/sco_formsemestre_edit.py +++ b/app/scodoc/sco_formsemestre_edit.py @@ -793,13 +793,16 @@ def do_formsemestre_createwithmodules(edit=False, formsemestre: FormSemestre = N {tf[1]} """ elif tf[0] == -1: - return redirect( - url_for( - "notes.formsemestre_status", - scodoc_dept=g.scodoc_dept, - formsemestre_id=formsemestre.id if formsemestre else formsemestre_id, + if formsemestre: + return redirect( + url_for( + "notes.formsemestre_status", + scodoc_dept=g.scodoc_dept, + formsemestre_id=formsemestre.id, + ) ) - ) + else: + return redirect(url_for("notes.index_html", scodoc_dept=g.scodoc_dept)) else: if tf[2]["gestion_compensation_lst"]: tf[2]["gestion_compensation"] = True From 428a34e6ba2def904a20126be97f533622f5eeed Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Thu, 6 Jul 2023 09:57:51 +0200 Subject: [PATCH 08/13] =?UTF-8?q?Am=C3=A9liore=20traitement=20erreur=20si?= =?UTF-8?q?=20id=20invalide=20envoy=C3=A9=20=C3=A0=20formsemestre=5Fvalida?= =?UTF-8?q?tion=5Fbut?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/views/notes.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/views/notes.py b/app/views/notes.py index 4bc79ebae8..4ee25a900f 100644 --- a/app/views/notes.py +++ b/app/views/notes.py @@ -2339,14 +2339,14 @@ def formsemestre_validation_but( formsemestre: FormSemestre = FormSemestre.query.filter_by( id=formsemestre_id, dept_id=g.scodoc_dept_id ).first_or_404() - etud = Identite.get_etud(etudid) - nb_etuds = formsemestre.etuds.count() # la route ne donne pas le type d'etudid pour pouvoir construire des URLs # provisoires avec NEXT et PREV try: etudid = int(etudid) - except ValueError: - abort(404, "invalid etudid") + except ValueError as exc: + raise ScoValueError("adresse invalide") from exc + etud = Identite.get_etud(etudid) + nb_etuds = formsemestre.etuds.count() read_only = not formsemestre.can_edit_jury() # --- Navigation From 6c56b921e8e8daddaa9888277bd5ccd7a9796541 Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Thu, 6 Jul 2023 12:08:00 +0200 Subject: [PATCH 09/13] =?UTF-8?q?Bonus=20La=20Roche-sur-Yon:=20modificatio?= =?UTF-8?q?n=20de=20la=20r=C3=A8gle.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/comp/bonus_spo.py | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/app/comp/bonus_spo.py b/app/comp/bonus_spo.py index d3e8db03e9..b5b4e966bc 100644 --- a/app/comp/bonus_spo.py +++ b/app/comp/bonus_spo.py @@ -827,16 +827,32 @@ class BonusStMalo(BonusIUTRennes1): class BonusLaRocheSurYon(BonusSportAdditif): """Bonus IUT de La Roche-sur-Yon - Si une note de bonus est saisie, l'étudiant est gratifié de 0,2 points - sur sa moyenne générale ou, en BUT, sur la moyenne de chaque UE. +

    + La note saisie s'applique directement: si on saisit 0,2, un bonus de 0,2 points est appliqué + aux moyennes. + La valeur maximale du bonus est 1 point. Il est appliqué sur les moyennes d'UEs en BUT, + ou sur la moyenne générale dans les autres formations. +

    +

    Pour les semestres antérieurs à janvier 2023: si une note de bonus est saisie, + l'étudiant est gratifié de 0,2 points sur sa moyenne générale ou, en BUT, sur la + moyenne de chaque UE. +

    """ name = "bonus_larochesuryon" displayed_name = "IUT de La Roche-sur-Yon" seuil_moy_gen = 0.0 seuil_comptage = 0.0 - proportion_point = 1e10 # le moindre point sature le bonus - bonus_max = 0.2 # à 0.2 + + def compute_bonus(self, sem_modimpl_moys_inscrits, modimpl_coefs_etuds_no_nan): + """calcul du bonus, avec réglage différent suivant la date""" + if self.formsemestre.date_debut > datetime.date(2022, 12, 31): + self.proportion_point = 1.0 + self.bonus_max = 1 + else: # ancienne règle + self.proportion_point = 1e10 # le moindre point sature le bonus + self.bonus_max = 0.2 # à 0.2 + super().compute_bonus(sem_modimpl_moys_inscrits, modimpl_coefs_etuds_no_nan) class BonusLaRochelle(BonusSportAdditif): From 41a791282a6f91222b6c77118bc1091960a03f26 Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Thu, 6 Jul 2023 16:24:52 +0200 Subject: [PATCH 10/13] =?UTF-8?q?Fix:=20jury=20formation=20classique=20en?= =?UTF-8?q?=20cas=20de=20d=C3=A9faillance?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/comp/res_common.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/comp/res_common.py b/app/comp/res_common.py index e59172170a..54fd0de039 100644 --- a/app/comp/res_common.py +++ b/app/comp/res_common.py @@ -381,7 +381,11 @@ class ResultatsSemestre(ResultatsCache): was_capitalized = False if etudid in self.validations.ue_capitalisees.index: ue_cap = self._get_etud_ue_cap(etudid, ue) - if ue_cap and not np.isnan(ue_cap["moy_ue"]): + if ( + ue_cap + and (ue_cap["moy_ue"] is not None) + and not np.isnan(ue_cap["moy_ue"]) + ): was_capitalized = True if ue_cap["moy_ue"] > cur_moy_ue or np.isnan(cur_moy_ue): moy_ue = ue_cap["moy_ue"] From d88e41b83efa936088de3d65d2581e8f778edabc Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Sat, 8 Jul 2023 13:57:44 +0200 Subject: [PATCH 11/13] Fix change group/import etud admission --- app/models/groups.py | 17 +++++++++++++++-- app/scodoc/sco_groups.py | 11 ++++++----- app/scodoc/sco_import_etuds.py | 21 ++++++++++----------- sco_version.py | 2 +- 4 files changed, 32 insertions(+), 19 deletions(-) diff --git a/app/models/groups.py b/app/models/groups.py index 35d22f8e9b..dce377da8b 100644 --- a/app/models/groups.py +++ b/app/models/groups.py @@ -119,11 +119,12 @@ class Partition(db.Model): .first() ) - def set_etud_group(self, etudid: int, group: "GroupDescr"): + def set_etud_group(self, etudid: int, group: "GroupDescr") -> bool: """Affect etudid to group_id in given partition. Raises IntegrityError si conflit, or ValueError si ce group_id n'est pas dans cette partition ou que l'étudiant n'est pas inscrit au semestre. + Return True si changement, False s'il était déjà dans ce groupe. """ if not group.id in (g.id for g in self.groups): raise ScoValueError( @@ -141,8 +142,19 @@ class Partition(db.Model): .filter_by(partition_id=self.id) .first() ) + existing_group_id = existing_row[1] if existing_row: - existing_row.update({"group_id": group.id}) + if group.id == existing_group_id: + return False + update_row = ( + group_membership.update() + .where( + group_membership.c.etudid == etudid, + group_membership.c.group_id == existing_group_id, + ) + .values(group_id=group.id) + ) + db.session.execute(update_row) else: new_row = group_membership.insert().values( etudid=etudid, group_id=group.id @@ -152,6 +164,7 @@ class Partition(db.Model): except IntegrityError: db.session.rollback() raise + return True class GroupDescr(db.Model): diff --git a/app/scodoc/sco_groups.py b/app/scodoc/sco_groups.py index b02fd3379b..8438ba09c7 100644 --- a/app/scodoc/sco_groups.py +++ b/app/scodoc/sco_groups.py @@ -664,16 +664,17 @@ def set_group(etudid: int, group_id: int) -> bool: # OBSOLETE ! return True -def change_etud_group_in_partition(etudid: int, group: GroupDescr): +def change_etud_group_in_partition(etudid: int, group: GroupDescr) -> bool: """Inscrit etud au groupe - (et le desinscrit d'autres groupes de cette partition.) + (et le désinscrit d'autres groupes de cette partition) + Return True si changement, False s'il était déjà dans ce groupe. """ - log(f"change_etud_group_in_partition: etudid={etudid} group={group}") - - group.partition.set_etud_group(etudid, group) + if not group.partition.set_etud_group(etudid, group): + return # pas de changement # - log formsemestre: FormSemestre = group.partition.formsemestre + log(f"change_etud_group_in_partition: etudid={etudid} group={group}") Scolog.logdb( method="changeGroup", etudid=etudid, diff --git a/app/scodoc/sco_import_etuds.py b/app/scodoc/sco_import_etuds.py index 5bf61fdaed..3141c412f4 100644 --- a/app/scodoc/sco_import_etuds.py +++ b/app/scodoc/sco_import_etuds.py @@ -639,10 +639,10 @@ def scolars_import_admission(datafile, formsemestre_id=None, type_admission=None fields = adm_get_fields(titles, formsemestre_id) idx_nom = None idx_prenom = None - for idx in fields: - if fields[idx][0] == "nom": + for idx, field in fields.items(): + if field[0] == "nom": idx_nom = idx - if fields[idx][0] == "prenom": + if field[0] == "prenom": idx_prenom = idx if (idx_nom is None) or (idx_prenom is None): log("fields indices=" + ", ".join([str(x) for x in fields])) @@ -664,21 +664,20 @@ def scolars_import_admission(datafile, formsemestre_id=None, type_admission=None # Retrouve l'étudiant parmi ceux du semestre par (nom, prenom) nom = adm_normalize_string(line[idx_nom]) prenom = adm_normalize_string(line[idx_prenom]) - if not (nom, prenom) in etuds_by_nomprenom: - log( - "unable to find %s %s among members" % (line[idx_nom], line[idx_prenom]) - ) + if (nom, prenom) not in etuds_by_nomprenom: + msg = f"""Étudiant {line[idx_nom]} {line[idx_prenom]} inexistant""" + diag.append(msg) else: etud = etuds_by_nomprenom[(nom, prenom)] cur_adm = sco_etud.admission_list(cnx, args={"etudid": etud["etudid"]})[0] # peuple les champs presents dans le tableau args = {} - for idx in fields: - field_name, convertor = fields[idx] + for idx, field in fields.items(): + field_name, convertor = field if field_name in modifiable_fields: try: val = convertor(line[idx]) - except ValueError: + except ValueError as exc: raise ScoFormatError( 'scolars_import_admission: valeur invalide, ligne %d colonne %s: "%s"' % (nline, field_name, line[idx]), @@ -687,7 +686,7 @@ def scolars_import_admission(datafile, formsemestre_id=None, type_admission=None scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre_id, ), - ) + ) from exc if val is not None: # note: ne peut jamais supprimer une valeur args[field_name] = val if args: diff --git a/sco_version.py b/sco_version.py index 95fb08eb18..6ef51420b8 100644 --- a/sco_version.py +++ b/sco_version.py @@ -1,7 +1,7 @@ # -*- mode: python -*- # -*- coding: utf-8 -*- -SCOVERSION = "9.4.98" +SCOVERSION = "9.4.99" SCONAME = "ScoDoc" From 8b37f661d6a2eb7afe54eb89b2c64b9320998fa0 Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Sat, 8 Jul 2023 16:35:32 +0200 Subject: [PATCH 12/13] =?UTF-8?q?Am=C3=A9liore=20code=20gestion=20des=20gr?= =?UTF-8?q?oupes=20et=20corrige=20qq=20bugs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/formsemestre.py | 11 ++++ app/models/groups.py | 64 ++++++++++++++++------- app/scodoc/sco_groups.py | 81 +++++++++++++++-------------- app/scodoc/sco_permissions_check.py | 4 +- 4 files changed, 101 insertions(+), 59 deletions(-) diff --git a/app/models/formsemestre.py b/app/models/formsemestre.py index 6b76e22505..8ab323cee5 100644 --- a/app/models/formsemestre.py +++ b/app/models/formsemestre.py @@ -573,6 +573,17 @@ class FormSemestre(db.Model): user ) + def can_change_groups(self, user: User = None) -> bool: + """Vrai si l'utilisateur (par def. current) peut changer les groupes dans + ce semestre: vérifie permission et verrouillage. + """ + if not self.etat: + return False # semestre verrouillé + user = user or current_user + if user.has_permission(Permission.ScoEtudChangeGroups): + return True # typiquement admin, chef dept + return self.est_responsable(user) + def can_edit_jury(self, user: User = None): """Vrai si utilisateur (par def. current) peut saisir decision de jury dans ce semestre: vérifie permission et verrouillage. diff --git a/app/models/groups.py b/app/models/groups.py index dce377da8b..8e9d5b6200 100644 --- a/app/models/groups.py +++ b/app/models/groups.py @@ -10,11 +10,11 @@ from operator import attrgetter from sqlalchemy.exc import IntegrityError -from app import db +from app import db, log from app.models import SHORT_STR_LEN from app.models import GROUPNAME_STR_LEN from app.scodoc import sco_utils as scu -from app.scodoc.sco_exceptions import ScoValueError +from app.scodoc.sco_exceptions import AccessDenied, ScoValueError class Partition(db.Model): @@ -119,7 +119,7 @@ class Partition(db.Model): .first() ) - def set_etud_group(self, etudid: int, group: "GroupDescr") -> bool: + def set_etud_group(self, etud: "Identite", group: "GroupDescr") -> bool: """Affect etudid to group_id in given partition. Raises IntegrityError si conflit, or ValueError si ce group_id n'est pas dans cette partition @@ -128,36 +128,37 @@ class Partition(db.Model): """ if not group.id in (g.id for g in self.groups): raise ScoValueError( - f"""Le groupe {group.id} n'est pas dans la partition {self.partition_name or "tous"}""" + f"""Le groupe {group.id} n'est pas dans la partition { + self.partition_name or "tous"}""" ) - if etudid not in (e.id for e in self.formsemestre.etuds): + if etud.id not in (e.id for e in self.formsemestre.etuds): raise ScoValueError( - f"etudiant {etudid} non inscrit au formsemestre du groupe {group.id}" + f"""étudiant {etud.nomprenom} non inscrit au formsemestre du groupe { + group.group_name}""" ) try: existing_row = ( db.session.query(group_membership) - .filter_by(etudid=etudid) + .filter_by(etudid=etud.id) .join(GroupDescr) .filter_by(partition_id=self.id) .first() ) - existing_group_id = existing_row[1] if existing_row: + existing_group_id = existing_row[1] if group.id == existing_group_id: return False - update_row = ( - group_membership.update() - .where( - group_membership.c.etudid == etudid, - group_membership.c.group_id == existing_group_id, - ) - .values(group_id=group.id) - ) - db.session.execute(update_row) + # Fait le changement avec l'ORM sinon risque élevé de blocage + existing_group = GroupDescr.query.get(existing_group_id) + db.session.commit() + group.etuds.append(etud) + existing_group.etuds.remove(etud) + db.session.add(etud) + db.session.add(existing_group) + db.session.add(group) else: new_row = group_membership.insert().values( - etudid=etudid, group_id=group.id + etudid=etud.id, group_id=group.id ) db.session.execute(new_row) db.session.commit() @@ -166,6 +167,33 @@ class Partition(db.Model): raise return True + def create_group(self, group_name="", default=False) -> "GroupDescr": + "Crée un groupe dans cette partition" + if not self.formsemestre.can_change_groups(): + raise AccessDenied( + """Vous n'avez pas le droit d'effectuer cette opération, + ou bien le semestre est verrouillé !""" + ) + if group_name: + group_name = group_name.strip() + if not group_name and not default: + raise ValueError("invalid group name: ()") + if not GroupDescr.check_name(self, group_name, default=default): + raise ScoValueError( + f"Le groupe {group_name} existe déjà dans cette partition" + ) + numeros = [g.numero if g.numero is not None else 0 for g in self.groups] + if len(numeros) > 0: + new_numero = max(numeros) + 1 + else: + new_numero = 0 + group = GroupDescr(partition=self, group_name=group_name, numero=new_numero) + db.session.add(group) + db.session.commit() + log(f"create_group: created group_id={group.id}") + # + return group + class GroupDescr(db.Model): """Description d'un groupe d'une partition""" diff --git a/app/scodoc/sco_groups.py b/app/scodoc/sco_groups.py index 8438ba09c7..689dab3475 100644 --- a/app/scodoc/sco_groups.py +++ b/app/scodoc/sco_groups.py @@ -34,7 +34,6 @@ Optimisation possible: """ import collections -import operator import time from xml.etree import ElementTree @@ -45,7 +44,7 @@ from flask import g, request from flask import url_for, make_response from sqlalchemy.sql import text -from app import db +from app import cache, db, log from app.comp import res_sem from app.comp.res_compat import NotesTableCompat from app.models import FormSemestre, Identite, Scolog @@ -53,7 +52,6 @@ from app.models import GROUPNAME_STR_LEN, SHORT_STR_LEN from app.models.groups import GroupDescr, Partition, group_membership import app.scodoc.sco_utils as scu import app.scodoc.notesdb as ndb -from app import log, cache from app.scodoc.scolog import logdb from app.scodoc import html_sco_header from app.scodoc import sco_cache @@ -669,7 +667,8 @@ def change_etud_group_in_partition(etudid: int, group: GroupDescr) -> bool: (et le désinscrit d'autres groupes de cette partition) Return True si changement, False s'il était déjà dans ce groupe. """ - if not group.partition.set_etud_group(etudid, group): + etud: Identite = Identite.query.get_or_404(etudid) + if not group.partition.set_etud_group(etud, group): return # pas de changement # - log @@ -706,7 +705,6 @@ def setGroups( Ne peux pas modifier les groupes des partitions non éditables. """ - from app.scodoc import sco_formsemestre def xml_error(msg, code=404): data = ( @@ -716,26 +714,27 @@ def setGroups( response.headers["Content-Type"] = scu.XML_MIMETYPE return response - partition = get_partition(partition_id) - if not partition["groups_editable"] and (groupsToCreate or groupsToDelete): + partition: Partition = Partition.query.get(partition_id) + if not partition.groups_editable and (groupsToCreate or groupsToDelete): msg = "setGroups: partition non editable" log(msg) return xml_error(msg, code=403) - formsemestre_id = partition["formsemestre_id"] - formsemestre = FormSemestre.get_formsemestre(formsemestre_id) - if not sco_permissions_check.can_change_groups(formsemestre_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 !") log("***setGroups: partition_id=%s" % partition_id) log("groupsLists=%s" % groupsLists) log("groupsToCreate=%s" % groupsToCreate) log("groupsToDelete=%s" % groupsToDelete) - sem = sco_formsemestre.get_formsemestre(formsemestre_id) - if not sem["etat"]: + + if not partition.formsemestre.etat: raise AccessDenied("Modification impossible: semestre verrouillé") groupsToDelete = [g for g in groupsToDelete.split(";") if g] - etud_groups = formsemestre_get_etud_groupnames(formsemestre_id, attr="group_id") + etud_groups = formsemestre_get_etud_groupnames( + partition.formsemestre.id, attr="group_id" + ) for line in groupsLists.split("\n"): # for each group_id (one per line) fs = line.split(";") group_id = fs[0].strip() @@ -748,15 +747,13 @@ def setGroups( continue group: GroupDescr = GroupDescr.query.get(group_id) # Anciens membres du groupe: - old_members = get_group_members(group_id) - old_members_set = set([x["etudid"] for x in old_members]) + old_members_set = {etud.id for etud in group.etuds} # Place dans ce groupe les etudiants indiqués: for etudid_str in fs[1:-1]: etudid = int(etudid_str) if etudid in old_members_set: - old_members_set.remove( - etudid - ) # a nouveau dans ce groupe, pas besoin de l'enlever + # était dans ce groupe, l'enlever + old_members_set.remove(etudid) if (etudid not in etud_groups) or ( group_id != etud_groups[etudid].get(partition_id, "") ): # pas le meme groupe qu'actuel @@ -765,7 +762,6 @@ def setGroups( cnx = ndb.GetDBConnexion() cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor) for etudid in old_members_set: - log("removing %s from group %s" % (etudid, group_id)) ndb.SimpleQuery( "DELETE FROM group_membership WHERE etudid=%(etudid)s and group_id=%(group_id)s", {"etudid": etudid, "group_id": group_id}, @@ -775,8 +771,8 @@ def setGroups( cnx, method="removeFromGroup", etudid=etudid, - msg="formsemestre_id=%s,partition_name=%s, group_name=%s" - % (formsemestre_id, partition["partition_name"], group["group_name"]), + msg=f"""formsemestre_id={partition.formsemestre.id},partition_name={ + partition.partition_name}, group_name={group.group_name}""", ) # Supprime les groupes indiqués comme supprimés: @@ -799,7 +795,7 @@ def setGroups( change_etud_group_in_partition(etudid, group.id) # Update parcours - formsemestre.update_inscriptions_parcours_from_groups() + partition.formsemestre.update_inscriptions_parcours_from_groups() data = ( 'Groupes enregistrés' @@ -812,6 +808,7 @@ def setGroups( def create_group(partition_id, group_name="", default=False) -> GroupDescr: """Create a new group in this partition. If default, create default partition (with no name) + Obsolete: utiliser Partition.create_group """ partition = Partition.query.get_or_404(partition_id) if not sco_permissions_check.can_change_groups(partition.formsemestre_id): @@ -833,7 +830,7 @@ def create_group(partition_id, group_name="", default=False) -> GroupDescr: 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}") + log(f"create_group: created group_id={group.id}") # return group @@ -1377,11 +1374,11 @@ def groups_auto_repartition(partition_id=None): """Reparti les etudiants dans des groupes dans une partition, en respectant le niveau et la mixité. """ - partition = get_partition(partition_id) - if not partition["groups_editable"]: + partition: Partition = Partition.query.get_or_404(partition_id) + if not partition.groups_editable: raise AccessDenied("Partition non éditable") - formsemestre_id = partition["formsemestre_id"] - formsemestre = FormSemestre.get_formsemestre(formsemestre_id) + formsemestre_id = partition.formsemestre_id + formsemestre = partition.formsemestre # renvoie sur page édition groupes dest_url = url_for( "scolar.affect_groups", scodoc_dept=g.scodoc_dept, partition_id=partition_id @@ -1404,12 +1401,14 @@ def groups_auto_repartition(partition_id=None): H = [ html_sco_header.sco_header(page_title="Répartition des groupes"), - "

    Répartition des groupes de %s

    " % partition["partition_name"], - f"

    Semestre {formsemestre.titre_annee()}

    ", - """

    Les groupes existants seront effacés et remplacés par + f"""

    Répartition des groupes de {partition.partition_name}

    +

    Semestre {formsemestre.titre_annee()}

    ", +

    Les groupes existants seront effacés et remplacés par ceux créés ici. La répartition aléatoire tente d'uniformiser le niveau des groupes (en utilisant la dernière moyenne générale disponible pour - chaque étudiant) et de maximiser la mixité de chaque groupe.

    """, + chaque étudiant) et de maximiser la mixité de chaque groupe. +

    + """, ] tf = TrivialFormulator( @@ -1429,23 +1428,24 @@ def groups_auto_repartition(partition_id=None): # form submission log( "groups_auto_repartition( partition_id=%s partition_name=%s" - % (partition_id, partition["partition_name"]) + % (partition_id, partition.partition_name) ) groupNames = tf[2]["groupNames"] - group_names = sorted(set([x.strip() for x in groupNames.split(",")])) + group_names = sorted({x.strip() for x in groupNames.split(",")}) # Détruit les groupes existant de cette partition - for old_group in get_partition_groups(partition): - group_delete(old_group["group_id"]) + for group in partition.groups: + db.session.delete(group) + db.session.commit() # Crée les nouveaux groupes groups = [] for group_name in group_names: if group_name.strip(): - groups.append(create_group(partition_id, group_name)) + groups.append(partition.create_group(group_name)) # nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre) identdict = nt.identdict # build: { civilite : liste etudids trie par niveau croissant } - civilites = set([x["civilite"] for x in identdict.values()]) + civilites = {x["civilite"] for x in identdict.values()} listes = {} for civilite in civilites: listes[civilite] = [ @@ -1460,14 +1460,17 @@ def groups_auto_repartition(partition_id=None): igroup = 0 nbgroups = len(groups) while n > 0: + log(f"n={n}") for civilite in civilites: + log(f"civilite={civilite}") if len(listes[civilite]): n -= 1 etudid = listes[civilite].pop()[1] group = groups[igroup] igroup = (igroup + 1) % nbgroups + log(f"in {etudid} in group {group.id}") change_etud_group_in_partition(etudid, group) - log("%s in group %s" % (etudid, group.id)) + log(f"{etudid} in group {group.id}") return flask.redirect(dest_url) @@ -1475,8 +1478,6 @@ def _get_prev_moy(etudid, formsemestre_id): """Donne la derniere moyenne generale calculee pour cette étudiant, ou 0 si on n'en trouve pas (nouvel inscrit,...). """ - from app.scodoc import sco_cursus_dut - info = sco_etud.get_etud_info(etudid=etudid, filled=True) if not info: raise ScoValueError("etudiant invalide: etudid=%s" % etudid) diff --git a/app/scodoc/sco_permissions_check.py b/app/scodoc/sco_permissions_check.py index 224881bf87..dcc19f455c 100644 --- a/app/scodoc/sco_permissions_check.py +++ b/app/scodoc/sco_permissions_check.py @@ -142,7 +142,9 @@ def check_access_diretud(formsemestre_id, required_permission=Permission.ScoImpl def can_change_groups(formsemestre_id: int) -> bool: - "Vrai si l'utilisateur peut changer les groupes dans ce semestre" + """Vrai si l'utilisateur peut changer les groupes dans ce semestre + Obsolete: utiliser FormSemestre.can_change_groups + """ formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id) if not formsemestre.etat: return False # semestre verrouillé From dba48f32eb0970d7110fc6ddb75137a1f0e5c7cd Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Sun, 9 Jul 2023 21:57:50 +0200 Subject: [PATCH 13/13] add 404 --- app/scodoc/sco_groups.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/scodoc/sco_groups.py b/app/scodoc/sco_groups.py index 689dab3475..ab3e014d5d 100644 --- a/app/scodoc/sco_groups.py +++ b/app/scodoc/sco_groups.py @@ -745,7 +745,7 @@ def setGroups( except ValueError: log(f"setGroups: ignoring invalid group_id={group_id}") continue - group: GroupDescr = GroupDescr.query.get(group_id) + group: GroupDescr = GroupDescr.query.get_or_404(group_id) # Anciens membres du groupe: old_members_set = {etud.id for etud in group.etuds} # Place dans ce groupe les etudiants indiqués: