diff --git a/app/models/but_validations.py b/app/models/but_validations.py index 8c23694d..adf0d203 100644 --- a/app/models/but_validations.py +++ b/app/models/but_validations.py @@ -113,6 +113,12 @@ class ApcValidationRCUE(db.Model): "formsemestre_id": self.formsemestre_id, } + def get_codes_apogee(self) -> set[str]: + """Les codes Apogée associés à cette validation RCUE. + Prend les codes des deux UEs + """ + return self.ue1.get_codes_apogee_rcue() | self.ue2.get_codes_apogee_rcue() + class ApcValidationAnnee(db.Model): """Validation des années du BUT""" diff --git a/app/models/formsemestre.py b/app/models/formsemestre.py index c3623bb3..39c657aa 100644 --- a/app/models/formsemestre.py +++ b/app/models/formsemestre.py @@ -610,6 +610,41 @@ class FormSemestre(models.ScoDocModel): ) ) + @classmethod + def est_in_semestre_scolaire( + cls, + date_debut: datetime.date, + year=False, + periode=None, + mois_pivot_annee=scu.MONTH_DEBUT_ANNEE_SCOLAIRE, + mois_pivot_periode=scu.MONTH_DEBUT_PERIODE2, + ) -> bool: + """Vrai si la date_debut est dans la période indiquée (1,2,0) + du semestre `periode` de l'année scolaire indiquée + (ou, à défaut, de celle en cours). + + La période utilise les même conventions que semset["sem_id"]; + * 1 : première période + * 2 : deuxième période + * 0 ou période non précisée: annualisé (donc inclut toutes les périodes) + ) + """ + if not year: + year = scu.annee_scolaire() + # n'utilise pas le jour pivot + jour_pivot_annee = jour_pivot_periode = 1 + # calcule l'année universitaire et la période + sem_annee, sem_periode = cls.comp_periode( + date_debut, + mois_pivot_annee, + mois_pivot_periode, + jour_pivot_annee, + jour_pivot_periode, + ) + if periode is None or periode == 0: + return sem_annee == year + return sem_annee == year and sem_periode == periode + def est_terminal(self) -> bool: "Vrai si dernier semestre de son cursus (ou formation mono-semestre)" return (self.semestre_id < 0) or ( @@ -1225,9 +1260,17 @@ class FormSemestreEtape(db.Model): "Etape False if code empty" return self.etape_apo is not None and (len(self.etape_apo) > 0) + def __eq__(self, other): + if isinstance(other, ApoEtapeVDI): + return self.as_apovdi() == other + return str(self) == str(other) + def __repr__(self): return f"" + def __str__(self): + return self.etape_apo or "" + def as_apovdi(self) -> ApoEtapeVDI: return ApoEtapeVDI(self.etape_apo) @@ -1381,8 +1424,9 @@ class FormSemestreInscription(db.Model): def __repr__(self): return f"""<{self.__class__.__name__} {self.id} etudid={self.etudid} sem={ - self.formsemestre_id} etat={self.etat} { - ('parcours='+str(self.parcour)) if self.parcour else ''}>""" + self.formsemestre_id} (S{self.formsemestre.semestre_id}) etat={self.etat} { + ('parcours="'+str(self.parcour.code)+'"') if self.parcour else '' + } {('etape="'+self.etape+'"') if self.etape else ''}>""" class NotesSemSet(db.Model): diff --git a/app/models/ues.py b/app/models/ues.py index 51467c7e..d6282fc7 100644 --- a/app/models/ues.py +++ b/app/models/ues.py @@ -276,6 +276,12 @@ class UniteEns(models.ScoDocModel): return {x.strip() for x in self.code_apogee.split(",") if x} return set() + def get_codes_apogee_rcue(self) -> set[str]: + """Les codes Apogée RCUE (codés en base comme "VRT1,VRT2")""" + if self.code_apogee_rcue: + return {x.strip() for x in self.code_apogee_rcue.split(",") if x} + return set() + def _parcours_niveaux_ids(self, parcours=list[ApcParcours]) -> set[int]: """set des ids de niveaux communs à tous les parcours listés""" return set.intersection( diff --git a/app/scodoc/sco_apogee_csv.py b/app/scodoc/sco_apogee_csv.py index 7feea971..80d1e8e4 100644 --- a/app/scodoc/sco_apogee_csv.py +++ b/app/scodoc/sco_apogee_csv.py @@ -43,12 +43,11 @@ import re import time from zipfile import ZipFile -from flask import send_file +from flask import g, send_file import numpy as np from app import log -from app.but import jury_but from app.comp import res_sem from app.comp.res_compat import NotesTableCompat from app.models import ( @@ -79,7 +78,6 @@ from app.scodoc.codes_cursus import ( ) from app.scodoc import sco_cursus from app.scodoc import sco_formsemestre -from app.scodoc import sco_etud def _apo_fmt_note(note, fmt="%3.2f"): @@ -99,7 +97,7 @@ class EtuCol: """Valeurs colonnes d'un element pour un etudiant""" def __init__(self, nip, apo_elt, init_vals): - pass # XXX + pass ETUD_OK = "ok" @@ -150,9 +148,9 @@ class ApoEtud(dict): _apo_fmt_note, fmt=ScoDocSiteConfig.get_code_apo("NOTES_FMT") or "3.2f" ) # Initialisés par associate_sco: - self.autre_sem: dict = None + self.autre_formsemestre: FormSemestre = None self.autre_res: NotesTableCompat = None - self.cur_sem: dict = None + self.cur_formsemestre: FormSemestre = None self.cur_res: NotesTableCompat = None self.new_cols = {} "{ col_id : value to record in csv }" @@ -222,7 +220,8 @@ class ApoEtud(dict): self.new_cols[col_id] = self.cols[col_id] except KeyError as exc: raise ScoFormatError( - f"""Fichier Apogee invalide : ligne mal formatée ?
colonne {col_id} non déclarée ?""" + f"""Fichier Apogee invalide : ligne mal formatée ?
colonne { + col_id} non déclarée ?""" ) from exc else: try: @@ -248,7 +247,7 @@ class ApoEtud(dict): # codes = set([apo_data.apo_csv.cols[col_id].code for col_id in apo_data.apo_csv.col_ids]) # return codes - set(sco_elts) - def search_elt_in_sem(self, code, sem) -> dict: + def search_elt_in_sem(self, code: str, sem: dict) -> dict: """ VET code jury etape (en BUT, le code annuel) ELP élément pédagogique: UE, module @@ -263,8 +262,8 @@ class ApoEtud(dict): sem (dict): semestre dans lequel on cherche l'élément Utilise notamment: - cur_sem (dict): semestre "courant" pour résultats annuels (VET) - autre_sem (dict): autre semestre utilisé pour calculer les résultats annuels (VET) + cur_formsemestre : semestre "courant" pour résultats annuels (VET) + autre_formsemestre : autre formsemestre utilisé pour les résultats annuels (VET) Returns: dict: with N, B, J, R keys, ou None si elt non trouvé @@ -314,10 +313,10 @@ class ApoEtud(dict): code in {x.strip() for x in sem["elt_annee_apo"].split(",")} ): export_res_etape = self.export_res_etape - if (not export_res_etape) and self.cur_sem: + if (not export_res_etape) and self.cur_formsemestre: # exporte toujours le résultat de l'étape si l'étudiant est diplômé Se = sco_cursus.get_situation_etud_cursus( - self.etud, self.cur_sem["formsemestre_id"] + self.etud, self.cur_formsemestre.id ) export_res_etape = Se.all_other_validated() @@ -375,8 +374,20 @@ class ApoEtud(dict): if module_code_found: return VOID_APO_RES + # RCUE du BUT - deca = jury_but.DecisionsProposeesAnnee(self.etud, formsemestre) + if res.is_apc: + for val_rcue in ApcValidationRCUE.query.filter_by( + etudid=etudid, formsemestre_id=sem["formsemestre_id"] + ): + if code in val_rcue.get_codes_apogee(): + return dict( + N="", # n'exporte pas de moyenne RCUE + B=20, + J="", + R=ScoDocSiteConfig.get_code_apo(val_rcue.code), + M="", + ) # return None # element Apogee non trouvé dans ce semestre @@ -418,11 +429,10 @@ class ApoEtud(dict): # # XXX cette règle est discutable, à valider - # log('comp_elt_annuel cur_sem=%s autre_sem=%s' % (cur_sem['formsemestre_id'], autre_sem['formsemestre_id'])) - if not self.cur_sem: + if not self.cur_formsemestre: # l'étudiant n'a pas de semestre courant ?! self.log.append("pas de semestre courant") - log(f"comp_elt_annuel: etudid {etudid} has no cur_sem") + log(f"comp_elt_annuel: etudid {etudid} has no cur_formsemestre") return VOID_APO_RES if self.is_apc: @@ -438,7 +448,7 @@ class ApoEtud(dict): # ne touche pas aux RATs return VOID_APO_RES - if not self.autre_sem: + if not self.autre_formsemestre: # formations monosemestre, ou code VET semestriel, # ou jury intermediaire et etudiant non redoublant... return self.comp_elt_semestre(self.cur_res, cur_decision, etudid) @@ -518,7 +528,7 @@ class ApoEtud(dict): self.validation_annee_but: ApcValidationAnnee = ( ApcValidationAnnee.query.filter_by( formsemestre_id=formsemestre.id, - etudid=self.etud["etudid"], + etudid=self.etud.id, referentiel_competence_id=self.cur_res.formsemestre.formation.referentiel_competence_id, ).first() ) @@ -527,7 +537,7 @@ class ApoEtud(dict): ) def etud_set_semestres_de_etape(self, apo_data: "ApoData"): - """Set .cur_sem and .autre_sem et charge les résultats. + """Set .cur_formsemestre and .autre_formsemestre et charge les résultats. Lorsqu'on a une formation semestrialisée mais avec un code étape annuel, il faut considérer les deux semestres ((S1,S2) ou (S3,S4)) pour calculer le code annuel (VET ou VRT1A (voir elt_annee_apo)). @@ -535,52 +545,49 @@ class ApoEtud(dict): Pour les jurys intermediaires (janvier, S1 ou S3): (S2 ou S4) de la même étape lors d'une année précédente ? - Set cur_sem: le semestre "courant" et autre_sem, ou None s'il n'y en a pas. + Set cur_formsemestre: le formsemestre "courant" + et autre_formsemestre, ou None s'il n'y en a pas. """ - # Cherche le semestre "courant": - cur_sems = [ - sem - for sem in self.etud["sems"] + # Cherche le formsemestre "courant": + cur_formsemestres = [ + formsemestre + for formsemestre in self.etud.get_formsemestres() if ( - (sem["semestre_id"] == apo_data.cur_semestre_id) - and (apo_data.etape in sem["etapes"]) + (formsemestre.semestre_id == apo_data.cur_semestre_id) + and (apo_data.etape in formsemestre.etapes) and ( - sco_formsemestre.sem_in_semestre_scolaire( - sem, + FormSemestre.est_in_semestre_scolaire( + formsemestre.date_debut, apo_data.annee_scolaire, 0, # annee complete ) ) ) ] - if not cur_sems: - cur_sem = None - else: - # prend le plus recent avec decision - cur_sem = None - for sem in cur_sems: - formsemestre = FormSemestre.query.get_or_404(sem["formsemestre_id"]) + cur_formsemestre = None + if cur_formsemestres: + # prend le plus récent avec décision + for formsemestre in cur_formsemestres: res: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre) - has_decision = res.etud_has_decision(self.etud["etudid"]) + has_decision = res.etud_has_decision(self.etud.id) if has_decision: - cur_sem = sem + cur_formsemestre = formsemestre self.cur_res = res break - if cur_sem is None: - cur_sem = cur_sems[0] # aucun avec décision, prend le plus recent - if res.formsemestre.id == cur_sem["formsemestre_id"]: + if cur_formsemestres is None: + cur_formsemestre = cur_formsemestres[ + 0 + ] # aucun avec décision, prend le plus recent + if res.formsemestre.id == cur_formsemestre.id: self.cur_res = res else: - formsemestre = FormSemestre.query.get_or_404( - cur_sem["formsemestre_id"] - ) - self.cur_res = res_sem.load_formsemestre_results(formsemestre) + self.cur_res = res_sem.load_formsemestre_results(cur_formsemestre) - self.cur_sem = cur_sem + self.cur_formsemestre = cur_formsemestre if apo_data.cur_semestre_id <= 0: - # "autre_sem" non pertinent pour sessions sans semestres: - self.autre_sem = None + # autre_formsemestre non pertinent pour sessions sans semestres: + self.autre_formsemestre = None self.autre_res = None return @@ -601,52 +608,49 @@ class ApoEtud(dict): courant_mois_debut = 1 # ou 2 (fev-jul) else: raise ValueError("invalid periode value !") # bug ? - courant_date_debut = "%d-%02d-01" % ( - courant_annee_debut, - courant_mois_debut, + courant_date_debut = datetime.date( + day=1, month=courant_mois_debut, year=courant_annee_debut ) else: - courant_date_debut = "9999-99-99" + courant_date_debut = datetime.date(day=31, month=12, year=9999) - # etud['sems'] est la liste des semestres de l'étudiant, triés par date, - # le plus récemment effectué en tête. # Cherche les semestres (antérieurs) de l'indice autre de la même étape apogée # s'il y en a plusieurs, choisit le plus récent ayant une décision autres_sems = [] - for sem in self.etud["sems"]: + for formsemestre in self.etud.get_formsemestres(): if ( - sem["semestre_id"] == autre_semestre_id - and apo_data.etape_apogee in sem["etapes"] + formsemestre.semestre_id == autre_semestre_id + and apo_data.etape_apogee in formsemestre.etapes ): if ( - sem["date_debut_iso"] < courant_date_debut + formsemestre.date_debut < courant_date_debut ): # on demande juste qu'il ait démarré avant - autres_sems.append(sem) + autres_sems.append(formsemestre) if not autres_sems: - autre_sem = None + autre_formsemestre = None elif len(autres_sems) == 1: - autre_sem = autres_sems[0] + autre_formsemestre = autres_sems[0] else: - autre_sem = None - for sem in autres_sems: - formsemestre = FormSemestre.query.get_or_404(sem["formsemestre_id"]) + autre_formsemestre = None + for formsemestre in autres_sems: res: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre) if res.is_apc: - has_decision = res.etud_has_decision(self.etud["etudid"]) + has_decision = res.etud_has_decision(self.etud.id) else: - has_decision = res.get_etud_decision_sem(self.etud["etudid"]) + has_decision = res.get_etud_decision_sem(self.etud.id) if has_decision: - autre_sem = sem + autre_formsemestre = formsemestre break - if autre_sem is None: - autre_sem = autres_sems[0] # aucun avec decision, prend le plus recent + if autre_formsemestre is None: + autre_formsemestre = autres_sems[ + 0 + ] # aucun avec decision, prend le plus recent - self.autre_sem = autre_sem + self.autre_formsemestre = autre_formsemestre # Charge les résultats: - if autre_sem: - formsemestre = FormSemestre.query.get_or_404(autre_sem["formsemestre_id"]) - self.autre_res = res_sem.load_formsemestre_results(formsemestre) + if autre_formsemestre: + self.autre_res = res_sem.load_formsemestre_results(self.autre_formsemestre) else: self.autre_res = None @@ -873,6 +877,16 @@ class ApoData: codes_ues = set().union( *[ue.get_codes_apogee() for ue in formsemestre.get_ues(with_sport=True)] ) + codes_rcues = ( + set().union( + *[ + ue.get_codes_apogee_rcue() + for ue in formsemestre.get_ues(with_sport=True) + ] + ) + if self.is_apc + else set() + ) s = set() codes_by_sem[sem["formsemestre_id"]] = s for col_id in self.apo_csv.col_ids[4:]: @@ -885,9 +899,14 @@ class ApoData: if code in codes_ues: s.add(code) continue + # associé à un RCUE BUT + if code in codes_rcues: + s.add(code) + continue # associé à un module: if code in codes_modules: s.add(code) + # log('codes_by_sem=%s' % pprint.pformat(codes_by_sem)) return codes_by_sem diff --git a/app/scodoc/sco_archives.py b/app/scodoc/sco_archives.py index 56d501ab..44acd08a 100644 --- a/app/scodoc/sco_archives.py +++ b/app/scodoc/sco_archives.py @@ -139,7 +139,7 @@ class BaseArchiver: dirs = glob.glob(base + "*") return [os.path.split(x)[1] for x in dirs] - def list_obj_archives(self, oid: int, dept_id: int = None): + def list_obj_archives(self, oid: int, dept_id: int = None) -> list[str]: """Returns :return: list of archive identifiers for this object (paths to non empty dirs) """ diff --git a/app/scodoc/sco_cursus_dut.py b/app/scodoc/sco_cursus_dut.py index 6c1344a4..7cb14eb1 100644 --- a/app/scodoc/sco_cursus_dut.py +++ b/app/scodoc/sco_cursus_dut.py @@ -291,14 +291,14 @@ class SituationEtudCursusClassic(SituationEtudCursus): if s_idx == NO_SEMESTRE_ID or s_idx > self.parcours.NB_SEM - 2: return False # n+2 en dehors du parcours if self._sem_list_validated(set(range(1, s_idx))): - # antérieurs validé, teste suivant + # antérieurs validés, teste suivant n1 = s_idx + 1 - for sem in self.get_semestres(): + for formsemestre in self.formsemestres: if ( - sem["semestre_id"] == n1 - and sem["formation_code"] == self.formation.formation_code + formsemestre.semestre_id == n1 + and formsemestre.formation.formation_code + == self.formation.formation_code ): - formsemestre = FormSemestre.query.get_or_404(sem["formsemestre_id"]) nt: NotesTableCompat = res_sem.load_formsemestre_results( formsemestre ) diff --git a/app/scodoc/sco_edit_ue.py b/app/scodoc/sco_edit_ue.py index 870421ea..152103ae 100644 --- a/app/scodoc/sco_edit_ue.py +++ b/app/scodoc/sco_edit_ue.py @@ -84,6 +84,7 @@ _ueEditor = ndb.EditableTable( "ects", "is_external", "code_apogee", + "code_apogee_rcue", "coefficient", "coef_rcue", "color", @@ -425,6 +426,20 @@ def ue_edit(ue_id=None, create=False, formation_id=None, default_semestre_idx=No "max_length": APO_CODE_STR_LEN, }, ), + ] + if is_apc: + form_descr += [ + ( + "code_apogee_rcue", + { + "title": "Code Apogée du RCUE", + "size": 25, + "explanation": "(optionnel) code(s) élément pédagogique Apogée du RCUE", + "max_length": APO_CODE_STR_LEN, + }, + ), + ] + form_descr += [ ( "is_external", { diff --git a/app/scodoc/sco_etape_apogee.py b/app/scodoc/sco_etape_apogee.py index a4fcdb02..e11e5713 100644 --- a/app/scodoc/sco_etape_apogee.py +++ b/app/scodoc/sco_etape_apogee.py @@ -247,9 +247,7 @@ def apo_csv_check_etape(semset, set_nips, etape_apo): return nips_ok, apo_nips, nips_no_apo, nips_no_sco, maq_elems, sem_elems -def apo_csv_semset_check( - semset, allow_missing_apo=False, allow_missing_csv=False -): # was apo_csv_check +def apo_csv_semset_check(semset, allow_missing_apo=False, allow_missing_csv=False): """ check students in stored maqs vs students in semset Cas à détecter: @@ -346,120 +344,3 @@ def apo_csv_retreive_etuds_by_nip(semset, nips): etuds[nip] = apo_etuds_by_nips.get(nip, {"nip": nip, "etape_apo": "?"}) return etuds - - -""" -Tests: - -from debug import * -from app.scodoc import sco_groups -from app.scodoc import sco_groups_view -from app.scodoc import sco_formsemestre -from app.scodoc.sco_etape_apogee import * -from app.scodoc.sco_apogee_csv import * -from app.scodoc.sco_semset import * - -app.set_sco_dept('RT') -csv_data = open('/opt/misc/VDTRT_V1RT.TXT').read() -annee_scolaire=2015 -sem_id=1 - -apo_data = sco_apogee_csv.ApoData(csv_data, periode=sem_id) -print apo_data.etape_apogee - -apo_data.setup() -e = apo_data.etuds[0] -e.lookup_scodoc( apo_data.etape_formsemestre_ids) -e.associate_sco( apo_data) - -print apo_csv_list_stored_archives() - - -# apo_csv_store(csv_data, annee_scolaire, sem_id) - - - -groups_infos = sco_groups_view.DisplayedGroupsInfos( [sco_groups.get_default_group(formsemestre_id)], formsemestre_id=formsemestre_id) - -formsemestre = FormSemestre.get_formsemestre(formsemestre_id) -nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre) -# -s = SemSet('NSS29902') -apo_data = sco_apogee_csv.ApoData(open('/opt/scodoc/var/scodoc/archives/apo_csv/RT/2015-2/2016-07-10-11-26-15/V1RT.csv').read(), periode=1) - -# cas Tiziri K. (inscrite en S1, démission en fin de S1, pas inscrite en S2) -# => pas de décision, ce qui est voulu (?) -# - -apo_data.setup() -e = [ e for e in apo_data.etuds if e['nom'] == 'XYZ' ][0] -e.lookup_scodoc( apo_data.etape_formsemestre_ids) -e.associate_sco(apo_data) - -self=e -col_id='apoL_c0129' - -# -- -from app.scodoc import sco_portal_apogee -_ = go_dept(app, 'GEA').Notes -#csv_data = sco_portal_apogee.get_maquette_apogee(etape='V1GE', annee_scolaire=2015) -csv_data = open('/tmp/V1GE.txt').read() -apo_data = sco_apogee_csv.ApoData(csv_data, periode=1) - - -# ------ -# les elements inconnus: - -from debug import * -from app.scodoc import sco_groups -from app.scodoc import sco_groups_view -from app.scodoc import sco_formsemestre -from app.scodoc.sco_etape_apogee import * -from app.scodoc.sco_apogee_csv import * -from app.scodoc.sco_semset import * - -_ = go_dept(app, 'RT').Notes -csv_data = open('/opt/misc/V2RT.csv').read() -annee_scolaire=2015 -sem_id=1 - -apo_data = sco_apogee_csv.ApoData(csv_data, periode=1) -print apo_data.etape_apogee - -apo_data.setup() -for e in apo_data.etuds: - e.lookup_scodoc( apo_data.etape_formsemestre_ids) - e.associate_sco(apo_data) - -# ------ -# test export jury intermediaire -from debug import * -from app.scodoc import sco_groups -from app.scodoc import sco_groups_view -from app.scodoc import sco_formsemestre -from app.scodoc.sco_etape_apogee import * -from app.scodoc.sco_apogee_csv import * -from app.scodoc.sco_semset import * - -_ = go_dept(app, 'CJ').Notes -csv_data = open('/opt/scodoc/var/scodoc/archives/apo_csv/CJ/2016-1/2017-03-06-21-46-32/V1CJ.csv').read() -annee_scolaire=2016 -sem_id=1 - -apo_data = sco_apogee_csv.ApoData(csv_data, periode=1) -print apo_data.etape_apogee - -apo_data.setup() -e = [ e for e in apo_data.etuds if e['nom'] == 'XYZ' ][0] # -e.lookup_scodoc( apo_data.etape_formsemestre_ids) -e.associate_sco(apo_data) - -self=e - -sco_elts = {} -col_id='apoL_c0001' -code = apo_data.cols[col_id]['Code'] # 'V1RT' - -sem = apo_data.sems_periode[0] # le S1 - -""" diff --git a/app/scodoc/sco_etape_apogee_view.py b/app/scodoc/sco_etape_apogee_view.py index 158fe9ae..9e461199 100644 --- a/app/scodoc/sco_etape_apogee_view.py +++ b/app/scodoc/sco_etape_apogee_view.py @@ -125,14 +125,19 @@ def apo_semset_maq_status( H.append("""

Aucune maquette chargée

""") # Upload fichier: H.append( - """
- Charger votre fichier maquette Apogée: + f""" +
+ {'Charger votre fichier' if tab_archives.is_empty() else 'Ajouter un autre fichier'} + maquette Apogée: +
- + autodétecter encodage
""" - % (semset_id,) ) # Récupération sur portail: maquette_url = sco_portal_apogee.get_maquette_url() @@ -335,7 +340,7 @@ def apo_semset_maq_status( missing = maq_elems - sem_elems H.append('
') H.append( - '

Elements Apogée: %s

' + '

Élements Apogée: %s

' % ", ".join( [ e if not e in missing else '' + e + "" @@ -351,7 +356,7 @@ def apo_semset_maq_status( ] H.append( f"""
- Elements Apogée absents dans ScoDoc: + Élements Apogée absents dans ScoDoc: { ", ".join(sorted(missing)) } @@ -442,11 +447,11 @@ def table_apo_csv_list(semset): annee_scolaire = semset["annee_scolaire"] sem_id = semset["sem_id"] - T = sco_etape_apogee.apo_csv_list_stored_archives( + rows = sco_etape_apogee.apo_csv_list_stored_archives( annee_scolaire, sem_id, etapes=semset.list_etapes() ) - for t in T: + for t in rows: # Ajoute qq infos pour affichage: csv_data = sco_etape_apogee.apo_csv_get(t["etape_apo"], annee_scolaire, sem_id) apo_data = sco_apogee_csv.ApoData(csv_data, periode=semset["sem_id"]) @@ -484,7 +489,7 @@ def table_apo_csv_list(semset): "date_str": "Enregistré le", }, columns_ids=columns_ids, - rows=T, + rows=rows, html_class="table_leftalign apo_maq_list", html_sortable=True, # base_url = '%s?formsemestre_id=%s' % (request.base_url, formsemestre_id), diff --git a/app/scodoc/sco_etape_bilan.py b/app/scodoc/sco_etape_bilan.py index fec1cb2d..fb2c8bdf 100644 --- a/app/scodoc/sco_etape_bilan.py +++ b/app/scodoc/sco_etape_bilan.py @@ -93,7 +93,7 @@ import json from flask import url_for, g -from app.scodoc.sco_portal_apogee import get_inscrits_etape +from app.scodoc import sco_portal_apogee from app import log from app.scodoc.sco_utils import annee_scolaire_debut from app.scodoc.gen_tables import GenTable @@ -136,11 +136,16 @@ class DataEtudiant(object): self.etudid = etudid self.data_apogee = None self.data_scodoc = None - self.etapes = set() # l'ensemble des étapes où il est inscrit - self.semestres = set() # l'ensemble des formsemestre_id où il est inscrit - self.tags = set() # les anomalies relevées - self.ind_row = "-" # là où il compte dans les effectifs (ligne et colonne) + self.etapes = set() + "l'ensemble des étapes où il est inscrit" + self.semestres = set() + "l'ensemble des formsemestre_id où il est inscrit" + self.tags = set() + "les anomalies relevées" + self.ind_row = "-" + "ligne où il compte dans les effectifs" self.ind_col = "-" + "colonne où il compte dans les effectifs" def add_etape(self, etape): self.etapes.add(etape) @@ -163,9 +168,9 @@ class DataEtudiant(object): def set_ind_col(self, indicatif): self.ind_col = indicatif - def get_identity(self): + def get_identity(self) -> str: """ - Calcul le nom/prénom de l'étudiant (données ScoDoc en priorité, sinon données Apogée) + Calcule le nom/prénom de l'étudiant (données ScoDoc en priorité, sinon données Apogée) :return: L'identité calculée """ if self.data_scodoc is not None: @@ -176,9 +181,12 @@ class DataEtudiant(object): def _help() -> str: return """ -
Explications sur les tableaux des effectifs et liste des - étudiants -

Le tableau des effectifs présente le nombre d'étudiants selon deux critères:

+
+ Explications sur les tableaux des effectifs + et liste des étudiants +
+

Le tableau des effectifs présente le nombre d'étudiants selon deux critères: +

  • En colonne le statut de l'étudiant par rapport à Apogée:
      @@ -406,7 +414,8 @@ class EtapeBilan: for key_etape in self.etapes: annee_apogee, etapestr = key_to_values(key_etape) self.etu_etapes[key_etape] = set() - for etud in get_inscrits_etape(etapestr, annee_apogee): + # get_inscrits_etape interroge portail Apo: + for etud in sco_portal_apogee.get_inscrits_etape(etapestr, annee_apogee): key_etu = self.register_etud_apogee(etud, key_etape) self.etu_etapes[key_etape].add(key_etu) @@ -444,7 +453,6 @@ class EtapeBilan: data_etu = self.etudiants[key_etu] ind_col = "-" ind_row = "-" - # calcul de la colonne if len(data_etu.etapes) == 1: ind_col = self.indicatifs[list(data_etu.etapes)[0]] @@ -478,32 +486,34 @@ class EtapeBilan: affichage de l'html :return: Le code html à afficher """ + if not sco_portal_apogee.has_portal(): + return """
      + Pas de portail Apogée configuré +
      """ self.load_listes() # chargement des données self.dispatch() # analyse et répartition # calcul de la liste des colonnes et des lignes de la table des effectifs self.all_rows_str = "'" + ",".join(["." + r for r in self.all_rows_ind]) + "'" self.all_cols_str = "'" + ",".join(["." + c for c in self.all_cols_ind]) + "'" - H = [ - """
      + return f""" +
      - Tableau des effectifs - - """, - self._diagtable(), - """
      """, - self.display_tags(), - """
      - Liste des étudiants - - """, - entete_liste_etudiant(), - self.table_effectifs(), - """
      """, - _help(), - ] - - return "\n".join(H) + Tableau des effectifs + + {self._diagtable()} + + {self.display_tags()} +
      + + Liste des étudiants + + {entete_liste_etudiant()} + {self.table_effectifs()} +
      + {_help()} +
      + """ def _inc_count(self, ind_row, ind_col): if (ind_row, ind_col) not in self.repartition: @@ -692,26 +702,34 @@ class EtapeBilan: return "\n".join(H) @staticmethod - def link_etu(etudid, nom): - return '%s' % ( - url_for("scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid), - nom, - ) + def link_etu(etudid, nom) -> str: + "Lien html vers fiche de l'étudiant" + return f"""{nom}""" - def link_semestre(self, semestre, short=False): - if short: - return ( - '%(' - "formsemestre_id)s " % self.semestres[semestre] - ) - else: - return ( - '%(titre_num)s' - " %(mois_debut)s - %(mois_fin)s)" % self.semestres[semestre] - ) + def link_semestre(self, semestre, short=False) -> str: + "Lien html vers tableau de bord semestre" + key = "session_id" if short else "titremois" + sem = self.semestres[semestre] + return f"""{sem[key]} + """ - def table_effectifs(self): - H = [] + def table_effectifs(self) -> str: + "Table html donnant les étudiants dans chaque semestre" + H = [ + """ + + """ + ] col_ids = ["tag", "etudiant", "prenom", "nip", "semestre", "apogee", "annee"] titles = { @@ -766,6 +784,7 @@ class EtapeBilan: titles, html_class="table_leftalign", html_sortable=True, + html_with_td_classes=True, table_id="apo-detail", ).gen(fmt="html") ) diff --git a/app/scodoc/sco_formations.py b/app/scodoc/sco_formations.py index 46f751e2..86893ebb 100644 --- a/app/scodoc/sco_formations.py +++ b/app/scodoc/sco_formations.py @@ -143,6 +143,7 @@ def formation_export_dict( if not export_codes_apo: ue_dict.pop("code_apogee", None) + ue_dict.pop("code_apogee_rcue", None) if ue_dict.get("ects") is None: ue_dict.pop("ects", None) mats = sco_edit_matiere.matiere_list({"ue_id": ue.id}) diff --git a/app/scodoc/sco_formsemestre.py b/app/scodoc/sco_formsemestre.py index f9a86475..0a9264ea 100644 --- a/app/scodoc/sco_formsemestre.py +++ b/app/scodoc/sco_formsemestre.py @@ -419,49 +419,23 @@ def sem_set_responsable_name(sem): ) -def sem_in_semestre_scolaire( - sem, - year=False, - periode=None, - mois_pivot_annee=scu.MONTH_DEBUT_ANNEE_SCOLAIRE, - mois_pivot_periode=scu.MONTH_DEBUT_PERIODE2, -) -> bool: - """Vrai si la date du début du semestre est dans la période indiquée (1,2,0) - du semestre `periode` de l'année scolaire indiquée - (ou, à défaut, de celle en cours). - - La période utilise les même conventions que semset["sem_id"]; - * 1 : première période - * 2 : deuxième période - * 0 ou période non précisée: annualisé (donc inclut toutes les périodes) - ) - """ - if not year: - year = scu.annee_scolaire() - # n'utilise pas le jour pivot - jour_pivot_annee = jour_pivot_periode = 1 - # calcule l'année universitaire et la période - sem_annee, sem_periode = FormSemestre.comp_periode( - datetime.datetime.fromisoformat(sem["date_debut_iso"]), - mois_pivot_annee, - mois_pivot_periode, - jour_pivot_annee, - jour_pivot_periode, - ) - if periode is None or periode == 0: - return sem_annee == year - return sem_annee == year and sem_periode == periode - - -def sem_in_annee_scolaire(sem, year=False): +def sem_in_annee_scolaire(sem: dict, year=False): # OBSOLETE """Test si sem appartient à l'année scolaire year (int). N'utilise que la date de début, pivot au 1er août. Si année non specifiée, année scolaire courante """ - return sem_in_semestre_scolaire(sem, year, periode=0) + return FormSemestre.est_in_semestre_scolaire( + datetime.date.fromisoformat(sem["date_debut_iso"]), year, periode=0 + ) -def sem_est_courant(sem): # -> FormSemestre.est_courant +def sem_in_semestre_scolaire(sem, year=False, periode=None): # OBSOLETE + return FormSemestre.est_in_semestre_scolaire( + datetime.date.fromisoformat(sem["date_debut_iso"]), year, periode=periode + ) + + +def sem_est_courant(sem: dict): # -> FormSemestre.est_courant """Vrai si la date actuelle (now) est dans le semestre (les dates de début et fin sont incluses)""" now = time.strftime("%Y-%m-%d") debut = ndb.DateDMYtoISO(sem["date_debut"]) diff --git a/app/scodoc/sco_formsemestre_edit.py b/app/scodoc/sco_formsemestre_edit.py index c2f375f3..07717f59 100644 --- a/app/scodoc/sco_formsemestre_edit.py +++ b/app/scodoc/sco_formsemestre_edit.py @@ -438,12 +438,13 @@ def do_formsemestre_createwithmodules(edit=False, formsemestre: FormSemestre = N "elt_sem_apo", { "size": 32, - "title": "Element(s) Apogée:", + "title": "Element(s) Apogée sem.:", "explanation": "associé(s) au résultat du semestre (ex: VRTW1). Inutile en BUT. Séparés par des virgules.", "allow_null": not sco_preferences.get_preference( "always_require_apo_sem_codes" ) - or (formsemestre and formsemestre.modalite == "EXT"), + or (formsemestre and formsemestre.modalite == "EXT") + or (formsemestre.formation.is_apc()), }, ) ) @@ -452,7 +453,7 @@ def do_formsemestre_createwithmodules(edit=False, formsemestre: FormSemestre = N "elt_annee_apo", { "size": 32, - "title": "Element(s) Apogée:", + "title": "Element(s) Apogée année:", "explanation": "associé(s) au résultat de l'année (ex: VRT1A). Séparés par des virgules.", "allow_null": not sco_preferences.get_preference( "always_require_apo_sem_codes" diff --git a/app/scodoc/sco_semset.py b/app/scodoc/sco_semset.py index 985f9618..e70392c9 100644 --- a/app/scodoc/sco_semset.py +++ b/app/scodoc/sco_semset.py @@ -381,7 +381,6 @@ class SemSet(dict): (actif seulement si un portail est configuré) """ return self.bilan.html_diagnostic() - return "" def get_semsets_list(): diff --git a/app/static/css/scodoc.css b/app/static/css/scodoc.css index c4d55976..1c490842 100644 --- a/app/static/css/scodoc.css +++ b/app/static/css/scodoc.css @@ -4165,6 +4165,11 @@ div.apo_csv_list { border: 1px dashed rgb(150, 10, 40); } +table#apo_csv_list td { + white-space: nowrap; + word-break: no-wrap; +} + #apo_csv_download { margin-top: 5px; }