diff --git a/app/but/jury_but_recap.py b/app/but/jury_but_recap.py index 4d8e5922b5..12225fd8e3 100644 --- a/app/but/jury_but_recap.py +++ b/app/but/jury_but_recap.py @@ -4,7 +4,7 @@ # See LICENSE ############################################################################## -"""Jury BUT: table recap annuelle et liens saisie +"""Jury BUT et classiques: table recap annuelle et liens saisie """ import collections @@ -19,15 +19,15 @@ from app.but.jury_but import ( DecisionsProposeesUE, ) from app.comp.res_but import ResultatsSemestreBUT +from app.comp.res_compat import NotesTableCompat from app.comp import res_sem +from app.models import UniteEns from app.models.etudiants import Identite from app.scodoc.sco_exceptions import ScoNoReferentielCompetences from app.models.formsemestre import FormSemestre from app.scodoc import html_sco_header from app.scodoc.sco_codes_parcours import ( BUT_BARRE_RCUE, - BUT_BARRE_UE, - BUT_BARRE_UE8, BUT_RCUE_SUFFISANT, ) from app.scodoc import sco_formsemestre_status @@ -39,16 +39,97 @@ from app.tables.recap import RowRecap, TableRecap class TableJury(TableRecap): """Cette table recap reprend les colonnes du tableau recap, sauf les évaluations, et ajoute: - - les RCUEs - - le lien de saisie ou modif de la décision de jury + Pour le BUT: + - les RCUEs (moyenne et code décision) + Pour toutes les formations: + - les codes de décisions jury sur les UEs + - le lien de saisie ou modif de la décision de jury """ + def __init__(self, *args, row_class: str = None, read_only=True, **kwargs): + super().__init__( + *args, row_class=row_class or RowJury, finalize=False, **kwargs + ) + # redéclare pour VSCode + self.rows: list["RowJury"] = self.rows + self.res: NotesTableCompat = self.res + self.read_only = read_only + # Stats jury: fréquence de chaque code enregistré + self.freq_codes_annuels = collections.Counter() + # Ajout colonnes spécifiques à la table jury: + + if self.res.is_apc: + self.add_rcues() + self.add_jury() + # Termine la table + self.finalize() + self.add_groups_header() + + def add_rcues(self): + """Ajoute les colonnes indiquant le nb de RCUEs et chaque RCUE + pour tous les étudiants de la table. + La table contient des rows avec la clé etudid. + Les colonnes ont la classe css "rcue". + """ + self.insert_group("rcue", before="col_ues_validables") + for row in self.rows: + etud: Identite = row.etud + deca = row.deca + if deca.code_valide: + self.freq_codes_annuels[deca.code_valide] += 1 + row.add_nb_rcues_cell() + # --- Les RCUEs + for rcue in deca.rcues_annee: + dec_rcue = deca.dec_rcue_by_ue.get(rcue.ue_1.id) + if dec_rcue is not None: # None si l'UE n'est pas associée à un niveau + row.add_rcue_cols(dec_rcue) + + def add_jury(self): + """Ajoute la colonne code jury et le lien. + Le code jury est celui du semestre: cette colonne n'est montrée + que pour les formations classiques, ce code n'est pas utilisé en BUT. + """ + res = self.res + if res.validations: + for row in self.rows: + etud = row.etud + if not res.is_apc: + # formations classiques: code semestre + dec_sem = res.validations.decisions_jury.get(etud.id) + jury_code_sem = dec_sem["code"] if dec_sem else "" + row.add_cell( + "jury_code_sem", "Jury", jury_code_sem, group="jury_code_sem" + ) + row.add_cell( + "jury_link", + "", + f"""{("modifier" if res.validations.has_decision(etud) else "saisir") + if res.formsemestre.etat else "voir"} décisions""", + group="col_jury_link", + target=url_for( + "notes.formsemestre_validation_etud_form", + scodoc_dept=g.scodoc_dept, + formsemestre_id=res.formsemestre.id, + etudid=etud.id, + ), + target_attrs={"class": "stdlink"}, + ) + class RowJury(RowRecap): "Ligne de la table saisie jury" - def add_nb_rcues_cell(self, deca: DecisionsProposeesAnnee): + def __init__(self, table: TableJury, etud: Identite, *args, **kwargs): + self.table: TableJury = table + super().__init__(table, etud, *args, **kwargs) + # Conserve le deca de cet étudiant: + self.deca = jury_but.DecisionsProposeesAnnee( + self.etud, self.table.res.formsemestre + ) + + def add_nb_rcues_cell(self): "cell avec nb niveaux validables / total" + deca = self.deca classes = ["col_rcue", "col_rcues_validables"] if deca.nb_rcues_under_8 > 0: classes.append("moy_ue_warning") @@ -78,42 +159,71 @@ class RowJury(RowRecap): "RCUEs", f"""{deca.nb_validables}/{deca.nb_competences}""" + ((" " + scu.EMO_WARNING) if deca.nb_rcues_under_8 > 0 else ""), - group="rcues_validables", + raw_content=f"""{deca.nb_validables}/{deca.nb_competences}""", + group="rcue", classes=classes, data={"order": order}, ) - def add_ue_cells(self, dec_ue: DecisionsProposeesUE): - "cell de moyenne d'UE" - col_id = f"moy_ue_{dec_ue.ue.id}" + def add_ue_cols(self, ue: UniteEns, ue_status: dict): + "Ajoute 2 colonnes: moyenne d'UE et code jury" + super().add_ue_cols(ue, ue_status) # table recap standard + dues = self.table.res.get_etud_decision_ues(self.etud.id) + if not dues: + return + due = dues.get(ue.id) + if not due: + return + col_id = f"moy_ue_{ue.id}_code" + self.add_cell( + col_id, + "", # titre vide + due["code"], + raw_content=due["code"], + group="col_ue", + classes=["recorded_code"], + column_classes={"col_jury", "col_ue_code"}, + target_attrs={ + "title": f"""enregistrée le {due['event_date']}, { + (due["ects"] or 0):.3g} ECTS.""" + }, + ) + + def add_rcue_cols(self, dec_rcue: DecisionsProposeesRCUE): + "2 cells: moyenne du RCUE, code enregistré" + self.table.group_titles["rcue"] = "RCUEs en cours" + rcue = dec_rcue.rcue + col_id = f"moy_rcue_{rcue.ue_1.niveau_competence_id}" # le niveau_id note_class = "" - val = dec_ue.moy_ue + val = rcue.moy_rcue if isinstance(val, float): - if val < BUT_BARRE_UE: - note_class = "moy_inf" - elif val >= BUT_BARRE_UE: + if val < BUT_BARRE_RCUE: + note_class = "moy_ue_inf" + elif val >= BUT_BARRE_RCUE: note_class = "moy_ue_valid" - if val < BUT_BARRE_UE8: + if val < BUT_RCUE_SUFFISANT: note_class = "moy_ue_warning" # notes très basses self.add_cell( col_id, - dec_ue.ue.acronyme, - self.fmt_note(val), - group="col_ue", - classes="col_ue" + note_class, - column_class="col_ue", + f"
{rcue.ue_1.acronyme}
{rcue.ue_2.acronyme}
", + self.table.fmt_note(val), + raw_content=val, + group="rcue", + classes=[note_class], + column_classes={"col_rcue"}, ) self.add_cell( col_id + "_code", - dec_ue.ue.acronyme, - dec_ue.code_valide or "", - classes="col_ue_code recorded_code", - column_class="col_ue", + f"
{rcue.ue_1.acronyme}
{rcue.ue_2.acronyme}
", + dec_rcue.code_valide or "", + group="rcue", + classes=["col_rcue_code", "recorded_code"], + column_classes={"col_rcue"}, ) def formsemestre_saisie_jury_but( - formsemestre2: FormSemestre, + formsemestre: FormSemestre, read_only: bool = False, selected_etudid: int = None, mode="jury", @@ -125,7 +235,6 @@ def formsemestre_saisie_jury_but( Si mode == "recap", table recap des codes, sans liens de saisie. """ - # Quick & Dirty # pour chaque etud de res2 trié # S1: UE1, ..., UEn # S2: UE1, ..., UEn @@ -137,32 +246,40 @@ def formsemestre_saisie_jury_but( # Pour le 1er etud, faire un check_ues_ready_jury(self) -> page d'erreur # -> rcue .ue_1, .ue_2 -> stroe moy ues, rcue.moy_rcue, etc - if formsemestre2.formation.referentiel_competence is None: - raise ScoNoReferentielCompetences(formation=formsemestre2.formation) + if formsemestre.formation.referentiel_competence is None: + raise ScoNoReferentielCompetences(formation=formsemestre.formation) - rows, titles, column_ids, jury_stats = get_jury_but_table( - formsemestre2, read_only=read_only, mode=mode + res: ResultatsSemestreBUT = res_sem.load_formsemestre_results(formsemestre) + table = TableJury( + res, + convert_values=True, + mode_jury=True, + read_only=read_only, + classes=[ + "table_jury_but_bilan" if mode == "recap" else "", + "table_recap", + "apc", + "jury table_jury_but", + ], + selected_row_id=selected_etudid, ) - if not rows: + if table.is_empty(): return ( '
aucun étudiant !
' ) - filename = scu.sanitize_filename( - f"""jury-but-{formsemestre2.titre_num()}-{time.strftime("%Y-%m-%d")}""" - ) - klass = "table_jury_but_bilan" if mode == "recap" else "" - table_html = build_table_jury_but_html( - filename, rows, titles, column_ids, selected_etudid=selected_etudid, klass=klass + table.data["filename"] = scu.sanitize_filename( + f"""jury-but-{formsemestre.titre_num()}-{time.strftime("%Y-%m-%d")}""" ) + table_html = table.html() H = [ html_sco_header.sco_header( - page_title=f"{formsemestre2.sem_modalite()}: jury BUT annuel", + page_title=f"{formsemestre.sem_modalite()}: jury BUT", no_side_bar=True, init_qtip=True, javascripts=["js/etud_info.js", "js/table_recap.js"], ), sco_formsemestre_status.formsemestre_status_head( - formsemestre_id=formsemestre2.id + formsemestre_id=formsemestre.id ), ] if mode == "recap": @@ -173,12 +290,12 @@ def formsemestre_saisie_jury_but(