diff --git a/app/scodoc/sco_bulletins.py b/app/scodoc/sco_bulletins.py index 6cfdb25b..3fd15859 100644 --- a/app/scodoc/sco_bulletins.py +++ b/app/scodoc/sco_bulletins.py @@ -28,6 +28,7 @@ """Génération des bulletins de notes """ +import collections import email import time import numpy as np @@ -143,7 +144,7 @@ def formsemestre_bulletinetud_dict(formsemestre_id, etudid, version="long"): nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre) if not nt.get_etud_etat(etudid): raise ScoValueError("Étudiant non inscrit à ce semestre") - I = scu.DictDefault(defaultvalue="") + I = collections.defaultdict(str) I["etudid"] = etudid I["formsemestre_id"] = formsemestre_id I["sem"] = formsemestre.get_infos_dict() @@ -260,7 +261,7 @@ def formsemestre_bulletinetud_dict(formsemestre_id, etudid, version="long"): # n'affiche pas le rang sur le bulletin s'il y a des # notes en attente dans ce semestre rang = scu.RANG_ATTENTE_STR - rang_gr = scu.DictDefault(defaultvalue=scu.RANG_ATTENTE_STR) + rang_gr = collections.defaultdict(lambda: scu.RANG_ATTENTE_STR) inscriptions_counts = nt.get_inscriptions_counts() I["rang"] = rang I["rang_gr"] = rang_gr @@ -710,50 +711,15 @@ def etud_descr_situation_semestre( decisions_ue : noms (acronymes) des UE validées, séparées par des virgules. descr_decisions_ue : ' UE acquises: UE1, UE2', ou vide si pas de dec. ou si pas show_uevalid descr_mention : 'Mention Bien', ou vide si pas de mention ou si pas show_mention + descr_parcours : le nom (libelle) du parcours dans lequel est inscrit l'étudiant en BUT (vide ailleurs) """ # Fonction utilisée par tous les bulletins (APC ou classiques) - cnx = ndb.GetDBConnexion() - infos = scu.DictDefault(defaultvalue="") + infos = collections.defaultdict(str) # --- Situation et décisions jury - # démission/inscription ? - events = sco_etud.scolar_events_list( - cnx, args={"etudid": etudid, "formsemestre_id": formsemestre_id} - ) - date_inscr = None - date_dem = None - date_def = None - for event in events: - event_type = event["event_type"] - if event_type == "INSCRIPTION": - if date_inscr: - # plusieurs inscriptions ??? - # date_inscr += ', ' + event['event_date'] + ' (!)' - # il y a eu une erreur qui a laissé un event 'inscription' - # on l'efface: - log( - f"etud_descr_situation_semestre: removing duplicate INSCRIPTION event for etudid={etudid} !" - ) - sco_etud.scolar_events_delete(cnx, event["event_id"]) - else: - date_inscr = event["event_date"] - elif event_type == "DEMISSION": - # assert date_dem == None, 'plusieurs démissions !' - if date_dem: # cela ne peut pas arriver sauf bug (signale a Evry 2013?) - log( - f"etud_descr_situation_semestre: removing duplicate DEMISSION event for etudid={etudid} !" - ) - sco_etud.scolar_events_delete(cnx, event["event_id"]) - else: - date_dem = event["event_date"] - elif event_type == "DEFAILLANCE": - if date_def: - log( - f"etud_descr_situation_semestre: removing duplicate DEFAILLANCE event for etudid={etudid} !" - ) - sco_etud.scolar_events_delete(cnx, event["event_id"]) - else: - date_def = event["event_date"] + + date_inscr, date_dem, date_def = _dates_insc_dem_def(etudid, formsemestre_id) + if show_date_inscr: if not date_inscr: infos["date_inscription"] = "" @@ -851,6 +817,49 @@ def etud_descr_situation_semestre( return infos, dpv +def _dates_insc_dem_def(etudid, formsemestre_id) -> tuple: + "Cherche les dates d'inscription, démission et défaillance de l'étudiant" + cnx = ndb.GetDBConnexion() + events = sco_etud.scolar_events_list( + cnx, args={"etudid": etudid, "formsemestre_id": formsemestre_id} + ) + date_inscr = None + date_dem = None + date_def = None + for event in events: + event_type = event["event_type"] + if event_type == "INSCRIPTION": + if date_inscr: + # plusieurs inscriptions ??? + # date_inscr += ', ' + event['event_date'] + ' (!)' + # il y a eu une erreur qui a laissé un event 'inscription' + # on l'efface: + log( + f"etud_descr_situation_semestre: removing duplicate INSCRIPTION event for etudid={etudid} !" + ) + sco_etud.scolar_events_delete(cnx, event["event_id"]) + else: + date_inscr = event["event_date"] + elif event_type == "DEMISSION": + # assert date_dem == None, 'plusieurs démissions !' + if date_dem: # cela ne peut pas arriver sauf bug (signale a Evry 2013?) + log( + f"etud_descr_situation_semestre: removing duplicate DEMISSION event for etudid={etudid} !" + ) + sco_etud.scolar_events_delete(cnx, event["event_id"]) + else: + date_dem = event["event_date"] + elif event_type == "DEFAILLANCE": + if date_def: + log( + f"etud_descr_situation_semestre: removing duplicate DEFAILLANCE event for etudid={etudid} !" + ) + sco_etud.scolar_events_delete(cnx, event["event_id"]) + else: + date_def = event["event_date"] + return date_inscr, date_dem, date_def + + def _format_situation_fields( infos, field_names: list[str], extra_values: list[str] ) -> None: diff --git a/app/scodoc/sco_evaluations.py b/app/scodoc/sco_evaluations.py index 799a0e03..9bfcfd4c 100644 --- a/app/scodoc/sco_evaluations.py +++ b/app/scodoc/sco_evaluations.py @@ -27,6 +27,7 @@ """Evaluations """ +import collections import datetime import operator import time @@ -171,8 +172,8 @@ def do_evaluation_etat(evaluation_id, partition_id=None, select_first_partition= # On considere une note "manquante" lorsqu'elle n'existe pas # ou qu'elle est en attente (ATT) - GrNbMissing = scu.DictDefault() # group_id : nb notes manquantes - GrNotes = scu.DictDefault(defaultvalue=[]) # group_id: liste notes valides + GrNbMissing = collections.defaultdict(int) # group_id : nb notes manquantes + GrNotes = collections.defaultdict(list) # group_id: liste notes valides TotalNbMissing = 0 TotalNbAtt = 0 groups = {} # group_id : group diff --git a/app/scodoc/sco_formsemestre_inscriptions.py b/app/scodoc/sco_formsemestre_inscriptions.py index cdbe2c57..1e993a35 100644 --- a/app/scodoc/sco_formsemestre_inscriptions.py +++ b/app/scodoc/sco_formsemestre_inscriptions.py @@ -27,6 +27,7 @@ """Opérations d'inscriptions aux semestres et modules """ +import collections import time import flask @@ -543,10 +544,8 @@ def formsemestre_inscription_option(etudid, formsemestre_id): mods = sco_moduleimpl.moduleimpl_withmodule_list(formsemestre_id=formsemestre_id) inscr = sco_moduleimpl.do_moduleimpl_inscription_list(etudid=etudid) # Formulaire - modimpls_by_ue_ids = scu.DictDefault(defaultvalue=[]) # ue_id : [ moduleimpl_id ] - modimpls_by_ue_names = scu.DictDefault( - defaultvalue=[] - ) # ue_id : [ moduleimpl_name ] + modimpls_by_ue_ids = collections.defaultdict(list) # ue_id : [ moduleimpl_id ] + modimpls_by_ue_names = collections.defaultdict(list) # ue_id : [ moduleimpl_name ] ues = [] ue_ids = set() initvalues = {} diff --git a/app/scodoc/sco_modalites.py b/app/scodoc/sco_modalites.py index b12e54e7..1f79201a 100644 --- a/app/scodoc/sco_modalites.py +++ b/app/scodoc/sco_modalites.py @@ -33,8 +33,8 @@ Elle n'est pas utilisée pour les parcours, ni pour rien d'autre (c'est donc un attribut "cosmétique"). """ +import collections import app.scodoc.notesdb as ndb -import app.scodoc.sco_utils as scu from app import log @@ -54,7 +54,7 @@ def group_sems_by_modalite(sems): """Given the list of fromsemestre, group them by modalite, sorted in each one by semestre id and date """ - sems_by_mod = scu.DictDefault(defaultvalue=[]) + sems_by_mod = collections.defaultdict(list) modalites = list_formsemestres_modalites(sems) for modalite in modalites: for sem in sems: diff --git a/app/scodoc/sco_moduleimpl_inscriptions.py b/app/scodoc/sco_moduleimpl_inscriptions.py index 7de236be..06c7ffb1 100644 --- a/app/scodoc/sco_moduleimpl_inscriptions.py +++ b/app/scodoc/sco_moduleimpl_inscriptions.py @@ -27,6 +27,7 @@ """Opérations d'inscriptions aux modules (interface pour gérer options ou parcours) """ +import collections from operator import itemgetter import flask @@ -676,7 +677,7 @@ def get_etuds_with_capitalized_ue(formsemestre_id: int) -> list[dict]: """For each UE, computes list of students capitalizing the UE. returns { ue_id : [ { infos } ] } """ - ues_cap_info = scu.DictDefault(defaultvalue=[]) + ues_cap_info = collections.defaultdict(list) formsemestre = FormSemestre.query.get_or_404(formsemestre_id) nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre) diff --git a/app/scodoc/sco_prepajury.py b/app/scodoc/sco_prepajury.py index 24619dcc..9ec0a95b 100644 --- a/app/scodoc/sco_prepajury.py +++ b/app/scodoc/sco_prepajury.py @@ -27,6 +27,7 @@ """Feuille excel pour préparation des jurys classiques (non BUT) """ +import collections import time from openpyxl.styles.numbers import FORMAT_NUMBER_00 @@ -64,10 +65,10 @@ def feuille_preparation_jury(formsemestre_id): "partition_id" ] - prev_moy_ue = scu.DictDefault(defaultvalue={}) # ue_code_s : { etudid : moy ue } + prev_moy_ue = collections.defaultdict(dict) # ue_code_s : { etudid : moy ue } prev_ue_acro = {} # ue_code_s : acronyme (à afficher) prev_moy = {} # moyennes gen sem prec - moy_ue = scu.DictDefault(defaultvalue={}) # ue_acro : moyennes { etudid : moy ue } + moy_ue = collections.defaultdict(dict) # ue_acro : moyennes { etudid : moy ue } ue_acro = {} # ue_code_s : acronyme (à afficher) moy = {} # moyennes gen moy_inter = {} # moyenne gen. sur les 2 derniers semestres diff --git a/app/scodoc/sco_pv_forms.py b/app/scodoc/sco_pv_forms.py index 46cf7d61..af6cb65b 100644 --- a/app/scodoc/sco_pv_forms.py +++ b/app/scodoc/sco_pv_forms.py @@ -29,7 +29,7 @@ Formulaires paramétrage PV et génération des tables """ - +import collections import time from reportlab.platypus import Paragraph from reportlab.lib import styles @@ -283,7 +283,7 @@ def formsemestre_pvjury(formsemestre_id, format="html", publish=True): H.append(tab.html()) # Count number of cases for each decision - counts = scu.DictDefault() + counts = collections.defaultdict(int) for row in rows: counts[row["decision"]] += 1 # add codes for previous (for explanation, without count) diff --git a/app/scodoc/sco_report.py b/app/scodoc/sco_report.py index 3d8bb3c7..4cb0d7ca 100644 --- a/app/scodoc/sco_report.py +++ b/app/scodoc/sco_report.py @@ -29,6 +29,7 @@ - statistiques decisions - suivi cohortes """ +import collections import os import tempfile import re @@ -178,7 +179,8 @@ def _results_by_category( if etud[category] in Count: Count[etud[category]][etud[result]] += 1 else: - Count[etud[category]] = scu.DictDefault(kv_dict={etud[result]: 1}) + Count[etud[category]] = collections.defaultdict(int, {etud[result]: 1}) + # conversion en liste de dict C = [Count[cat] for cat in categories] # Totaux par lignes et colonnes @@ -551,7 +553,7 @@ def table_suivi_cohorte( indices_sems.sort() for p in P: p.nb_etuds = 0 # nombre total d'etudiants dans la periode - p.sems_by_id = scu.DictDefault(defaultvalue=[]) + p.sems_by_id = collections.defaultdict(list) for s in p.sems: p.sems_by_id[s["semestre_id"]].append(s) p.nb_etuds += len(s["members"]) @@ -1162,7 +1164,7 @@ def table_suivi_cursus(formsemestre_id, only_primo=False, grouped_parcours=True) civilites, statuts, ) = tsp_etud_list(formsemestre_id, only_primo=only_primo) - codes_etuds = scu.DictDefault(defaultvalue=[]) + codes_etuds = collections.defaultdict(list) for etud in etuds: etud["code_cursus"], etud["decisions_jury"] = get_code_cursus_etud(etud) codes_etuds[etud["code_cursus"]].append(etud) @@ -1350,17 +1352,16 @@ def graph_cursus( civilites, statuts, ) - edges = scu.DictDefault( - defaultvalue=set() - ) # {("SEM"formsemestre_id_origin, "SEM"formsemestre_id_dest) : etud_set} + edges = collections.defaultdict(set) + # {("SEM"formsemestre_id_origin, "SEM"formsemestre_id_dest) : etud_set} def sem_node_name(sem, prefix="SEM"): "pydot node name for this integer id" return prefix + str(sem["formsemestre_id"]) sems = {} - effectifs = scu.DictDefault(defaultvalue=set()) # formsemestre_id : etud_set - decisions = scu.DictDefault(defaultvalue={}) # formsemestre_id : { code : nb_etud } + effectifs = collections.defaultdict(set) # formsemestre_id : etud_set + decisions = collections.defaultdict(dict) # formsemestre_id : { code : nb_etud } isolated_nodes = [] # [ node_name_de_formsemestre_id, ... ] connected_nodes = set() # { node_name_de_formsemestre_id } diploma_nodes = [] diff --git a/app/scodoc/sco_utils.py b/app/scodoc/sco_utils.py index d56466c3..03d3ecc9 100644 --- a/app/scodoc/sco_utils.py +++ b/app/scodoc/sco_utils.py @@ -30,7 +30,7 @@ """ import base64 import bisect -import copy +import collections import datetime from enum import IntEnum import io @@ -270,32 +270,11 @@ def get_mention(moy): return "" -class DictDefault(dict): # obsolete, use collections.defaultdict - """A dictionnary with default value for all keys - Each time a non existent key is requested, it is added to the dict. - (used in python 2.4, can't use new __missing__ method) - """ - - defaultvalue = 0 - - def __init__(self, defaultvalue=0, kv_dict={}): - dict.__init__(self) - self.defaultvalue = defaultvalue - self.update(kv_dict) - - def __getitem__(self, k): - if k in self: - return self.get(k) - value = copy.copy(self.defaultvalue) - self[k] = value - return value - - -def group_by_key(d, key): - gr = DictDefault(defaultvalue=[]) +def group_by_key(d: dict, key) -> dict: + grouped = collections.defaultdict(lambda: []) for e in d: - gr[e[key]].append(e) - return gr + grouped[e[key]].append(e) + return grouped # ----- Global lock for critical sections (except notes_tables caches)