diff --git a/app/but/bulletin_but.py b/app/but/bulletin_but.py index 4af56a3ca..9f0a9b85c 100644 --- a/app/but/bulletin_but.py +++ b/app/but/bulletin_but.py @@ -385,7 +385,7 @@ class BulletinBUT: "injustifie": nbabs - nbabsjust, "total": nbabs, } - decisions_ues = self.res.get_etud_decision_ues(etud.id) or {} + decisions_ues = self.res.get_etud_decisions_ue(etud.id) or {} if self.prefs["bul_show_ects"]: ects_tot = res.etud_ects_tot_sem(etud.id) ects_acquis = res.get_etud_ects_valides(etud.id, decisions_ues) diff --git a/app/comp/res_compat.py b/app/comp/res_compat.py index e973a7a6c..1ebac8b17 100644 --- a/app/comp/res_compat.py +++ b/app/comp/res_compat.py @@ -271,9 +271,9 @@ class NotesTableCompat(ResultatsSemestre): def etud_has_decision(self, etudid): """True s'il y a une décision de jury pour cet étudiant""" - return self.get_etud_decision_ues(etudid) or self.get_etud_decision_sem(etudid) + return self.get_etud_decisions_ue(etudid) or self.get_etud_decision_sem(etudid) - def get_etud_decision_ues(self, etudid: int) -> dict: + def get_etud_decisions_ue(self, etudid: int) -> dict: """Decisions du jury pour les UE de cet etudiant, ou None s'il n'y en pas eu. Ne tient pas compte des UE capitalisées. { ue_id : { 'code' : ADM|CMP|AJ, 'event_date' : "d/m/y", 'ects' : x } @@ -288,10 +288,10 @@ class NotesTableCompat(ResultatsSemestre): def get_etud_ects_valides(self, etudid: int, decisions_ues: dict = False) -> 0: """Le total des ECTS validés (et enregistrés) par l'étudiant dans ce semestre. NB: avant jury, rien d'enregistré, donc zéro ECTS. - Optimisation: si decisions_ues est passé, l'utilise, sinon appelle get_etud_decision_ues() + Optimisation: si decisions_ues est passé, l'utilise, sinon appelle get_etud_decisions_ue() """ if decisions_ues is False: - decisions_ues = self.get_etud_decision_ues(etudid) + decisions_ues = self.get_etud_decisions_ue(etudid) if not decisions_ues: return 0.0 return sum([d.get("ects", 0.0) for d in decisions_ues.values()]) diff --git a/app/models/etudiants.py b/app/models/etudiants.py index cc1d4d528..c49717111 100644 --- a/app/models/etudiants.py +++ b/app/models/etudiants.py @@ -183,6 +183,10 @@ class Identite(db.Model): e["etudid"] = self.id e["date_naissance"] = ndb.DateISOtoDMY(e["date_naissance"]) e["ne"] = self.e + e["nomprenom"] = self.nomprenom + adresse = self.adresses.first() + if adresse: + e.update(adresse.to_dict()) return {k: e[k] or "" for k in e} # convert_null_outputs_to_empty def to_dict_bul(self, include_urls=True): diff --git a/app/scodoc/notes_table.py b/app/scodoc/notes_table.py index 0379bbc29..953697484 100644 --- a/app/scodoc/notes_table.py +++ b/app/scodoc/notes_table.py @@ -1102,7 +1102,7 @@ class NotesTable: else: return self.decisions_jury.get(etudid, None) - def get_etud_decision_ues(self, etudid): + def get_etud_decisions_ue(self, etudid): """Decisions du jury pour les UE de cet etudiant, ou None s'il n'y en pas eu. Ne tient pas compte des UE capitalisées. { ue_id : { 'code' : ADM|CMP|AJ, 'event_date' : } @@ -1122,7 +1122,7 @@ class NotesTable: def etud_has_decision(self, etudid): """True s'il y a une décision de jury pour cet étudiant""" - return self.get_etud_decision_ues(etudid) or self.get_etud_decision_sem(etudid) + return self.get_etud_decisions_ue(etudid) or self.get_etud_decision_sem(etudid) def all_etuds_have_sem_decisions(self): """True si tous les étudiants du semestre ont une décision de jury. diff --git a/app/scodoc/sco_apogee_csv.py b/app/scodoc/sco_apogee_csv.py index 1045e7ba4..1e9610c1b 100644 --- a/app/scodoc/sco_apogee_csv.py +++ b/app/scodoc/sco_apogee_csv.py @@ -433,7 +433,7 @@ class ApoEtud(dict): return VOID_APO_RES # Elements UE - decisions_ue = nt.get_etud_decision_ues(etudid) + decisions_ue = nt.get_etud_decisions_ue(etudid) for ue in nt.get_ues_stat_dict(): if ue["code_apogee"] and code in { x.strip() for x in ue["code_apogee"].split(",") diff --git a/app/scodoc/sco_bulletins_pdf.py b/app/scodoc/sco_bulletins_pdf.py index 0e0883704..a26397915 100644 --- a/app/scodoc/sco_bulletins_pdf.py +++ b/app/scodoc/sco_bulletins_pdf.py @@ -124,6 +124,24 @@ def replacement_function(match): ) +class WrapDict(object): + """Wrap a dict so that getitem returns '' when values are None""" + + def __init__(self, adict, NoneValue=""): + self.dict = adict + self.NoneValue = NoneValue + + def __getitem__(self, key): + try: + value = self.dict[key] + except KeyError: + return f"XXX {key} invalide XXX" + if value is None: + return self.NoneValue + else: + return value + + def process_field(field, cdict, style, suppress_empty_pars=False, format="pdf"): """Process a field given in preferences, returns - if format = 'pdf': a list of Platypus objects @@ -137,18 +155,19 @@ def process_field(field, cdict, style, suppress_empty_pars=False, format="pdf"): If format = 'html', replaces by

. HTML does not allow logos. """ try: - text = (field or "") % scu.WrapDict( - cdict - ) # note that None values are mapped to empty strings + # None values are mapped to empty strings by WrapDict + text = (field or "") % WrapDict(cdict) except KeyError as exc: + missing_key = exc.args[0] if len(exc.args) > 0 else "?" log( - f"""process_field: KeyError on field={field!r} + f"""process_field: KeyError {missing_key} on field={field!r} values={pprint.pformat(cdict)} """ ) - if len(exc.args) > 0: - missing_field = exc.args[0] - text = f"""format invalide: champs {missing_field} inexistant !""" + text = f"""format invalide: champs {missing_key} inexistant !""" + scu.flash_once( + f"Attention: format PDF invalide (champs {field}, clef {missing_key})" + ) except: # pylint: disable=bare-except log( f"""process_field: invalid format. field={field!r} @@ -156,7 +175,7 @@ def process_field(field, cdict, style, suppress_empty_pars=False, format="pdf"): """ ) # ne sera pas visible si lien vers pdf: - scu.flash_once(f"Attention: format PDF invalide (champs {field}") + scu.flash_once(f"Attention: format PDF invalide (champs {field})") text = ( "format invalide !" + traceback.format_exc() diff --git a/app/scodoc/sco_pv_dict.py b/app/scodoc/sco_pv_dict.py index 0dcc421ff..9c059b62a 100644 --- a/app/scodoc/sco_pv_dict.py +++ b/app/scodoc/sco_pv_dict.py @@ -42,6 +42,7 @@ from app.models import ( but_validations, ) from app.scodoc import codes_cursus +from app.scodoc import sco_edit_ue from app.scodoc import sco_etud from app.scodoc import sco_formsemestre from app.scodoc import sco_cursus @@ -109,7 +110,7 @@ def dict_pvjury( etudid ) # I|D|DEF (inscription ou démission ou défaillant) d["decision_sem"] = nt.get_etud_decision_sem(etudid) - d["decisions_ue"] = nt.get_etud_decision_ues(etudid) + d["decisions_ue"] = nt.get_etud_decisions_ue(etudid) if formsemestre.formation.is_apc(): d.update(but_validations.dict_decision_jury(etud, formsemestre)) d["last_formsemestre_id"] = Se.get_semestres()[ @@ -241,17 +242,17 @@ def _comp_ects_capitalises_by_ue_code(nt: NotesTableCompat, etudid: int): return ects_by_ue_code -def _comp_ects_by_ue_code(nt, decision_ues): +def _comp_ects_by_ue_code(nt, decisions_ue): """Calcul somme des ECTS validés dans ce semestre (sans les UE capitalisées) - decision_ues est le resultat de nt.get_etud_decision_ues + decisions_ue est le resultat de nt.get_etud_decisions_ue Chaque resultat est un dict: { ue_code : ects } """ - if not decision_ues: + if not decisions_ue: return {} ects_by_ue_code = {} - for ue_id in decision_ues: - d = decision_ues[ue_id] + for ue_id in decisions_ue: + d = decisions_ue[ue_id] ue = UniteEns.query.get(ue_id) ects_by_ue_code[ue.ue_code] = d["ects"] @@ -269,26 +270,22 @@ def _descr_decisions_ues(nt, etudid, decisions_ue, decision_sem) -> list[dict]: return [] uelist = [] # Les UE validées dans ce semestre: - for ue_id in decisions_ue.keys(): - try: - if decisions_ue[ue_id] and ( - codes_cursus.code_ue_validant(decisions_ue[ue_id]["code"]) - or ( - (not nt.is_apc) - and ( - # XXX ceci devrait dépendre du parcours et non pas être une option ! #sco8 - decision_sem - and scu.CONFIG.CAPITALIZE_ALL_UES - and codes_cursus.code_semestre_validant(decision_sem["code"]) - ) + for ue_id in decisions_ue: + if decisions_ue[ue_id] and ( + codes_cursus.code_ue_validant(decisions_ue[ue_id].get("code")) + or ( + (not nt.is_apc) + and ( + # XXX ceci devrait dépendre du parcours et non pas être une option ! #sco8 + decision_sem + and scu.CONFIG.CAPITALIZE_ALL_UES + and decision_sem + and codes_cursus.code_semestre_validant(decision_sem.get("code")) ) - ): - ue = sco_edit_ue.ue_list(args={"ue_id": ue_id})[0] - uelist.append(ue) - except: - log( - f"Exception in descr_decisions_ues: ue_id={ue_id} decisions_ue={decisions_ue}" ) + ): + ue = sco_edit_ue.ue_list(args={"ue_id": ue_id})[0] + uelist.append(ue) # Les UE capitalisées dans d'autres semestres: if etudid in nt.validations.ue_capitalisees.index: for ue_id in nt.validations.ue_capitalisees.loc[[etudid]]["ue_id"]: diff --git a/app/scodoc/sco_pv_lettres_inviduelles.py b/app/scodoc/sco_pv_lettres_inviduelles.py index 7a4f2f50a..66868bcbe 100644 --- a/app/scodoc/sco_pv_lettres_inviduelles.py +++ b/app/scodoc/sco_pv_lettres_inviduelles.py @@ -47,7 +47,6 @@ from app.models import FormSemestre, Identite import app.scodoc.sco_utils as scu from app.scodoc import sco_bulletins_pdf from app.scodoc import sco_pv_dict -from app.scodoc import sco_etud from app.scodoc import sco_pdf from app.scodoc import sco_preferences from app.scodoc.sco_exceptions import ScoValueError @@ -70,9 +69,6 @@ def pdf_lettres_individuelles( dpv = sco_pv_dict.dict_pvjury(formsemestre_id, etudids=etudids, with_prev=True) if not dpv: return "" - # Ajoute infos sur etudiants - etuds = [x["identite"] for x in dpv["decisions"]] - sco_etud.fill_etuds_info(etuds) # formsemestre: FormSemestre = FormSemestre.query.get(formsemestre_id) prefs = sco_preferences.SemPreferences(formsemestre_id) @@ -95,9 +91,10 @@ def pdf_lettres_individuelles( decision["decision_sem"] or decision.get("decision_annee") or decision.get("decision_rcue") + or decision.get("decisions_ue") ): # decision prise etud: Identite = Identite.query.get(decision["identite"]["etudid"]) - params["nomEtud"] = etud.nomprenom + params["nomEtud"] = etud.nomprenom # backward compat bookmarks[npages + 1] = scu.suppress_accents(etud.nomprenom) try: objects += pdf_lettre_individuelle( @@ -217,7 +214,7 @@ def pdf_lettre_individuelle(sem, decision, etud: Identite, params, signature=Non params.update(decision["identite"]) # fix domicile - if params["domicile"]: + if params.get("domicile"): params["domicile"] = params["domicile"].replace("\\n", "
") # UE capitalisées: diff --git a/app/scodoc/sco_saisie_notes.py b/app/scodoc/sco_saisie_notes.py index bf8d8c296..b8a2de08c 100644 --- a/app/scodoc/sco_saisie_notes.py +++ b/app/scodoc/sco_saisie_notes.py @@ -953,7 +953,7 @@ def has_existing_decision(M, E, etudid): nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre) if nt.get_etud_decision_sem(etudid): return True - dec_ues = nt.get_etud_decision_ues(etudid) + dec_ues = nt.get_etud_decisions_ue(etudid) if dec_ues: mod = sco_edit_module.module_list({"module_id": M["module_id"]})[0] ue_id = mod["ue_id"] diff --git a/app/scodoc/sco_utils.py b/app/scodoc/sco_utils.py index 34ea2f0d1..abdff099e 100644 --- a/app/scodoc/sco_utils.py +++ b/app/scodoc/sco_utils.py @@ -291,21 +291,6 @@ class DictDefault(dict): # obsolete, use collections.defaultdict return value -class WrapDict(object): - """Wrap a dict so that getitem returns '' when values are None""" - - def __init__(self, adict, NoneValue=""): - self.dict = adict - self.NoneValue = NoneValue - - def __getitem__(self, key): - value = self.dict[key] - if value is None: - return self.NoneValue - else: - return value - - def group_by_key(d, key): gr = DictDefault(defaultvalue=[]) for e in d: diff --git a/app/tables/jury_recap.py b/app/tables/jury_recap.py index 78ed33b5c..f10437a8d 100644 --- a/app/tables/jury_recap.py +++ b/app/tables/jury_recap.py @@ -158,7 +158,7 @@ class TableJury(TableRecap): titre, validation_rcue.code, group="cursus_" + annee, - classes=["recorded_code"], + classes=[], column_classes=["cursus_but" + (" first" if first else "")], target_attrs={ "title": f"{niveau.competence.titre} niveau {niveau.ordre}" @@ -221,7 +221,7 @@ class RowJury(RowRecap): "Ajoute 2 colonnes: moyenne d'UE et code jury" # table recap standard (mais avec group différent) super().add_ue_cols(ue, ue_status, col_group=col_group or "col_ue") - dues = self.table.res.get_etud_decision_ues(self.etud.id) + dues = self.table.res.get_etud_decisions_ue(self.etud.id) due = dues.get(ue.id) if dues else None col_id = f"moy_ue_{ue.id}_code" diff --git a/tests/unit/test_sco_basic.py b/tests/unit/test_sco_basic.py index 5b5f85e6f..0030d036d 100644 --- a/tests/unit/test_sco_basic.py +++ b/tests/unit/test_sco_basic.py @@ -233,7 +233,7 @@ def run_sco_basic(verbose=False) -> FormSemestre: formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id) nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre) for etud in etuds[:5]: - dec_ues = nt.get_etud_decision_ues(etud["etudid"]) + dec_ues = nt.get_etud_decisions_ue(etud["etudid"]) for ue_id in dec_ues: assert dec_ues[ue_id]["code"] in {"ADM", "CMP"}