diff --git a/app/models/etudiants.py b/app/models/etudiants.py index 6bed20db..359ca35e 100644 --- a/app/models/etudiants.py +++ b/app/models/etudiants.py @@ -205,6 +205,19 @@ class Identite(db.Model): d.update(adresse.to_dict(convert_nulls_to_str=True)) return d + def inscriptions(self) -> list["FormSemestreInscription"]: + "Liste des inscriptions à des formsemestres, triée, la plus récente en tête" + from app.models.formsemestre import FormSemestre, FormSemestreInscription + + return ( + FormSemestreInscription.query.join(FormSemestreInscription.formsemestre) + .filter( + FormSemestreInscription.etudid == self.id, + ) + .order_by(desc(FormSemestre.date_debut)) + .all() + ) + def inscription_courante(self): """La première inscription à un formsemestre _actuellement_ en cours. None s'il n'y en a pas (ou plus, ou pas encore). @@ -216,7 +229,7 @@ class Identite(db.Model): ] return r[0] if r else None - def inscriptions_courantes(self) -> list: # -> list[FormSemestreInscription]: + def inscriptions_courantes(self) -> list["FormSemestreInscription"]: """Liste des inscriptions à des semestres _courants_ (il est rare qu'il y en ai plus d'une, mais c'est possible). Triées par date de début de semestre décroissante (le plus récent en premier). @@ -244,18 +257,6 @@ class Identite(db.Model): ] return r[0] if r else None - def inscription_etat(self, formsemestre_id): - """État de l'inscription de cet étudiant au semestre: - False si pas inscrit, ou scu.INSCRIT, DEMISSION, DEF - """ - # voir si ce n'est pas trop lent: - ins = models.FormSemestreInscription.query.filter_by( - etudid=self.id, formsemestre_id=formsemestre_id - ).first() - if ins: - return ins.etat - return False - def inscription_descr(self) -> dict: """Description de l'état d'inscription""" inscription_courante = self.inscription_courante() @@ -294,6 +295,18 @@ class Identite(db.Model): "situation": situation, } + def inscription_etat(self, formsemestre_id): + """État de l'inscription de cet étudiant au semestre: + False si pas inscrit, ou scu.INSCRIT, DEMISSION, DEF + """ + # voir si ce n'est pas trop lent: + ins = models.FormSemestreInscription.query.filter_by( + etudid=self.id, formsemestre_id=formsemestre_id + ).first() + if ins: + return ins.etat + return False + def descr_situation_etud(self) -> str: """Chaîne décrivant la situation _actuelle_ de l'étudiant. Exemple: diff --git a/app/models/formsemestre.py b/app/models/formsemestre.py index 8d982847..99dd4a59 100644 --- a/app/models/formsemestre.py +++ b/app/models/formsemestre.py @@ -487,6 +487,19 @@ class FormSemestre(db.Model): etudid, self.date_debut.isoformat(), self.date_fin.isoformat() ) + def get_codes_apogee(self, category=None) -> set[str]: + """Les codes Apogée (codés en base comme "VRT1,VRT2") + category: None: tous, "etapes": étapes associées, "sem: code semestre", "annee": code annuel + """ + codes = set() + if category is None or category == "etapes": + codes |= {e.etape_apo for e in self.etapes if e} + if (category is None or category == "sem") and self.elt_sem_apo: + codes |= {x.strip() for x in self.elt_sem_apo.split(",") if x} + if (category is None or category == "annee") and self.elt_annee_apo: + codes |= {x.strip() for x in self.elt_annee_apo.split(",") if x} + return codes + def get_inscrits(self, include_demdef=False, order=False) -> list[Identite]: """Liste des étudiants inscrits à ce semestre Si include_demdef, tous les étudiants, avec les démissionnaires diff --git a/app/models/modules.py b/app/models/modules.py index 793c75a6..b3655772 100644 --- a/app/models/modules.py +++ b/app/models/modules.py @@ -178,7 +178,7 @@ class Module(db.Model): def get_codes_apogee(self) -> set[str]: """Les codes Apogée (codés en base comme "VRT1,VRT2")""" if self.code_apogee: - return {x.strip() for x in self.code_apogee.split(",")} + return {x.strip() for x in self.code_apogee.split(",") if x} return set() diff --git a/app/models/ues.py b/app/models/ues.py index 6d4efabc..450482bc 100644 --- a/app/models/ues.py +++ b/app/models/ues.py @@ -124,5 +124,5 @@ class UniteEns(db.Model): def get_codes_apogee(self) -> set[str]: """Les codes Apogée (codés en base comme "VRT1,VRT2")""" if self.code_apogee: - return {x.strip() for x in self.code_apogee.split(",")} + return {x.strip() for x in self.code_apogee.split(",") if x} return set() diff --git a/app/scodoc/sco_apogee_csv.py b/app/scodoc/sco_apogee_csv.py index 10f5e4b5..e309beda 100644 --- a/app/scodoc/sco_apogee_csv.py +++ b/app/scodoc/sco_apogee_csv.py @@ -258,11 +258,16 @@ class ApoEtud(dict): self["nom"] = nom self["prenom"] = prenom self["naissance"] = naissance - self.cols = cols # { col_id : value } colid = 'apoL_c0001' + self.cols = cols + "{ col_id : value } colid = 'apoL_c0001'" + self.col_elts = {} + "{'V1RT': {'R': 'ADM', 'J': '', 'B': 20, 'N': '12.14'}}" self.new_cols = {} # { col_id : value to record in csv } - self.etud = None # etud ScoDoc + self.etud: Identite = None + "etudiant ScoDoc associé" self.etat = None # ETUD_OK, ... - self.is_NAR = False # set to True si NARé dans un semestre + self.is_NAR = False + "True si NARé dans un semestre" self.log = [] self.has_logged_no_decision = False self.export_res_etape = export_res_etape # VET, ... @@ -276,7 +281,7 @@ class ApoEtud(dict): ) def __repr__(self): - return "ApoEtud( nom='%s', nip='%s' )" % (self["nom"], self["nip"]) + return f"""ApoEtud( nom='{self["nom"]}', nip='{self["nip"]}' )""" def lookup_scodoc(self, etape_formsemestre_ids): """Cherche l'étudiant ScoDoc associé à cet étudiant Apogée. @@ -284,6 +289,10 @@ class ApoEtud(dict): met .etud à None. Sinon, cherche le semestre, et met l'état à ETUD_OK ou ETUD_NON_INSCRIT. """ + + # futur: #WIP + # etud: Identite = Identite.query.filter_by(code_nip=self["nip"]).first() + # self.etud = etud etuds = sco_etud.get_etud_info(code_nip=self["nip"], filled=True) if not etuds: # pas dans ScoDoc @@ -291,13 +300,16 @@ class ApoEtud(dict): self.log.append("non inscrit dans ScoDoc") self.etat = ETUD_ORPHELIN else: + # futur: #WIP + # formsemestre_ids = { + # ins.formsemestre_id for ins in etud.formsemestre_inscriptions + # } + # in_formsemestre_ids = formsemestre_ids.intersection(etape_formsemestre_ids) self.etud = etuds[0] # cherche le semestre ScoDoc correspondant à l'un de ceux de l'etape: formsemestre_ids = {s["formsemestre_id"] for s in self.etud["sems"]} - self.in_formsemestre_ids = formsemestre_ids.intersection( - etape_formsemestre_ids - ) - if not self.in_formsemestre_ids: + in_formsemestre_ids = formsemestre_ids.intersection(etape_formsemestre_ids) + if not in_formsemestre_ids: self.log.append( "connu dans ScoDoc, mais pas inscrit dans un semestre de cette étape" ) @@ -305,7 +317,7 @@ class ApoEtud(dict): else: self.etat = ETUD_OK - def associate_sco(self, apo_data): + def associate_sco(self, apo_data: "ApoData"): """Recherche les valeurs des éléments Apogée pour cet étudiant Set .new_cols """ @@ -327,7 +339,7 @@ class ApoEtud(dict): cur_sem, autre_sem = self.etud_semestres_de_etape(apo_data) for sem in apo_data.sems_etape: el = self.search_elt_in_sem(code, sem, cur_sem, autre_sem) - if el != None: + if el is not None: sco_elts[code] = el break self.col_elts[code] = el @@ -338,15 +350,15 @@ class ApoEtud(dict): self.new_cols[col_id] = sco_elts[code][ apo_data.cols[col_id]["Type Rés."] ] - except KeyError: + except KeyError as exc: log( - "associate_sco: missing key, etud=%s\ncode='%s'\netape='%s'" - % (self, code, apo_data.etape_apogee) + f"associate_sco: missing key, etud={self}\ncode='{code}'\netape='{apo_data.etape_apogee}'" ) raise ScoValueError( - """L'élément %s n'a pas de résultat: peut-être une erreur dans les codes sur le programme pédagogique (vérifier qu'il est bien associé à une UE ou semestre)?""" - % code - ) + f"""L'élément {code} n'a pas de résultat: peut-être une erreur + dans les codes sur le programme pédagogique + (vérifier qu'il est bien associé à une UE ou semestre)?""" + ) from exc # recopie les 4 premieres colonnes (nom, ..., naissance): for col_id in apo_data.col_ids[:4]: self.new_cols[col_id] = self.cols[col_id] @@ -356,7 +368,7 @@ class ApoEtud(dict): # codes = set([apo_data.cols[col_id].code for col_id in apo_data.col_ids]) # return codes - set(sco_elts) - def search_elt_in_sem(self, code, sem, cur_sem, autre_sem): + def search_elt_in_sem(self, code, sem, cur_sem, autre_sem) -> dict: """ VET code jury etape ELP élément pédagogique: UE, module @@ -820,10 +832,8 @@ class ApoData(object): elts[col["Code"]] = ApoElt([col]) return elts # { code apo : ApoElt } - def apo_read_etuds(self, f): - """Lecture des etudiants (et resultats) du fichier CSV Apogée - -> liste de dicts - """ + def apo_read_etuds(self, f) -> list[ApoEtud]: + """Lecture des etudiants (et resultats) du fichier CSV Apogée""" L = [] while True: line = f.readline() @@ -958,8 +968,11 @@ class ApoData(object): """ codes_by_sem = {} for sem in self.sems_etape: - formsemestre = FormSemestre.query.get_or_404(sem["formsemestre_id"]) - # L'ensemble des codes apo associés aux modules: + formsemestre: FormSemestre = FormSemestre.query.get_or_404( + sem["formsemestre_id"] + ) + # L'ensemble des codes apo associés aux éléments: + codes_semestre = formsemestre.get_codes_apogee() codes_modules = set().union( *[ modimpl.module.get_codes_apogee() @@ -976,12 +989,8 @@ class ApoData(object): codes_by_sem[sem["formsemestre_id"]] = s for col_id in self.col_ids[4:]: code = self.cols[col_id]["Code"] # 'V1RT' - # associé à l'étape, l'année ou les semestre: - if ( - sco_formsemestre.sem_has_etape(sem, code) - or (code in {x.strip() for x in sem["elt_sem_apo"].split(",")}) - or (code in {x.strip() for x in sem["elt_annee_apo"].split(",")}) - ): + # associé à l'étape, l'année ou le semestre: + if code in codes_semestre: s.add(code) continue # associé à une UE: diff --git a/app/scodoc/sco_parcours_dut.py b/app/scodoc/sco_parcours_dut.py index 8c1d23a4..48c92318 100644 --- a/app/scodoc/sco_parcours_dut.py +++ b/app/scodoc/sco_parcours_dut.py @@ -109,7 +109,7 @@ class DecisionSem(object): # log('%s: %s %s %s %s %s' % (self.codechoice,code_etat,new_code_prev,formsemestre_id_utilise_pour_compenser,devenir,assiduite) ) -def SituationEtudParcours(etud, formsemestre_id): +def SituationEtudParcours(etud: dict, formsemestre_id: int): """renvoie une instance de SituationEtudParcours (ou sous-classe spécialisée)""" formsemestre = FormSemestre.query.get_or_404(formsemestre_id) nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre) @@ -124,7 +124,7 @@ def SituationEtudParcours(etud, formsemestre_id): class SituationEtudParcoursGeneric(object): "Semestre dans un parcours" - def __init__(self, etud, formsemestre_id, nt): + def __init__(self, etud: dict, formsemestre_id: int, nt: NotesTableCompat): """ etud: dict filled by fill_etuds_info() """ @@ -132,7 +132,7 @@ class SituationEtudParcoursGeneric(object): self.etudid = etud["etudid"] self.formsemestre_id = formsemestre_id self.sem = sco_formsemestre.get_formsemestre(formsemestre_id) - self.nt = nt + self.nt: NotesTableCompat = nt self.formation = self.nt.formsemestre.formation self.parcours = self.nt.parcours # Ce semestre est-il le dernier de la formation ? (e.g. semestre 4 du DUT)