From d78fb13821325f7dc4a8aa87f97fed458df8ab06 Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Wed, 31 Aug 2022 19:14:21 +0200 Subject: [PATCH] BUT/parcours: inscriptions, visualisation des parcours --- app/models/formsemestre.py | 1 + app/models/groups.py | 3 +- app/models/modules.py | 14 +++ app/scodoc/sco_formations.py | 2 +- app/scodoc/sco_formsemestre.py | 1 + app/scodoc/sco_formsemestre_edit.py | 75 ++++++++------- app/scodoc/sco_groups.py | 21 ++--- app/scodoc/sco_moduleimpl.py | 2 +- app/scodoc/sco_moduleimpl_inscriptions.py | 108 ++++++++++++---------- app/views/notes.py | 2 + 10 files changed, 130 insertions(+), 99 deletions(-) diff --git a/app/models/formsemestre.py b/app/models/formsemestre.py index 1016a5f09..ef700ace5 100644 --- a/app/models/formsemestre.py +++ b/app/models/formsemestre.py @@ -125,6 +125,7 @@ class FormSemestre(db.Model): "Partition", backref=db.backref("formsemestre", lazy=True), lazy="dynamic", + order_by="Partition.numero", ) # Ancien id ScoDoc7 pour les migrations de bases anciennes # ne pas utiliser après migrate_scodoc7_dept_archives diff --git a/app/models/groups.py b/app/models/groups.py index 1a5dc257d..5d2026eb9 100644 --- a/app/models/groups.py +++ b/app/models/groups.py @@ -47,6 +47,7 @@ class Partition(db.Model): backref=db.backref("partition", lazy=True), lazy="dynamic", cascade="all, delete-orphan", + order_by="GroupDescr.numero", ) def __init__(self, **kwargs): @@ -109,7 +110,7 @@ class GroupDescr(db.Model): partition_id = db.Column(db.Integer, db.ForeignKey("partition.id")) # "A", "C2", ... (NULL for 'all'): group_name = db.Column(db.String(GROUPNAME_STR_LEN)) - # Numero = ordre de presentation) + # Numero = ordre de presentation numero = db.Column(db.Integer) etuds = db.relationship( diff --git a/app/models/modules.py b/app/models/modules.py index 082d5a93a..9052f94b6 100644 --- a/app/models/modules.py +++ b/app/models/modules.py @@ -51,6 +51,7 @@ class Module(db.Model): secondary=parcours_modules, lazy="subquery", backref=db.backref("modules", lazy=True), + order_by="ApcParcours.numero", ) app_critiques = db.relationship( @@ -115,6 +116,19 @@ class Module(db.Model): """ return scu.ModuleType.get_abbrev(self.module_type) + def sort_key_apc(self) -> tuple: + """Clé de tri pour avoir + présentation par type (res, sae), parcours, type, numéro + """ + if ( + len(self.parcours) == self.formation.referentiel_competence.parcours.count() + or len(self.parcours) == 0 + ): + key_parcours = "" + else: + key_parcours = "/".join([p.code for p in self.parcours]) + return self.module_type, key_parcours, self.numero + def set_ue_coef(self, ue, coef: float) -> None: """Set coef module vers cette UE""" self.update_ue_coef_dict({ue.id: coef}) diff --git a/app/scodoc/sco_formations.py b/app/scodoc/sco_formations.py index 24993922c..b060ffe08 100644 --- a/app/scodoc/sco_formations.py +++ b/app/scodoc/sco_formations.py @@ -290,7 +290,7 @@ def formation_import_xml(doc: str, import_tags=True): module.parcours.append(parcours) db.session.add(module) else: - log("Warning: parcours {code_parcours} inexistant !") + log(f"Warning: parcours {code_parcours} inexistant !") if import_tags and tag_names: sco_tag_module.module_tag_set(mod_id, tag_names) if module.is_apc() and ue_coef_dict: diff --git a/app/scodoc/sco_formsemestre.py b/app/scodoc/sco_formsemestre.py index ce7f2c10d..a4f9ad500 100644 --- a/app/scodoc/sco_formsemestre.py +++ b/app/scodoc/sco_formsemestre.py @@ -254,6 +254,7 @@ def do_formsemestre_create(args, silent=False): formsemestre_id, default=True, redirect=0, + numero=1000000, # à la fin ) _group_id = sco_groups.create_group(partition_id, default=True) diff --git a/app/scodoc/sco_formsemestre_edit.py b/app/scodoc/sco_formsemestre_edit.py index bbf80097d..2f9ea31c1 100644 --- a/app/scodoc/sco_formsemestre_edit.py +++ b/app/scodoc/sco_formsemestre_edit.py @@ -105,8 +105,9 @@ def formsemestre_editwithmodules(formsemestre_id): ] if not sem["etat"]: H.append( - """

%sCe semestre est verrouillé.

""" - % scu.icontag("lock_img", border="0", title="Semestre verrouillé") + f"""

{scu.icontag( + "lock_img", border="0", title="Semestre verrouillé") + }Ce semestre est verrouillé.

""" ) else: r = do_formsemestre_createwithmodules(edit=1) @@ -159,7 +160,7 @@ def do_formsemestre_createwithmodules(edit=False): "vous n'avez pas le droit d'effectuer cette opération" ) - # Liste des enseignants avec forme pour affichage / saisie avec suggestion + # Liste des enseignants avec form pour affichage / saisie avec suggestion # attention: il faut prendre ici tous les utilisateurs, même inactifs, car # les responsables de modules d'anciens semestres peuvent ne plus être actifs. # Mais la suggestion utilise get_user_list_xml() qui ne suggérera que les actifs. @@ -226,7 +227,11 @@ def do_formsemestre_createwithmodules(edit=False): semestre_id_labels.append(f"S{sid}") # Liste des modules dans cette formation if is_apc: - modules = formation.modules.order_by(Module.module_type, Module.numero) + # BUT: trie par type (res, sae), parcours, numéro + modules = sorted( + formation.modules, + key=lambda m: m.sort_key_apc(), + ) else: modules = ( Module.query.filter( @@ -235,11 +240,10 @@ def do_formsemestre_createwithmodules(edit=False): .order_by(Module.module_type, UniteEns.numero, Module.numero) .all() ) - mods = [mod.to_dict() for mod in modules] # Pour regroupement des modules par semestres: semestre_ids = {} - for mod in mods: - semestre_ids[mod["semestre_id"]] = 1 + for mod in modules: + semestre_ids[mod.semestre_id] = 1 semestre_ids = list(semestre_ids.keys()) semestre_ids.sort() @@ -607,16 +611,16 @@ def do_formsemestre_createwithmodules(edit=False): }, ) ) - for mod in mods: - if mod["semestre_id"] == semestre_id and ( + for mod in modules: + if mod.semestre_id == semestre_id and ( (not edit) # creation => tous modules or (not is_apc) # pas BUT, on peut mixer les semestres or (semestre_id == formsemestre.semestre_id) # module du semestre - or (mod["module_id"] in module_ids_set) # module déjà présent + or (mod.id in module_ids_set) # module déjà présent ): nbmod += 1 if edit: - select_name = f"{mod['module_id']}!group_id" + select_name = f"{mod.id}!group_id" def opt_selected(gid): if gid == vals.get(select_name): @@ -624,26 +628,34 @@ def do_formsemestre_createwithmodules(edit=False): else: return "" - if mod["module_id"] in module_ids_set: + if mod.id in module_ids_set: + # pas de menu inscription si le module est déjà présent disabled = "disabled" else: disabled = "" - fcg = '' default_group_id = sco_groups.get_default_group(formsemestre_id) - fcg += '' % ( - default_group_id, - opt_selected(default_group_id), - ) - fcg += '' % opt_selected("") - for p in sco_groups.get_partitions_list(formsemestre_id): - if p["partition_name"] != None: - for group in sco_groups.get_partition_groups(p): - fcg += '' % ( - group["group_id"], - opt_selected(group["group_id"]), - p["partition_name"], - group["group_name"], - ) + fcg += f"""""" + + fcg += f'' + for partition in formsemestre.partitions: + if partition.partition_name is not None: + for group in partition.groups: + # Si le module n'est associé qu'à un parcours, propose d'y inscrire les étudiants directement + if ( + partition.partition_name == scu.PARTITION_PARCOURS + and len(mod.parcours) == 1 + and group.group_name == mod.parcours[0].code + ): + selected = "selected" + else: + selected = opt_selected(group.id) + # print( + # f"{partition.partition_name} {group.group_name} {selected}" + # ) + fcg += f"""""" fcg += "" itemtemplate = f""" %(label)s @@ -657,12 +669,12 @@ def do_formsemestre_createwithmodules(edit=False): """ modform.append( ( - "MI" + str(mod["module_id"]), + "MI" + str(mod.id), { "input_type": "text_suggest", "size": 50, "withcheckbox": True, - "title": "%s %s" % (mod["code"] or "", mod["titre"] or ""), + "title": "%s %s" % (mod.code or "", mod.titre or ""), "allowed_values": allowed_user_names, "template": itemtemplate, "text_suggest_options": { @@ -689,11 +701,6 @@ def do_formsemestre_createwithmodules(edit=False): ) ) if edit: - # modform.append( ('inscrire_etudslist', - # { 'input_type' : 'checkbox', - # 'allowed_values' : ['X'], 'labels' : [ '' ], - # 'title' : '' , - # 'explanation' : 'inscrire tous les étudiants du semestre aux modules ajoutés'}) ) submitlabel = "Modifier ce semestre" else: submitlabel = "Créer ce semestre de formation" diff --git a/app/scodoc/sco_groups.py b/app/scodoc/sco_groups.py index 6e008e9c1..ae8f57c8e 100644 --- a/app/scodoc/sco_groups.py +++ b/app/scodoc/sco_groups.py @@ -137,7 +137,9 @@ def get_partition(partition_id): def get_partitions_list(formsemestre_id, with_default=True) -> list[dict]: - """Liste des partitions pour ce semestre (list of dicts)""" + """Liste des partitions pour ce semestre (list of dicts), + triées par numéro, avec la partition par défaut en fin de liste. + """ partitions = ndb.SimpleDictFetch( """SELECT p.id AS partition_id, p.* FROM partition p @@ -205,7 +207,7 @@ def get_partition_groups(partition): FROM group_descr gd, partition p WHERE gd.partition_id=%(partition_id)s AND gd.partition_id=p.id - ORDER BY group_name + ORDER BY gd.numero """, partition, ) @@ -575,7 +577,6 @@ def XMLgetGroupsInPartition(partition_id): # was XMLgetGroupesTD formsemestre = FormSemestre.query.get_or_404(formsemestre_id) etuds_set = {ins.etudid for ins in formsemestre.inscriptions} - sem = formsemestre.get_infos_dict() # transition TODO groups = get_partition_groups(partition) # Build XML: t1 = time.time() @@ -593,7 +594,6 @@ def XMLgetGroupsInPartition(partition_id): # was XMLgetGroupesTD x_response.append(x_group) for e in get_group_members(group["group_id"]): etud = sco_etud.get_etud_info(etudid=e["etudid"], filled=True)[0] - # etud = sco_etud.get_etud_info_filled_by_etudid(e["etudid"], cnx) x_group.append( Element( "etud", @@ -602,7 +602,7 @@ def XMLgetGroupsInPartition(partition_id): # was XMLgetGroupesTD sexe=etud["civilite_str"], # compat nom=sco_etud.format_nom(etud["nom"]), prenom=sco_etud.format_prenom(etud["prenom"]), - origin=comp_origin(etud, sem), + origin=_comp_etud_origin(etud, formsemestre), ) ) if e["etudid"] in etuds_set: @@ -620,7 +620,6 @@ def XMLgetGroupsInPartition(partition_id): # was XMLgetGroupesTD doc.append(x_group) for etudid in etuds_set: etud = sco_etud.get_etud_info(etudid=etudid, filled=True)[0] - # etud = sco_etud.get_etud_info_filled_by_etudid(etudid, cnx) x_group.append( Element( "etud", @@ -628,7 +627,7 @@ def XMLgetGroupsInPartition(partition_id): # was XMLgetGroupesTD sexe=etud["civilite_str"], nom=sco_etud.format_nom(etud["nom"]), prenom=sco_etud.format_prenom(etud["prenom"]), - origin=comp_origin(etud, sem), + origin=_comp_etud_origin(etud, formsemestre), ) ) t2 = time.time() @@ -640,14 +639,14 @@ def XMLgetGroupsInPartition(partition_id): # was XMLgetGroupesTD return response -def comp_origin(etud, cur_sem): +def _comp_etud_origin(etud: dict, cur_formsemestre: FormSemestre): """breve description de l'origine de l'étudiant (sem. precedent) (n'indique l'origine que si ce n'est pas le semestre precedent normal) """ # cherche le semestre suivant le sem. courant dans la liste cur_sem_idx = None for i in range(len(etud["sems"])): - if etud["sems"][i]["formsemestre_id"] == cur_sem["formsemestre_id"]: + if etud["sems"][i]["formsemestre_id"] == cur_formsemestre.id: cur_sem_idx = i break @@ -655,8 +654,8 @@ def comp_origin(etud, cur_sem): return "" # on pourrait indiquer le bac mais en general on ne l'a pas en debut d'annee prev_sem = etud["sems"][cur_sem_idx + 1] - if prev_sem["semestre_id"] != (cur_sem["semestre_id"] - 1): - return " (S%s)" % prev_sem["semestre_id"] + if prev_sem["semestre_id"] != (cur_formsemestre.semestre_id - 1): + return f" (S{prev_sem['semestre_id']})" else: return "" # parcours normal, ne le signale pas diff --git a/app/scodoc/sco_moduleimpl.py b/app/scodoc/sco_moduleimpl.py index e0dab5f42..97fb7df06 100644 --- a/app/scodoc/sco_moduleimpl.py +++ b/app/scodoc/sco_moduleimpl.py @@ -133,7 +133,7 @@ def moduleimpl_withmodule_list( - pour les formations classiques: semestre/UE/numero_matiere/numero_module; - pour le BUT: ignore UEs sauf si sort_by_ue et matières dans le tri. - Attention: Cette fonction fait partie de l'API ScoDoc 7 et est publiée. + NB: Cette fonction faisait partie de l'API ScoDoc 7. """ from app.scodoc import sco_edit_ue from app.scodoc import sco_edit_matiere diff --git a/app/scodoc/sco_moduleimpl_inscriptions.py b/app/scodoc/sco_moduleimpl_inscriptions.py index 76871cf35..b2be0e50e 100644 --- a/app/scodoc/sco_moduleimpl_inscriptions.py +++ b/app/scodoc/sco_moduleimpl_inscriptions.py @@ -214,7 +214,7 @@ def moduleimpl_inscriptions_edit(moduleimpl_id, etuds=[], submitted=False): return "\n".join(H) -def _make_menu(partitions, title="", check="true"): +def _make_menu(partitions: list[dict], title="", check="true") -> str: """Menu with list of all groups""" items = [{"title": "Tous", "attr": "onclick=\"group_select('', -1, %s)\"" % check}] p_idx = 0 @@ -258,8 +258,8 @@ def moduleimpl_inscriptions_stats(formsemestre_id): """ authuser = current_user - - sem = sco_formsemestre.get_formsemestre(formsemestre_id) + formsemestre = FormSemestre.query.get_or_404(formsemestre_id) + is_apc = formsemestre.formation.is_apc() inscrits = sco_formsemestre_inscriptions.do_formsemestre_inscription_list( args={"formsemestre_id": formsemestre_id} ) @@ -268,85 +268,93 @@ def moduleimpl_inscriptions_stats(formsemestre_id): formsemestre_id ) - can_change = authuser.has_permission(Permission.ScoEtudInscrit) and sem["etat"] - - # Liste des modules - Mlist = sco_moduleimpl.moduleimpl_withmodule_list( - formsemestre_id=formsemestre_id, sort_by_ue=True + can_change = ( + authuser.has_permission(Permission.ScoEtudInscrit) and formsemestre.etat ) - # Decrit les inscriptions aux modules: + + # Décrit les inscriptions aux modules: commons = [] # modules communs a tous les etuds du semestre options = [] # modules ou seuls quelques etudiants sont inscrits - for mod in Mlist: + mod_description = {} # modimplid : str + mod_nb_inscrits = {} # modimplid : int + for modimpl in formsemestre.modimpls_sorted: tous_inscrits, nb_inscrits, descr = descr_inscrs_module( - sem, - mod["moduleimpl_id"], + modimpl.id, set_all, partitions, - partitions_etud_groups, ) if tous_inscrits: - commons.append(mod) + commons.append(modimpl) else: - mod["descri"] = descr - mod["nb_inscrits"] = nb_inscrits - options.append(mod) + mod_description[modimpl.id] = descr + mod_nb_inscrits[modimpl.id] = nb_inscrits + options.append(modimpl) + # Page HTML: H = [html_sco_header.html_sem_header("Inscriptions aux modules du semestre")] - H.append("

Inscrits au semestre: %d étudiants

" % len(inscrits)) + H.append(f"

Inscrits au semestre: {len(inscrits)} étudiants

") if options: H.append("

Modules auxquels tous les étudiants ne sont pas inscrits:

") H.append( '' ) - for mod in options: + for modimpl in options: if can_change: - c_link = '%s' % ( - mod["moduleimpl_id"], - mod["descri"] or "(inscrire des étudiants)", - ) + c_link = f"""{mod_description[modimpl.id] or "(inscrire des étudiants)"} + """ else: - c_link = mod["descri"] + c_link = mod_description[modimpl.id] H.append( - '' - % ( - mod["ue"]["acronyme"] or "", - mod["module"]["code"] or "(module sans code)", - mod["nb_inscrits"], - c_link, - ) + f"""""" ) H.append("
UECodeInscrits
%s%s%s%s
{ + modimpl.module.ue.acronyme or "" + }{ + modimpl.module.code or "(module sans code)" + }{ + mod_nb_inscrits[modimpl.id]}{c_link}
") else: H.append( - 'Tous les étudiants sont inscrits à tous les modules.' + """Tous les étudiants sont inscrits à tous les modules.""" ) if commons: H.append( - "

Modules communs (auxquels tous les étudiants sont inscrits):

" + """

Modules communs (auxquels tous les étudiants sont inscrits):

+ + + """ ) - H.append( - '
UECodeModule
' - ) - for mod in commons: + if is_apc: + H.append("") + H.append("""""") + for modimpl in commons: if can_change: - c_link = ( - '%s' - % (mod["moduleimpl_id"], mod["module"]["titre"]) - ) + c_link = f"""{modimpl.module.titre}""" else: - c_link = mod["module"]["titre"] + c_link = modimpl.module.titre H.append( - '' - % ( - mod["ue"]["acronyme"], - mod["module"]["code"] or "(module sans code)", - c_link, - ) + f"""""" ) + if is_apc: + H.append( + f"""""" + ) + H.append("") H.append("
UECodeModule
Parcours
%s%s%s
{ + modimpl.module.ue.acronyme or "" + }{ + modimpl.module.code or "(module sans code)" + }{c_link}{', '.join(p.code for p in modimpl.module.parcours)}
") # Etudiants "dispensés" d'une UE (capitalisée) @@ -427,9 +435,7 @@ def moduleimpl_inscriptions_stats(formsemestre_id): return "\n".join(H) -def descr_inscrs_module( - sem, moduleimpl_id, set_all, partitions, partitions_etud_groups -): +def descr_inscrs_module(moduleimpl_id, set_all, partitions): """returns tous_inscrits, nb_inscrits, descr""" ins = sco_moduleimpl.do_moduleimpl_inscription_list(moduleimpl_id=moduleimpl_id) set_m = set([x["etudid"] for x in ins]) # ens. des inscrits au module diff --git a/app/views/notes.py b/app/views/notes.py index 0625db60d..34fb1b4b8 100644 --- a/app/views/notes.py +++ b/app/views/notes.py @@ -3173,6 +3173,7 @@ def check_integrity_all(): def moduleimpl_list( moduleimpl_id=None, formsemestre_id=None, module_id=None, format="json" ): + # TODO DEPRECATED A RETIRER try: data = sco_moduleimpl.moduleimpl_list( moduleimpl_id=moduleimpl_id, @@ -3193,6 +3194,7 @@ def moduleimpl_withmodule_list( moduleimpl_id=None, formsemestre_id=None, module_id=None, format="json" ): """API ScoDoc 7""" + # TODO DEPRECATED A RETIRER data = sco_moduleimpl.moduleimpl_withmodule_list( moduleimpl_id=moduleimpl_id, formsemestre_id=formsemestre_id,