From 320cfbebc8f718bab391eaa1272d492aed40d577 Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Fri, 7 Jun 2024 17:58:02 +0200 Subject: [PATCH] WIP: modernisation code jurys --- app/but/cursus_but.py | 2 +- app/models/etudiants.py | 7 +- app/models/ues.py | 2 + app/scodoc/sco_apogee_csv.py | 32 ++-- app/scodoc/sco_assiduites.py | 4 +- app/scodoc/sco_cursus.py | 4 +- app/scodoc/sco_cursus_dut.py | 205 ++++++++++++---------- app/scodoc/sco_formsemestre_validation.py | 141 ++++++++------- app/scodoc/sco_groups.py | 22 +-- app/scodoc/sco_groups_exports.py | 6 +- app/scodoc/sco_groups_view.py | 20 ++- app/scodoc/sco_page_etud.py | 3 +- app/scodoc/sco_prepajury.py | 13 +- app/scodoc/sco_pv_dict.py | 8 +- sco_version.py | 2 +- 15 files changed, 251 insertions(+), 220 deletions(-) diff --git a/app/but/cursus_but.py b/app/but/cursus_but.py index 4d7f70d4..c72938a8 100644 --- a/app/but/cursus_but.py +++ b/app/but/cursus_but.py @@ -44,7 +44,7 @@ from app.scodoc import sco_cursus_dut class SituationEtudCursusBUT(sco_cursus_dut.SituationEtudCursusClassic): """Pour compat ScoDoc 7: à revoir pour le BUT""" - def __init__(self, etud: dict, formsemestre_id: int, res: ResultatsSemestreBUT): + def __init__(self, etud: Identite, formsemestre_id: int, res: ResultatsSemestreBUT): super().__init__(etud, formsemestre_id, res) # Ajustements pour le BUT self.can_compensate_with_prev = False # jamais de compensation à la mode DUT diff --git a/app/models/etudiants.py b/app/models/etudiants.py index 87ce6163..97659430 100644 --- a/app/models/etudiants.py +++ b/app/models/etudiants.py @@ -359,14 +359,15 @@ class Identite(models.ScoDocModel): "Le mail associé à la première adresse de l'étudiant, ou None" return getattr(self.adresses[0], field) if self.adresses.count() > 0 else None - def get_formsemestres(self) -> list: + def get_formsemestres(self, recent_first=True) -> list: """Liste des formsemestres dans lesquels l'étudiant est (a été) inscrit, - triée par date_debut + triée par date_debut, le plus récent d'abord (comme "sems" de scodoc7) + (si recent_first=False, le plus ancien en tête) """ return sorted( [ins.formsemestre for ins in self.formsemestre_inscriptions], key=attrgetter("date_debut"), - reverse=True, + reverse=recent_first, ) def get_modimpls_by_formsemestre( diff --git a/app/models/ues.py b/app/models/ues.py index d88cc7e6..51467c7e 100644 --- a/app/models/ues.py +++ b/app/models/ues.py @@ -46,6 +46,8 @@ class UniteEns(models.ScoDocModel): # coef UE, utilise seulement si l'option use_ue_coefs est activée: coefficient = db.Column(db.Float) + # id de l'élément Apogée du RCUE (utilisé pour les UEs de sem. pair du BUT) + code_apogee_rcue = db.Column(db.String(APO_CODE_STR_LEN)) # coef. pour le calcul de moyennes de RCUE. Par défaut, 1. coef_rcue = db.Column(db.Float, nullable=False, default=1.0, server_default="1.0") diff --git a/app/scodoc/sco_apogee_csv.py b/app/scodoc/sco_apogee_csv.py index c046df40..7feea971 100644 --- a/app/scodoc/sco_apogee_csv.py +++ b/app/scodoc/sco_apogee_csv.py @@ -48,9 +48,9 @@ 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.comp.res_but import ResultatsSemestreBUT from app.models import ( ApcValidationAnnee, ApcValidationRCUE, @@ -132,7 +132,7 @@ class ApoEtud(dict): "Vrai si BUT" self.col_elts = {} "{'V1RT': {'R': 'ADM', 'J': '', 'B': 20, 'N': '12.14'}}" - self.etud: Identite = None + self.etud: Identite | None = None "etudiant ScoDoc associé" self.etat = None # ETUD_OK, ... self.is_nar = False @@ -171,24 +171,18 @@ class ApoEtud(dict): 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"], dept_id=g.scodoc_dept_id).first() - # self.etud = etud - etuds = sco_etud.get_etud_info(code_nip=self["nip"], filled=True) - if not etuds: + self.etud = Identite.query.filter_by( + code_nip=self["nip"], dept_id=g.scodoc_dept_id + ).first() + if not self.etud: # pas dans ScoDoc - self.etud = None 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"]} + formsemestre_ids = { + ins.formsemestre_id for ins in self.etud.formsemestre_inscriptions + } in_formsemestre_ids = formsemestre_ids.intersection(etape_formsemestre_ids) if not in_formsemestre_ids: self.log.append( @@ -267,13 +261,17 @@ class ApoEtud(dict): Args: code (str): code apo de l'element cherché 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) Returns: dict: with N, B, J, R keys, ou None si elt non trouvé """ - etudid = self.etud["etudid"] + if not self.etud: + return None + etudid = self.etud.id if not self.cur_res: log("search_elt_in_sem: no cur_res !") return None @@ -377,6 +375,8 @@ class ApoEtud(dict): if module_code_found: return VOID_APO_RES + # RCUE du BUT + deca = jury_but.DecisionsProposeesAnnee(self.etud, formsemestre) # return None # element Apogee non trouvé dans ce semestre diff --git a/app/scodoc/sco_assiduites.py b/app/scodoc/sco_assiduites.py index d2832ba6..ff2088fc 100644 --- a/app/scodoc/sco_assiduites.py +++ b/app/scodoc/sco_assiduites.py @@ -751,7 +751,7 @@ def formsemestre_get_assiduites_count( ) -> tuple[int, int, int]: """Les comptes d'absences de cet étudiant dans ce semestre: tuple (nb abs non justifiées, nb abs justifiées, nb abs total) - Utilise un cache. + Utilise un cache (si moduleimpl_id n'est pas spécifié). """ metrique = sco_preferences.get_preference("assi_metrique", formsemestre.id) return get_assiduites_count_in_interval( @@ -779,7 +779,7 @@ def get_assiduites_count_in_interval( """Les comptes d'absences de cet étudiant entre ces deux dates, incluses: tuple (nb abs non justifiées, nb abs justifiées, nb abs total) On peut spécifier les dates comme datetime ou iso. - Utilise un cache. + Utilise un cache (si moduleimpl_id n'est pas spécifié). """ date_debut_iso = date_debut_iso or date_debut.strftime("%Y-%m-%d") date_fin_iso = date_fin_iso or date_fin.strftime("%Y-%m-%d") diff --git a/app/scodoc/sco_cursus.py b/app/scodoc/sco_cursus.py index 1bd9983f..1d40e13a 100644 --- a/app/scodoc/sco_cursus.py +++ b/app/scodoc/sco_cursus.py @@ -34,13 +34,13 @@ from app.scodoc import sco_cursus_dut from app.comp.res_compat import NotesTableCompat from app.comp import res_sem -from app.models import FormSemestre +from app.models import FormSemestre, Identite import app.scodoc.notesdb as ndb # SituationEtudParcours -> get_situation_etud_cursus def get_situation_etud_cursus( - etud: dict, formsemestre_id: int + etud: Identite, formsemestre_id: int ) -> sco_cursus_dut.SituationEtudCursus: """renvoie une instance de SituationEtudCursus (ou sous-classe spécialisée)""" formsemestre = FormSemestre.get_formsemestre(formsemestre_id) diff --git a/app/scodoc/sco_cursus_dut.py b/app/scodoc/sco_cursus_dut.py index 10e17d4d..6c1344a4 100644 --- a/app/scodoc/sco_cursus_dut.py +++ b/app/scodoc/sco_cursus_dut.py @@ -31,7 +31,7 @@ from app import db from app.comp import res_sem from app.comp.res_compat import NotesTableCompat -from app.models import FormSemestre, UniteEns, ScolarAutorisationInscription +from app.models import FormSemestre, Identite, ScolarAutorisationInscription, UniteEns import app.scodoc.sco_utils as scu import app.scodoc.notesdb as ndb @@ -115,14 +115,22 @@ class SituationEtudCursus: class SituationEtudCursusClassic(SituationEtudCursus): "Semestre dans un parcours" - def __init__(self, etud: dict, formsemestre_id: int, nt: NotesTableCompat): + def __init__(self, etud: Identite, formsemestre_id: int, nt: NotesTableCompat): """ etud: dict filled by fill_etuds_info() """ + assert formsemestre_id == nt.formsemestre.id self.etud = etud - self.etudid = etud["etudid"] + self.etudid = etud.id self.formsemestre_id = formsemestre_id - self.sem = sco_formsemestre.get_formsemestre(formsemestre_id) + self.formsemestres: list[FormSemestre] = [] + "les semestres parcourus, le plus ancien en tête" + self.sem = sco_formsemestre.get_formsemestre( + formsemestre_id + ) # TODO utiliser formsemestres + self.cur_sem: FormSemestre = nt.formsemestre + self.can_compensate: set[int] = set() + "les formsemestre_id qui peuvent compenser le courant" self.nt: NotesTableCompat = nt self.formation = self.nt.formsemestre.formation self.parcours = self.nt.parcours @@ -130,18 +138,20 @@ class SituationEtudCursusClassic(SituationEtudCursus): # pour le DUT, le dernier est toujours S4. # Ici: terminal si semestre == NB_SEM ou bien semestre_id==-1 # (licences et autres formations en 1 seule session)) - self.semestre_non_terminal = self.sem["semestre_id"] != self.parcours.NB_SEM - if self.sem["semestre_id"] == NO_SEMESTRE_ID: + self.semestre_non_terminal = self.cur_sem.semestre_id != self.parcours.NB_SEM + if self.cur_sem.semestre_id == NO_SEMESTRE_ID: self.semestre_non_terminal = False # Liste des semestres du parcours de cet étudiant: self._comp_semestres() # Determine le semestre "precedent" - self.prev_formsemestre_id = self._search_prev() + self._search_prev() # Verifie barres self._comp_barres() # Verifie compensation - if self.prev and self.sem["gestion_compensation"]: - self.can_compensate_with_prev = self.prev["can_compensate"] + if self.prev_formsemestre and self.cur_sem.gestion_compensation: + self.can_compensate_with_prev = ( + self.prev_formsemestre.id in self.can_compensate + ) else: self.can_compensate_with_prev = False @@ -170,14 +180,14 @@ class SituationEtudCursusClassic(SituationEtudCursus): if rule.conclusion[0] in self.parcours.UNUSED_CODES: continue # Saute regles REDOSEM si pas de semestres decales: - if (not self.sem["gestion_semestrielle"]) and rule.conclusion[ + if (not self.cur_sem.gestion_semestrielle) and rule.conclusion[ 3 ] == "REDOSEM": continue if rule.match(state): if rule.conclusion[0] == ADC: # dans les regles on ne peut compenser qu'avec le PRECEDENT: - fiduc = self.prev_formsemestre_id + fiduc = self.prev_formsemestre.id assert fiduc else: fiduc = None @@ -203,8 +213,8 @@ class SituationEtudCursusClassic(SituationEtudCursus): "Phrase d'explication pour le code devenir" if not devenir: return "" - s = self.sem["semestre_id"] # numero semestre courant - if s < 0: # formation sans semestres (eg licence) + s_idx = self.cur_sem.semestre_id # numero semestre courant + if s_idx < 0: # formation sans semestres (eg licence) next_s = 1 else: next_s = self._get_next_semestre_id() @@ -219,27 +229,27 @@ class SituationEtudCursusClassic(SituationEtudCursus): elif devenir == REO: return "Réorienté" elif devenir == REDOANNEE: - return "Redouble année (recommence %s%s)" % (SA, (s - 1)) + return "Redouble année (recommence %s%s)" % (SA, (s_idx - 1)) elif devenir == REDOSEM: - return "Redouble semestre (recommence en %s%s)" % (SA, s) + return "Redouble semestre (recommence en %s%s)" % (SA, s_idx) elif devenir == RA_OR_NEXT: - return passage + ", ou redouble année (en %s%s)" % (SA, (s - 1)) + return passage + ", ou redouble année (en %s%s)" % (SA, (s_idx - 1)) elif devenir == RA_OR_RS: return "Redouble semestre %s%s, ou redouble année (en %s%s)" % ( SA, - s, + s_idx, SA, - s - 1, + s_idx - 1, ) elif devenir == RS_OR_NEXT: - return passage + ", ou semestre %s%s" % (SA, s) + return passage + ", ou semestre %s%s" % (SA, s_idx) elif devenir == NEXT_OR_NEXT2: return passage + ", ou en semestre %s%s" % ( SA, - s + 2, + s_idx + 2, ) # coherent avec get_next_semestre_ids elif devenir == NEXT2: - return "Passe en %s%s" % (SA, s + 2) + return "Passe en %s%s" % (SA, s_idx + 2) else: log("explique_devenir: code devenir inconnu: %s" % devenir) return "Code devenir inconnu !" @@ -258,7 +268,7 @@ class SituationEtudCursusClassic(SituationEtudCursus): def _sems_validated(self, exclude_current=False): "True si semestres du parcours validés" - if self.sem["semestre_id"] == NO_SEMESTRE_ID: + if self.cur_sem.semestre_id == NO_SEMESTRE_ID: # mono-semestre: juste celui ci decision = self.nt.get_etud_decision_sem(self.etudid) return decision and code_semestre_validant(decision["code"]) @@ -266,8 +276,8 @@ class SituationEtudCursusClassic(SituationEtudCursus): to_validate = set( range(1, self.parcours.NB_SEM + 1) ) # ensemble des indices à valider - if exclude_current and self.sem["semestre_id"] in to_validate: - to_validate.remove(self.sem["semestre_id"]) + if exclude_current and self.cur_sem.semestre_id in to_validate: + to_validate.remove(self.cur_sem.semestre_id) return self._sem_list_validated(to_validate) def can_jump_to_next2(self): @@ -275,14 +285,14 @@ class SituationEtudCursusClassic(SituationEtudCursus): Il faut donc que tous les semestres 1...n-1 soient validés et que n+1 soit en attente. (et que le sem courant n soit validé, ce qui n'est pas testé ici) """ - n = self.sem["semestre_id"] - if not self.sem["gestion_semestrielle"]: + s_idx = self.cur_sem.semestre_id + if not self.cur_sem.gestion_semestrielle: return False # pas de semestre décalés - if n == NO_SEMESTRE_ID or n > self.parcours.NB_SEM - 2: + 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, n))): + if self._sem_list_validated(set(range(1, s_idx))): # antérieurs validé, teste suivant - n1 = n + 1 + n1 = s_idx + 1 for sem in self.get_semestres(): if ( sem["semestre_id"] == n1 @@ -315,19 +325,17 @@ class SituationEtudCursusClassic(SituationEtudCursus): return not sem_idx_set def _comp_semestres(self): - # etud['sems'] est trie par date decroissante (voir fill_etuds_info) - if not "sems" in self.etud: - self.etud["sems"] = sco_etud.etud_inscriptions_infos( - self.etud["etudid"], self.etud["ne"] - )["sems"] - sems = self.etud["sems"][:] # copy - sems.reverse() + # plus ancien en tête: + self.formsemestres = self.etud.get_formsemestres(recent_first=False) + # Nb max d'UE et acronymes ue_acros = {} # acronyme ue : 1 nb_max_ue = 0 - for sem in sems: - formsemestre = FormSemestre.query.get_or_404(sem["formsemestre_id"]) + sems = [] + for formsemestre in self.formsemestres: # plus ancien en tête nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre) + sem = formsemestre.to_dict() + sems.append(sem) ues = nt.get_ues_stat_dict(filter_sport=True) for ue in ues: ue_acros[ue["acronyme"]] = 1 @@ -338,14 +346,15 @@ class SituationEtudCursusClassic(SituationEtudCursus): sem["formation_code"] = formsemestre.formation.formation_code # si sem peut servir à compenser le semestre courant, positionne # can_compensate - sem["can_compensate"] = self.check_compensation_dut(sem, nt) + if self.check_compensation_dut(sem, nt): + self.can_compensate.add(formsemestre.id) self.ue_acros = list(ue_acros.keys()) self.ue_acros.sort() self.nb_max_ue = nb_max_ue self.sems = sems - def get_semestres(self): + def get_semestres(self) -> list[dict]: """Liste des semestres dans lesquels a été inscrit l'étudiant (quelle que soit la formation), le plus ancien en tête""" return self.sems @@ -355,24 +364,30 @@ class SituationEtudCursusClassic(SituationEtudCursus): Si filter_futur, ne mentionne pas les semestres qui sont après le semestre courant. Si filter_formation_code, restreint aux semestres de même code formation que le courant. """ - cur_begin_date = self.sem["dateord"] - cur_formation_code = self.sem["formation_code"] + cur_begin_date = self.cur_sem.date_debut + cur_formation_code = self.cur_sem.formation.formation_code p = [] - for s in self.sems: - if s["ins"]["etat"] == scu.DEMISSION: + for formsemestre in self.formsemestres: + inscription = formsemestre.etuds_inscriptions.get(self.etud.id) + if inscription is None: + raise ValueError("Etudiant non inscrit au semestre") # bug + if inscription.etat == scu.DEMISSION: dem = " (dem.)" else: dem = "" - if filter_futur and s["dateord"] > cur_begin_date: + if filter_futur and formsemestre.date_debut > cur_begin_date: continue # skip semestres demarrant apres le courant - if filter_formation_code and s["formation_code"] != cur_formation_code: + if ( + filter_formation_code + and formsemestre.formation.formation_code != cur_formation_code + ): continue # restreint aux semestres de la formation courante (pour les PV) session_abbrv = self.parcours.SESSION_ABBRV # 'S' ou 'A' - if s["semestre_id"] < 0: + if formsemestre.semestre_id < 0: session_abbrv = "A" # force, cas des DUT annuels par exemple - p.append("%s%d%s" % (session_abbrv, -s["semestre_id"], dem)) + p.append("%s%d%s" % (session_abbrv, -formsemestre.semestre_id, dem)) else: - p.append("%s%d%s" % (session_abbrv, s["semestre_id"], dem)) + p.append("%s%d%s" % (session_abbrv, formsemestre.semestre_id, dem)) return ", ".join(p) def get_parcours_decisions(self): @@ -381,7 +396,7 @@ class SituationEtudCursusClassic(SituationEtudCursus): Returns: { semestre_id : code } """ r = {} - if self.sem["semestre_id"] == NO_SEMESTRE_ID: + if self.cur_sem.semestre_id == NO_SEMESTRE_ID: indices = [NO_SEMESTRE_ID] else: indices = list(range(1, self.parcours.NB_SEM + 1)) @@ -424,22 +439,22 @@ class SituationEtudCursusClassic(SituationEtudCursus): "true si ce semestre pourrait etre compensé par un autre (e.g. barres UE > 8)" return self.barres_ue_ok - def _search_prev(self): + def _search_prev(self) -> FormSemestre | None: """Recherche semestre 'precedent'. - return prev_formsemestre_id + positionne .prev_decision """ - self.prev = None + self.prev_formsemestre = None self.prev_decision = None - if len(self.sems) < 2: + if len(self.formsemestres) < 2: return None # Cherche sem courant dans la liste triee par date_debut cur = None icur = -1 - for cur in self.sems: + for cur in self.formsemestres: icur += 1 - if cur["formsemestre_id"] == self.formsemestre_id: + if cur.id == self.formsemestre_id: break - if not cur or cur["formsemestre_id"] != self.formsemestre_id: + if not cur or cur.id != self.formsemestre_id: log( f"*** SituationEtudCursus: search_prev: cur not found (formsemestre_id={self.formsemestre_id}, etudid={self.etudid})" ) @@ -447,60 +462,59 @@ class SituationEtudCursusClassic(SituationEtudCursus): # Cherche semestre antérieur de même formation (code) et semestre_id precedent # # i = icur - 1 # part du courant, remonte vers le passé - i = len(self.sems) - 1 # par du dernier, remonte vers le passé - prev = None + i = len(self.formsemestres) - 1 # par du dernier, remonte vers le passé + prev_formsemestre = None while i >= 0: if ( - self.sems[i]["formation_code"] == self.formation.formation_code - and self.sems[i]["semestre_id"] == cur["semestre_id"] - 1 + self.formsemestres[i].formation.formation_code + == self.formation.formation_code + and self.formsemestres[i].semestre_id == cur.semestre_id - 1 ): - prev = self.sems[i] + prev_formsemestre = self.formsemestres[i] break i -= 1 - if not prev: + if not prev_formsemestre: return None # pas de precedent trouvé - self.prev = prev + self.prev_formsemestre = prev_formsemestre # Verifications basiques: # ? # Code etat du semestre precedent: - formsemestre = FormSemestre.query.get_or_404(prev["formsemestre_id"]) - nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre) + nt: NotesTableCompat = res_sem.load_formsemestre_results(prev_formsemestre) self.prev_decision = nt.get_etud_decision_sem(self.etudid) self.prev_moy_gen = nt.get_etud_moy_gen(self.etudid) self.prev_barres_ue_ok = nt.etud_check_conditions_ues(self.etudid)[0] - return self.prev["formsemestre_id"] - def get_next_semestre_ids(self, devenir): + def get_next_semestre_ids(self, devenir: str) -> list[int]: """Liste des numeros de semestres autorises avec ce devenir Ne vérifie pas que le devenir est possible (doit être fait avant), juste que le rang du semestre est dans le parcours [1..NB_SEM] """ - s = self.sem["semestre_id"] + s_idx = self.cur_sem.semestre_id if devenir == NEXT: ids = [self._get_next_semestre_id()] elif devenir == REDOANNEE: - ids = [s - 1] + ids = [s_idx - 1] elif devenir == REDOSEM: - ids = [s] + ids = [s_idx] elif devenir == RA_OR_NEXT: - ids = [s - 1, self._get_next_semestre_id()] + ids = [s_idx - 1, self._get_next_semestre_id()] elif devenir == RA_OR_RS: - ids = [s - 1, s] + ids = [s_idx - 1, s_idx] elif devenir == RS_OR_NEXT: - ids = [s, self._get_next_semestre_id()] + ids = [s_idx, self._get_next_semestre_id()] elif devenir == NEXT_OR_NEXT2: ids = [ self._get_next_semestre_id(), - s + 2, + s_idx + 2, ] # cohérent avec explique_devenir() elif devenir == NEXT2: - ids = [s + 2] + ids = [s_idx + 2] else: ids = [] # reoriente ou autre: pas de next ! # clip [1..NB_SEM] r = [] for idx in ids: - if idx > 0 and idx <= self.parcours.NB_SEM: + if 0 < idx <= self.parcours.NB_SEM: r.append(idx) return r @@ -508,27 +522,27 @@ class SituationEtudCursusClassic(SituationEtudCursus): """Indice du semestre suivant non validé. S'il n'y en a pas, ramène NB_SEM+1 """ - s = self.sem["semestre_id"] - if s >= self.parcours.NB_SEM: + s_idx = self.cur_sem.semestre_id + if s_idx >= self.parcours.NB_SEM: return self.parcours.NB_SEM + 1 validated = True - while validated and (s < self.parcours.NB_SEM): - s = s + 1 + while validated and (s_idx < self.parcours.NB_SEM): + s_idx = s_idx + 1 # semestre s validé ? validated = False - for sem in self.sems: + for formsemestre in self.formsemestres: if ( - sem["formation_code"] == self.formation.formation_code - and sem["semestre_id"] == s + formsemestre.formation.formation_code + == self.formation.formation_code + and formsemestre.semestre_id == s_idx ): - formsemestre = FormSemestre.query.get_or_404(sem["formsemestre_id"]) nt: NotesTableCompat = res_sem.load_formsemestre_results( formsemestre ) decision = nt.get_etud_decision_sem(self.etudid) if decision and code_semestre_validant(decision["code"]): validated = True - return s + return s_idx def valide_decision(self, decision): """Enregistre la decision (instance de DecisionSem) @@ -543,8 +557,11 @@ class SituationEtudCursusClassic(SituationEtudCursus): fsid = decision.formsemestre_id_utilise_pour_compenser if fsid: ok = False - for sem in self.sems: - if sem["formsemestre_id"] == fsid and sem["can_compensate"]: + for formsemestre in self.formsemestres: + if ( + formsemestre.id == fsid + and formsemestre.id in self.can_compensate + ): ok = True break if not ok: @@ -585,7 +602,7 @@ class SituationEtudCursusClassic(SituationEtudCursus): decision.assiduite, ) # -- modification du code du semestre precedent - if self.prev and decision.new_code_prev: + if self.prev_formsemestre and decision.new_code_prev: if decision.new_code_prev == ADC: # ne compense le prec. qu'avec le sem. courant fsid = self.formsemestre_id @@ -593,7 +610,7 @@ class SituationEtudCursusClassic(SituationEtudCursus): fsid = None to_invalidate += formsemestre_update_validation_sem( cnx, - self.prev["formsemestre_id"], + self.prev_formsemestre.id, self.etudid, decision.new_code_prev, assidu=True, @@ -605,18 +622,18 @@ class SituationEtudCursusClassic(SituationEtudCursus): etudid=self.etudid, commit=False, msg="formsemestre_id=%s code=%s" - % (self.prev["formsemestre_id"], decision.new_code_prev), + % (self.prev_formsemestre.id, decision.new_code_prev), ) # modifs des codes d'UE (pourraient passer de ADM a CMP, meme sans modif des notes) formsemestre_validate_ues( - self.prev["formsemestre_id"], + self.prev_formsemestre.id, self.etudid, decision.new_code_prev, decision.assiduite, # attention: en toute rigueur il faudrait utiliser une indication de l'assiduite au sem. precedent, que nous n'avons pas... ) sco_cache.invalidate_formsemestre( - formsemestre_id=self.prev["formsemestre_id"] + formsemestre_id=self.prev_formsemestre.id ) # > modif decisions jury (sem, UE) try: @@ -698,7 +715,7 @@ class SituationEtudCursusClassic(SituationEtudCursus): class SituationEtudCursusECTS(SituationEtudCursusClassic): """Gestion parcours basés sur ECTS""" - def __init__(self, etud, formsemestre_id, nt): + def __init__(self, etud: Identite, formsemestre_id: int, nt): SituationEtudCursusClassic.__init__(self, etud, formsemestre_id, nt) def could_be_compensated(self): diff --git a/app/scodoc/sco_formsemestre_validation.py b/app/scodoc/sco_formsemestre_validation.py index 55ed0665..e6b4e156 100644 --- a/app/scodoc/sco_formsemestre_validation.py +++ b/app/scodoc/sco_formsemestre_validation.py @@ -116,7 +116,7 @@ def formsemestre_validation_etud_form( check = True etud_d = sco_etud.get_etud_info(etudid=etudid, filled=True)[0] - Se = sco_cursus.get_situation_etud_cursus(etud_d, formsemestre_id) + Se = sco_cursus.get_situation_etud_cursus(etud, formsemestre_id) if not Se.sem["etat"]: raise ScoValueError("validation: semestre verrouille") @@ -262,8 +262,8 @@ def formsemestre_validation_etud_form( return "\n".join(H + footer) # Infos si pas de semestre précédent - if not Se.prev: - if Se.sem["semestre_id"] == 1: + if not Se.prev_formsemestre: + if Se.cur_sem.semestre_id == 1: H.append("

Premier semestre (pas de précédent)

") else: H.append("

Pas de semestre précédent !

") @@ -274,7 +274,7 @@ def formsemestre_validation_etud_form( f"""Le jury n'a pas statué sur le semestre précédent ! (le faire maintenant) """ @@ -310,9 +310,9 @@ def formsemestre_validation_etud_form( H.append("

") # Cas particulier pour ATJ: corriger precedent avant de continuer - if Se.prev_decision and Se.prev_decision["code"] == ATJ: + if Se.prev_formsemestre and Se.prev_decision and Se.prev_decision["code"] == ATJ: H.append( - """

La décision du semestre précédent est en + f"""

La décision du semestre précédent est en attente à cause d\'un problème d\'assiduité.

Vous devez la corriger avant de continuer ce jury. Soit vous considérez que le problème d'assiduité n'est pas réglé et choisissez de ne pas valider le semestre @@ -320,14 +320,16 @@ def formsemestre_validation_etud_form( l'assiduité.

- - - + + + """ - % (Se.prev["formsemestre_id"], etudid, etudid, formsemestre_id) ) if sortcol: - H.append('' % sortcol) + H.append(f"""""") H.append("
") H.append(html_sco_header.sco_footer()) @@ -405,7 +407,7 @@ def formsemestre_validation_etud( sortcol=None, ): """Enregistre validation""" - etud = sco_etud.get_etud_info(etudid=etudid, filled=True)[0] + etud = Identite.get_etud(etudid) Se = sco_cursus.get_situation_etud_cursus(etud, formsemestre_id) # retrouve la decision correspondant au code: choices = Se.get_possible_choices(assiduite=True) @@ -438,7 +440,7 @@ def formsemestre_validation_etud_manu( """Enregistre validation""" if assidu: assidu = True - etud = sco_etud.get_etud_info(etudid=etudid, filled=True)[0] + etud = Identite.get_etud(etudid) Se = sco_cursus.get_situation_etud_cursus(etud, formsemestre_id) if code_etat in Se.parcours.UNUSED_CODES: raise ScoValueError("code decision invalide dans ce parcours") @@ -494,32 +496,35 @@ def decisions_possible_rows(Se, assiduite, subtitle="", trclass=""): choices = Se.get_possible_choices(assiduite=assiduite) if not choices: return "" - TitlePrev = "" - if Se.prev: - if Se.prev["semestre_id"] >= 0: - TitlePrev = "%s%d" % (Se.parcours.SESSION_ABBRV, Se.prev["semestre_id"]) + prev_title = "" + if Se.prev_formsemestre: + if Se.prev_formsemestre.semestre_id >= 0: + prev_title = "%s%d" % ( + Se.parcours.SESSION_ABBRV, + Se.prev_formsemestre.semestre_id, + ) else: - TitlePrev = "Prec." + prev_title = "Prec." - if Se.sem["semestre_id"] >= 0: - TitleCur = "%s%d" % (Se.parcours.SESSION_ABBRV, Se.sem["semestre_id"]) + if Se.cur_sem.semestre_id >= 0: + cur_title = "%s%d" % (Se.parcours.SESSION_ABBRV, Se.cur_sem.semestre_id) else: - TitleCur = Se.parcours.SESSION_NAME + cur_title = Se.parcours.SESSION_NAME H = [ '%s' % (trclass, subtitle) ] - if Se.prev: - H.append("Code %s" % TitlePrev) - H.append("Code %sDevenir" % TitleCur) + if Se.prev_formsemestre: + H.append(f"Code {prev_title}") + H.append(f"Code {cur_title}Devenir") for ch in choices: H.append( """""" % (trclass, ch.rule_id, ch.codechoice) ) H.append("%s " % ch.explication) - if Se.prev: + if Se.prev_formsemestre: H.append('%s' % _dispcode(ch.new_code_prev)) H.append( '%s%s' @@ -535,7 +540,6 @@ def formsemestre_recap_parcours_table( etudid, with_links=False, with_all_columns=True, - a_url="", sem_info=None, show_details=False, ): @@ -576,14 +580,14 @@ def formsemestre_recap_parcours_table( H.append("") num_sem = 0 - for sem in situation_etud_cursus.get_semestres(): - is_prev = situation_etud_cursus.prev and ( - situation_etud_cursus.prev["formsemestre_id"] == sem["formsemestre_id"] + for formsemestre in situation_etud_cursus.formsemestres: + is_prev = situation_etud_cursus.prev_formsemestre and ( + situation_etud_cursus.prev_formsemestre.id == formsemestre.id ) - is_cur = situation_etud_cursus.formsemestre_id == sem["formsemestre_id"] + is_cur = situation_etud_cursus.formsemestre_id == formsemestre.id num_sem += 1 - dpv = sco_pv_dict.dict_pvjury(sem["formsemestre_id"], etudids=[etudid]) + dpv = sco_pv_dict.dict_pvjury(formsemestre.id, etudids=[etudid]) pv = dpv["decisions"][0] decision_sem = pv["decision_sem"] decisions_ue = pv["decisions_ue"] @@ -592,7 +596,6 @@ def formsemestre_recap_parcours_table( else: ass = "" - formsemestre = db.session.get(FormSemestre, sem["formsemestre_id"]) nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre) if is_cur: type_sem = "*" # now unused @@ -603,20 +606,24 @@ def formsemestre_recap_parcours_table( else: type_sem = "" class_sem = "sem_autre" - if sem["formation_code"] != situation_etud_cursus.formation.formation_code: + if ( + formsemestre.formation.formation_code + != situation_etud_cursus.formation.formation_code + ): class_sem += " sem_autre_formation" - if sem["bul_bgcolor"]: - bgcolor = sem["bul_bgcolor"] - else: - bgcolor = "background-color: rgb(255,255,240)" + bgcolor = ( + formsemestre.bul_bgcolor + if formsemestre.bul_bgcolor + else "background-color: rgb(255,255,240)" + ) # 1ere ligne: titre sem, decision, acronymes UE - H.append('' % (class_sem, sem["formsemestre_id"])) + H.append('' % (class_sem, formsemestre.id)) if is_cur: pm = "" elif is_prev: - pm = minuslink % sem["formsemestre_id"] + pm = minuslink % formsemestre.id else: - pm = plusminus % sem["formsemestre_id"] + pm = plusminus % formsemestre.id inscr = formsemestre.etuds_inscriptions.get(etudid) parcours_name = "" @@ -638,9 +645,12 @@ def formsemestre_recap_parcours_table( H.append( f""" {num_sem}{pm} - {sem['mois_debut']} + {formsemestre.mois_debut()} {formsemestre.titre_annee()}{parcours_name} """ ) @@ -675,7 +685,7 @@ def formsemestre_recap_parcours_table( ues = [ ue for ue in ues - if etud_est_inscrit_ue(cnx, etudid, sem["formsemestre_id"], ue.id) + if etud_est_inscrit_ue(cnx, etudid, formsemestre.id, ue.id) or etud_ue_status[ue.id]["is_capitalized"] ] @@ -697,7 +707,7 @@ def formsemestre_recap_parcours_table( H.append("") H.append("") # 2eme ligne: notes - H.append(f"""""") + H.append(f"""""") H.append( f""" """ @@ -706,21 +716,28 @@ def formsemestre_recap_parcours_table( default_sem_info = '[sem. précédent]' else: default_sem_info = "" - if not sem["etat"]: # locked + if not formsemestre.etat: # locked lockicon = scu.icontag("lock32_img", title="verrouillé", border="0") default_sem_info += lockicon - if sem["formation_code"] != situation_etud_cursus.formation.formation_code: - default_sem_info += f"""Autre formation: {sem["formation_code"]}""" + if ( + formsemestre.formation.formation_code + != situation_etud_cursus.formation.formation_code + ): + default_sem_info += ( + f"""Autre formation: {formsemestre.formation.formation_code}""" + ) H.append( '%s%s' - % (sem["mois_fin"], sem_info.get(sem["formsemestre_id"], default_sem_info)) + % (formsemestre.mois_fin(), sem_info.get(formsemestre.id, default_sem_info)) ) # Moy Gen (sous le code decision) H.append( f"""{scu.fmt_note(nt.get_etud_moy_gen(etudid))}""" ) # Absences (nb d'abs non just. dans ce semestre) - nbabsnj = sco_assiduites.get_assiduites_count(etudid, sem)[0] + nbabsnj = sco_assiduites.formsemestre_get_assiduites_count( + etudid, formsemestre + )[0] H.append(f"""{nbabsnj}""") # UEs @@ -767,26 +784,30 @@ def formsemestre_recap_parcours_table( H.append("") if with_links: H.append( - 'modifier' - % (a_url, sem["formsemestre_id"], etudid) + f"""modifier""" ) H.append("") # 3eme ligne: ECTS if ( - sco_preferences.get_preference("bul_show_ects", sem["formsemestre_id"]) + sco_preferences.get_preference("bul_show_ects", formsemestre.id) or nt.parcours.ECTS_ONLY ): etud_ects_infos = nt.get_etud_ects_pot(etudid) # ECTS potentiels H.append( - f""" + f"""   """ ) # Total ECTS (affiché sous la moyenne générale) H.append( f"""ECTS: - {pv.get("sum_ects",0):2.2g} / {etud_ects_infos["ects_total"]:2.2g} + { + pv.get("sum_ects",0):2.2g} / {etud_ects_infos["ects_total"]:2.2g + } """ ) @@ -865,7 +886,7 @@ def form_decision_manuelle(Se, formsemestre_id, etudid, desturl="", sortcol=None # précédent n'est pas géré dans ScoDoc (code ADC_) # log(str(Se.sems)) for sem in Se.sems: - if sem["can_compensate"]: + if sem["formsemestre_id"] in Se.can_compensate: H.append( '' % ( @@ -882,7 +903,7 @@ def form_decision_manuelle(Se, formsemestre_id, etudid, desturl="", sortcol=None H.append("") # Choix code semestre precedent: - if Se.prev: + if Se.prev_formsemestre: H.append( 'Code semestre précédent: