From 711f8cfcfb441e74d4f986dee8325cd7f48f91e4 Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Fri, 3 Feb 2023 22:39:45 +0100 Subject: [PATCH] WIP: refactoring table recap --- app/api/formsemestres.py | 3 + app/but/jury_but_recap.py | 42 +- app/comp/res_common.py | 711 +----------------------- app/scodoc/sco_cache.py | 4 +- app/scodoc/sco_recapcomplet.py | 26 +- app/{scodoc => tables}/table_builder.py | 0 6 files changed, 28 insertions(+), 758 deletions(-) rename app/{scodoc => tables}/table_builder.py (100%) diff --git a/app/api/formsemestres.py b/app/api/formsemestres.py index 3ec024793..e7231e556 100644 --- a/app/api/formsemestres.py +++ b/app/api/formsemestres.py @@ -32,6 +32,7 @@ from app.scodoc import sco_groups from app.scodoc.sco_permissions import Permission from app.scodoc.sco_utils import ModuleType import app.scodoc.sco_utils as scu +from app.tables.recap import TableRecap @bp.route("/formsemestre/") @@ -476,12 +477,14 @@ def formsemestre_resultat(formsemestre_id: int): formsemestre: FormSemestre = query.first_or_404(formsemestre_id) app.set_sco_dept(formsemestre.departement.acronym) res: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre) + table = TableRecap() table = res.get_table_recap( convert_values=convert_values, include_evaluations=False, mode_jury=False, allow_html=False, ) + raise NotImplementedError() # A COMPLETER XXX # Supprime les champs inutiles (mise en forme) rows = table.to_list() # Ajoute le groupe de chaque partition: diff --git a/app/but/jury_but_recap.py b/app/but/jury_but_recap.py index 2e56ca3c3..d47e38a79 100644 --- a/app/but/jury_but_recap.py +++ b/app/but/jury_but_recap.py @@ -33,14 +33,14 @@ from app.scodoc.sco_codes_parcours import ( from app.scodoc import sco_formsemestre_status from app.scodoc import sco_pvjury from app.scodoc import sco_utils as scu -from app.scodoc import table_builder as tb +from app.tables.recap import RowRecap, TableRecap -class TableJury(tb.Table): +class TableJury(TableRecap): pass -class RowJury(tb.Row): +class RowJury(RowRecap): "Ligne de la table saisie jury" def add_nb_rcues_cell(self, deca: DecisionsProposeesAnnee): @@ -96,14 +96,14 @@ class RowJury(tb.Row): dec_ue.ue.acronyme, self.fmt_note(val), group="col_ue", - "col_ue" + note_class, + classes="col_ue" + note_class, column_class="col_ue", ) self.add_cell( col_id + "_code", dec_ue.ue.acronyme, dec_ue.code_valide or "", - "col_ue_code recorded_code", + classes="col_ue_code recorded_code", column_class="col_ue", ) @@ -342,38 +342,6 @@ class RowCollector: self.column_classes[col_id] = column_class self.idx += 1 - # def add_etud_cells( - # self, etud: Identite, formsemestre: FormSemestre, with_links=True - # ): - # "Les cells code, nom, prénom etc." - # # --- Codes (seront cachés, mais exportés en excel) - # self.add_cell("etudid", "etudid", etud.id, "codes") - # self.add_cell("code_nip", "code_nip", etud.code_nip or "", "codes") - # # --- Identité étudiant (adapté de res_common/get_table_recap, à factoriser XXX TODO) - # self.add_cell("civilite_str", "Civ.", etud.civilite_str, "identite_detail") - # self.add_cell("nom_disp", "Nom", etud.nom_disp(), "identite_detail") - # self["_nom_disp_order"] = etud.sort_key - # self.add_cell("prenom", "Prénom", etud.prenom, "identite_detail") - # self.add_cell("nom_short", "Nom", etud.nom_short, "identite_court") - # self["_nom_short_data"] = { - # "etudid": etud.id, - # "nomprenom": etud.nomprenom, - # } - # if with_links: - # self["_nom_short_order"] = etud.sort_key - # self["_nom_short_target"] = url_for( - # "notes.formsemestre_bulletinetud", - # scodoc_dept=g.scodoc_dept, - # formsemestre_id=formsemestre.id, - # etudid=etud.id, - # ) - # self["_nom_short_target_attrs"] = f'class="etudinfo" id="{etud.id}"' - # self["_nom_disp_target"] = self["_nom_short_target"] - # self["_nom_disp_target_attrs"] = self["_nom_short_target_attrs"] - # self.last_etud_cell_idx = self.idx - - - def add_rcue_cells(self, dec_rcue: DecisionsProposeesRCUE): "2 cells: moyenne du RCUE, code enregistré" rcue = dec_rcue.rcue diff --git a/app/comp/res_common.py b/app/comp/res_common.py index ce5886efb..c89bca49e 100644 --- a/app/comp/res_common.py +++ b/app/comp/res_common.py @@ -15,7 +15,6 @@ import pandas as pd from flask import g, url_for -from app.auth.models import User from app.comp import res_sem from app.comp.res_cache import ResultatsCache from app.comp.jury import ValidationsSemestre @@ -25,12 +24,10 @@ from app.models import Identite from app.models import ModuleImpl, ModuleImplInscription from app.models.ues import UniteEns from app.scodoc.sco_cache import ResultatsSemestreCache -from app.scodoc.sco_codes_parcours import UE_SPORT, DEF, DEM -from app.scodoc import sco_evaluation_db +from app.scodoc.sco_codes_parcours import UE_SPORT from app.scodoc.sco_exceptions import ScoValueError from app.scodoc import sco_groups from app.scodoc import sco_utils as scu -from app.scodoc import table_builder as tb # Il faut bien distinguer # - ce qui est caché de façon persistente (via redis): @@ -432,709 +429,3 @@ class ResultatsSemestre(ResultatsCache): # ici si l'étudiant est inscrit dans le semestre courant, # somme des coefs des modules de l'UE auxquels il est inscrit return self.compute_etud_ue_coef(etudid, ue) - - -# --- TABLEAU RECAP - - -class TableRecap(tb.Table): # was get_table_recap - """Table récap. des résultats. - allow_html: si vrai, peut mettre du HTML dans les valeurs - - Result: Table, le contenu étant une ligne par étudiant. - - - Si convert_values, transforme les notes en chaines ("12.34"). - Les colonnes générées sont: - etudid - rang : rang indicatif (basé sur moy gen) - moy_gen : moy gen indicative - moy_ue_, ..., les moyennes d'UE - moy_res__, ... les moyennes de ressources dans l'UE - moy_sae__, ... les moyennes de SAE dans l'UE - - On ajoute aussi des classes: - - pour les lignes: - selected_row pour l'étudiant sélectionné - - les colonnes: - - la moyenne générale a la classe col_moy_gen - - les colonnes SAE ont la classe col_sae - - les colonnes Resources ont la classe col_res - - les colonnes d'UE ont la classe col_ue - - les colonnes de modules (SAE ou res.) d'une UE ont la classe mod_ue_ - """ - - def __init__( - self, - res: ResultatsSemestre, - convert_values=False, - include_evaluations=False, - mode_jury=False, - ): - self.res = res - self.include_evaluations = include_evaluations - self.mode_jury = mode_jury - - parcours = self.formsemestre.formation.get_parcours() - self.barre_moy = parcours.BARRE_MOY - scu.NOTES_TOLERANCE - self.barre_valid_ue = parcours.NOTES_BARRE_VALID_UE - self.barre_warning_ue = parcours.BARRE_UE_DISPLAY_WARNING - self.cache_nomcomplet = {} # cache uid : nomcomplet - if convert_values: - self.fmt_note = scu.fmt_note - else: - self.fmt_note = lambda x: x - # couples (modimpl, ue) effectivement présents dans la table: - self.modimpl_ue_ids = set() - - etuds_inscriptions = self.formsemestre.etuds_inscriptions - ues = self.formsemestre.query_ues(with_sport=True) # avec bonus - ues_sans_bonus = [ue for ue in ues if ue.type != UE_SPORT] - - for etudid in etuds_inscriptions: - etud = Identite.query.get(etudid) - row = RowRecap(self, etudid) - self.add_row(row) - self.recap_add_etud(row, etud) - self._recap_add_moyennes(row, etud, ues_sans_bonus) - - self.recap_add_partitions() - self.recap_add_cursus() - self._recap_add_admissions() - - # tri par rang croissant - if not self.formsemestre.block_moyenne_generale: - self.sort_rows(key=lambda row: row.rang_order) - else: - self.sort_rows(key=lambda row: row.nb_ues_validables, reverse=True) - - # Lignes footer (min, max, ects, apo, ...) - self.add_bottom_rows(ues_sans_bonus) - - # Evaluations: - if include_evaluations: - self.add_evaluations() - - self.mark_empty_cols() - self.add_type_row() - - def mark_empty_cols(self): - """Ajoute style "col_empty" aux colonnes de modules vides""" - # identifie les col. vides par la classe sur leur moyenne - row_moy = self.get_row_by_id("moy") - for col_id in self.column_ids: - cell: tb.Cell = row_moy.cells.get(col_id) - if cell and "col_empty" in cell.classes: - self.column_classes[col_id].append("col_empty") - - def add_type_row(self): - """Ligne avec la classe de chaque colonne recap.""" - # récupère le type à partir des classes css (hack...) - row_type = tb.BottomRow( - self, - "type_col", - left_title="Type col.", - left_title_col_ids=["prenom", "nom_short"], - category="bottom_infos", - classes=["bottom_info"], - ) - for col_id in self.column_ids: - group_name = self.column_group.get(col_id, "") - if group_name.startswith("col_"): - group_name = group_name[4:] - row_type.add_cell(col_id, None, group_name) - - def add_bottom_rows(self, ues): - """Les informations à mettre en bas de la table recap: - min, max, moy, ECTS, Apo.""" - res = self.res - # Ordre des lignes: Min, Max, Moy, Coef, ECTS, Apo - row_min = tb.BottomRow( - self, - "min", - left_title="Min.", - left_title_col_ids=["prenom", "nom_short"], - category="bottom_infos", - classes=["bottom_info"], - ) - row_max = tb.BottomRow( - self, - "max", - left_title="Max.", - left_title_col_ids=["prenom", "nom_short"], - category="bottom_infos", - classes=["bottom_info"], - ) - row_moy = tb.BottomRow( - self, - "moy", - left_title="Moy.", - left_title_col_ids=["prenom", "nom_short"], - category="bottom_infos", - classes=["bottom_info"], - ) - row_coef = tb.BottomRow( - self, - "coef", - left_title="Coef.", - left_title_col_ids=["prenom", "nom_short"], - category="bottom_infos", - classes=["bottom_info"], - ) - row_ects = tb.BottomRow( - self, - "ects", - left_title="ECTS", - left_title_col_ids=["prenom", "nom_short"], - category="bottom_infos", - classes=["bottom_info"], - ) - row_apo = tb.BottomRow( - self, - "apo", - left_title="Code Apogée", - left_title_col_ids=["prenom", "nom_short"], - category="bottom_infos", - classes=["bottom_info"], - ) - - # --- ECTS - # titre (à gauche) sur 2 colonnes pour s'adapter à l'affichage des noms/prenoms - for ue in ues: - col_id = f"moy_ue_{ue.id}" - row_ects.add_cell(col_id, None, ue.ects) - # ajoute cell UE vides sur ligne coef pour borders verticales - # XXX TODO classes dans table sur colonne ajoutées à tous les TD - row_coef.add_cell(col_id, None, "") - row_ects.add_cell( - "moy_gen", - None, - sum([ue.ects or 0 for ue in ues if ue.type != UE_SPORT]), - ) - # --- MIN, MAX, MOY, APO - row_min.add_cell("moy_gen", None, self.fmt_note(res.etud_moy_gen.min())) - row_max.add_cell("moy_gen", None, self.fmt_note(res.etud_moy_gen.max())) - row_moy.add_cell("moy_gen", None, self.fmt_note(res.etud_moy_gen.mean())) - - for ue in ues: - col_id = f"moy_ue_{ue.id}" - row_min.add_cell(col_id, None, self.fmt_note(res.etud_moy_ue[ue.id].min())) - row_max.add_cell(col_id, None, self.fmt_note(res.etud_moy_ue[ue.id].max())) - row_moy.add_cell(col_id, None, self.fmt_note(res.etud_moy_ue[ue.id].mean())) - row_apo.add_cell(col_id, None, ue.code_apogee or "") - - for modimpl in res.formsemestre.modimpls_sorted: - if (modimpl.id, ue.id) in self.modimpl_ue_ids: - col_id = f"moy_{modimpl.module.type_abbrv()}_{modimpl.id}_{ue.id}" - if res.is_apc: - coef = res.modimpl_coefs_df[modimpl.id][ue.id] - else: - coef = modimpl.module.coefficient or 0 - row_coef.add_cell( - col_id, - None, - self.fmt_note(coef), - group=f"col_ue_{ue.id}_modules", - ) - notes = res.modimpl_notes(modimpl.id, ue.id) - if np.isnan(notes).all(): - # aucune note valide - row_min.add_cell(col_id, None, np.nan) - row_max.add_cell(col_id, None, np.nan) - moy = np.nan - else: - row_min.add_cell(col_id, None, self.fmt_note(np.nanmin(notes))) - row_max.add_cell(col_id, None, self.fmt_note(np.nanmax(notes))) - moy = np.nanmean(notes) - row_moy.add_cell( - col_id, - None, - self.fmt_note(moy), - # aucune note dans ce module ? - classes=["col_empty" if np.isnan(moy) else ""], - ) - row_apo.add_cell(col_id, None, modimpl.module.code_apogee or "") - - -class RowRecap(tb.Row): - "Ligne de la table recap" - - def add_etud(self, etud: Identite): - """Ajoute colonnes étudiant: codes, noms""" - res = self.table.res - # --- Codes (seront cachés, mais exportés en excel) - self.add_cell("etudid", "etudid", etud.id, "etud_codes") - self.add_cell( - "code_nip", - "code_nip", - etud.code_nip or "", - "etud_codes", - ) - - # --- Rang - if not res.formsemestre.block_moyenne_generale: - self.rang_order = res.etud_moy_gen_ranks_int[etud.id] - res.add_cell( - "rang", - "Rg", - self.etud_moy_gen_ranks[etud.id], - "rang", - data={"order": f"{self.rang_order:05d}"}, - ) - else: - self.rang_order = -1 - # --- Identité étudiant - url_bulletin = url_for( - "notes.formsemestre_bulletinetud", - scodoc_dept=g.scodoc_dept, - formsemestre_id=res.formsemestre.id, - etudid=etud.id, - ) - self.add_cell("civilite_str", "Civ.", etud.civilite_str, "identite_detail") - self.add_cell( - "nom_disp", - "Nom", - etud.nom_disp(), - "identite_detail", - data={"order": etud.sort_key}, - target=url_bulletin, - target_attrs={"class": "etudinfo", "id": str(etud.id)}, - ) - self.add_cell("prenom", "Prénom", etud.prenom, "identite_detail") - self.add_cell( - "nom_short", - "Nom", - etud.nom_short, - "identite_court", - data={ - "order": etud.sort_key, - "etudid": etud.id, - "nomprenom": etud.nomprenom, - }, - target=url_bulletin, - target_attrs={"class": "etudinfo", "id": str(etud.id)}, - ) - - def add_moyennes( # XXX was _recap_add_moyennes - self, - row: tb.Row, - etud: Identite, - ues_sans_bonus: list[UniteEns], - ): - """Ajoute cols moy_gen moy_ue et tous les modules...""" - table = self.table - res = table.res - # --- Moyenne générale - if not res.formsemestre.block_moyenne_generale: - moy_gen = res.etud_moy_gen.get(etud.id, False) - note_class = "" - if moy_gen is False: - moy_gen = scu.NO_NOTE_STR - elif isinstance(moy_gen, float) and moy_gen < table.barre_moy: - note_class = "moy_ue_warning" # en rouge - row.add_cell( - "moy_gen", - "Moy", - table.fmt_note(moy_gen), - "col_moy_gen", - classes=[note_class], - ) - # Ajoute bulle sur titre du pied de table: - if res.is_apc: - table.foot_title_row.cells["moy_gen"].target_attrs[ - "title" - ] = "moyenne indicative" - - # --- Moyenne d'UE - self.nb_ues_validables, self.nb_ues_warning = 0, 0 - for ue in ues_sans_bonus: - ue_status = res.get_etud_ue_status(etud.id, ue.id) - if ue_status is not None: - self.add_ue(ue, ue_status) - if table.mode_jury: - # pas d'autre colonnes de résultats - continue - - # Bonus (sport) dans cette UE ? - # Le bonus sport appliqué sur cette UE - if (res.bonus_ues is not None) and (ue.id in res.bonus_ues): - val = res.bonus_ues[ue.id][etud.id] or "" - val_fmt = val_fmt_html = table.fmt_note(val) - if val: - val_fmt_html = f"""{ - val_fmt - }""" - row.add_cell( - f"bonus_ue_{ue.id}", - f"Bonus {ue.acronyme}", - val_fmt_html, - raw_content=val_fmt, - group=f"col_ue_{ue.id}", - classes=["col_ue_bonus"], - ) - # Les moyennes des modules (ou ressources et SAÉs) dans cette UE - self.add_ue_modimpls( - ue, etud, ue_status["is_capitalized"] - ) # XXX _recap_add_ue_modimpls - - self.nb_ues_etud_parcours = len(res.etud_ues_ids(etud.id)) - ue_valid_txt = ( - ue_valid_txt_html - ) = f"{self.nb_ues_validables}/{self.nb_ues_etud_parcours}" - if self.nb_ues_warning: - ue_valid_txt_html += " " + scu.EMO_WARNING - # place juste avant moy. gen. - table.insert_group("col_ues_validables", before="col_moy_gen") - classes = ["col_ue"] - if self.nb_ues_warning: - classes.append("moy_ue_warning") - elif self.nb_ues_validables < len(ues_sans_bonus): - classes.append("moy_inf") - row.add_cell( - "ues_validables", - "UEs", - ue_valid_txt_html, - group="col_ues_validables", - classes=classes, - raw_content=ue_valid_txt, - data={"order": self.nb_ues_validables}, # tri - ) - - if table.mode_jury and res.validations: - if res.is_apc: - # formations BUT: pas de code semestre, concatene ceux des UEs - dec_ues = res.validations.decisions_jury_ues.get(etud.id) - if dec_ues: - jury_code_sem = ",".join( - [dec_ues[ue_id].get("code", "") for ue_id in dec_ues] - ) - else: - jury_code_sem = "" - else: - # formations classiques: code semestre - dec_sem = res.validations.decisions_jury.get(etud.id) - jury_code_sem = dec_sem["code"] if dec_sem else "" - self.add_cell("jury_code_sem", "Jury", jury_code_sem, "jury_code_sem") - self.add_cell( - "jury_link", - "", - f"""{("saisir" if not jury_code_sem else "modifier") - if res.formsemestre.etat else "voir"} décisions""", - "col_jury_link", - ) - - def add_ue(self, ue: UniteEns, ue_status: dict): - "Ajoute résultat UE au row (colonne col_ue)" - table = self.table - col_id = f"moy_ue_{ue.id}" - val = ue_status["moy"] - note_class = "" - if isinstance(val, float): - if val < table.barre_moy: - note_class = "moy_inf" - elif val >= table.barre_valid_ue: - note_class = "moy_ue_valid" - self.nb_ues_validables += 1 - if val < table.barre_warning_ue: - note_class = "moy_ue_warning" # notes très basses - self.nb_ues_warning += 1 - self.add_cell( - col_id, - ue.acronyme, - table.fmt_note(val), - group=f"col_ue_{ue.id}", - classes=["col_ue", "col_moy_ue", note_class], - ) - table.foot_title_row.cells[col_id].target_attrs[ - "title" - ] = f"""{ue.titre} S{ue.semestre_idx or '?'}""" - - def add_ue_modimpls( - self, row: tb.Row, ue: UniteEns, etud: Identite, is_capitalized: bool - ): - """Ajoute à row les moyennes des modules (ou ressources et SAÉs) dans l'UE""" - # pylint: disable=invalid-unary-operand-type - table = row.table - res = table.res - for modimpl in res.modimpls_in_ue(ue, etud.id, with_bonus=False): - if is_capitalized: - val = "-c-" - else: - modimpl_results = res.modimpls_results.get(modimpl.id) - if modimpl_results: # pas bonus - if res.is_apc: # BUT - moys_vers_ue = modimpl_results.etuds_moy_module.get(ue.id) - val = ( - moys_vers_ue.get(etud.id, "?") - if moys_vers_ue is not None - else "" - ) - else: # classique: Series indépendante de l'UE - val = modimpl_results.etuds_moy_module.get(etud.id, "?") - else: - val = "" - - col_id = f"moy_{modimpl.module.type_abbrv()}_{modimpl.id}_{ue.id}" - - val_fmt_html = val_fmt = table.fmt_note(val) - if modimpl.module.module_type == scu.ModuleType.MALUS: - if val and not isinstance(val, str) and not np.isnan(val): - if val >= 0: - val_fmt_html = f"""+{ - val_fmt - }""" - else: - # val_fmt_html = (scu.EMO_RED_TRIANGLE_DOWN + val_fmt) - val_fmt_html = f"""-{ - table.fmt_note(-val)}""" - - else: - val_fmt = val_fmt_html = "" # inscrit à ce malus, mais sans note - - cell = self.add_cell( - col_id, - modimpl.module.code, - val_fmt_html, - raw_content=val_fmt, - group=f"col_ue_{ue.id}_modules", - classes=[ - f"col_{modimpl.module.type_abbrv()}", - f"mod_ue_{ue.id}", - ], - ) - if modimpl.module.module_type == scu.ModuleType.MALUS: - # positionne la colonne à droite de l'UE - cell.group = f"col_ue_{ue.id}_malus" - table.insert_group(cell.group, after=f"col_ue_{ue.id}") - - table.foot_title_row.cells[col_id].target = url_for( - "notes.moduleimpl_status", - scodoc_dept=g.scodoc_dept, - moduleimpl_id=modimpl.id, - ) - - nom_resp = table.cache_nomcomplet.get(modimpl.responsable_id) - if nom_resp is None: - user = User.query.get(modimpl.responsable_id) - nom_resp = user.get_nomcomplet() if user else "" - table.cache_nomcomplet[modimpl.responsable_id] = nom_resp - table.foot_title_row.cells[col_id].target_attrs[ - "title" - ] = f"{modimpl.module.titre} ({nom_resp})" - table.modimpl_ue_ids.add((modimpl.id, ue.id)) - - def _recap_etud_groups_infos( - self, etudid: int, row: dict, titles: dict - ): # XXX non utilisé - """Table recap: ajoute à row les colonnes sur les groupes pour cet etud""" - # dec = self.get_etud_decision_sem(etudid) - # if dec: - # codes_nb[dec["code"]] += 1 - row_class = "" - etud_etat = self.get_etud_etat(etudid) - if etud_etat == DEM: - gr_name = "Dém." - row_class = "dem" - elif etud_etat == DEF: - gr_name = "Déf." - row_class = "def" - else: - # XXX probablement à revoir pour utiliser données cachées, - # via get_etud_groups_in_partition ou autre - group = sco_groups.get_etud_main_group(etudid, self.formsemestre.id) - gr_name = group["group_name"] or "" - row["group"] = gr_name - row["_group_class"] = "group" - if row_class: - row["_tr_class"] = " ".join([row.get("_tr_class", ""), row_class]) - titles["group"] = "Gr" - - def _recap_add_admissions(self, table: tb.Table): - """Ajoute les colonnes "admission" - Les colonnes ont la classe css "admission" - """ - fields = { - "bac": "Bac", - "specialite": "Spécialité", - "type_admission": "Type Adm.", - "classement": "Rg. Adm.", - } - first = True - for cid, title in fields.items(): - cell_head, cell_foot = table.add_title(cid, title) - cell_head.classes.append("admission") - cell_foot.classes.append("admission") - if first: - cell_head.classes.append("admission_first") - cell_foot.classes.append("admission_first") - first = False - - for row in table.rows: - etud = Identite.query.get(row.id) # TODO XXX - admission = etud.admission.first() - first = True - for cid, title in fields.items(): - cell = row.add_cell( - cid, - title, - getattr(admission, cid) or "", - "admission", - ) - if first: - cell.classes.append("admission_first") - first = False - - def recap_add_cursus(self, table: tb.Table): - """Ajoute colonne avec code cursus, eg 'S1 S2 S1'""" - table.insert_group("cursus", before="col_ues_validables") - cid = "code_cursus" - formation_code = self.formsemestre.formation.formation_code - for row in table.rows: - etud = Identite.query.get(row.id) # TODO XXX à optimiser: etud dans row - row.add_cell( - cid, - "Cursus", - " ".join( - [ - f"S{ins.formsemestre.semestre_id}" - for ins in reversed(etud.inscriptions()) - if ins.formsemestre.formation.formation_code == formation_code - ] - ), - "cursus", - ) - - def recap_add_partitions(self, table: tb.Table): - """Ajoute les colonnes indiquant les groupes - La table contient des rows avec la clé etudid. - - Les colonnes ont la classe css "partition" - """ - table.insert_group("partition", after="identite_court") - partitions, partitions_etud_groups = sco_groups.get_formsemestre_groups( - self.formsemestre.id - ) - first_partition = True - for partition in partitions: - cid = f"part_{partition['partition_id']}" - - partition_etud_groups = partitions_etud_groups[partition["partition_id"]] - for row in table.rows: - etudid = row.id - group = None # group (dict) de l'étudiant dans cette partition - # dans NotesTableCompat, à revoir - etud_etat = self.get_etud_etat(row.id) # row.id == etudid - tr_classes = [] - if etud_etat == scu.DEMISSION: - gr_name = "Dém." - tr_classes.append("dem") - elif etud_etat == DEF: - gr_name = "Déf." - tr_classes.append("def") - else: - group = partition_etud_groups.get(etudid) - gr_name = group["group_name"] if group else "" - if gr_name: - row.add_cell( - cid, - partition["partition_name"], - gr_name, - group="partition", - classes=[] if first_partition else ["partition_aux"], - # la classe "partition" est ajoutée par la Table car c'est le group - # la classe "partition_aux" est ajoutée à partir de la 2eme partition affichée - ) - first_partition = False - - # Rangs dans groupe - if ( - partition["bul_show_rank"] - and (group is not None) - and (group["id"] in self.moy_gen_rangs_by_group) - ): - rang = self.moy_gen_rangs_by_group[group["id"]][0] - rg_cid = cid + "_rg" # rang dans la partition - row.add_cell( - rg_cid, - f"Rg {partition['partition_name']}", - rang.get(etudid, ""), - group="partition", - classes=["partition_aux"], - ) - - def _recap_add_evaluations(self, table: tb.Table): - """Ajoute les colonnes avec les notes aux évaluations - rows est une liste de dict avec une clé "etudid" - Les colonnes ont la classe css "evaluation" - """ - # nouvelle ligne pour description évaluations: - row_descr_eval = tb.BottomRow( - table, - "evaluations", - left_title="Description évaluations", - left_title_col_ids=["prenom", "nom_short"], - category="bottom_infos", - classes=["bottom_info"], - ) - - first_eval = True - for modimpl in self.formsemestre.modimpls_sorted: - evals = self.modimpls_results[modimpl.id].get_evaluations_completes(modimpl) - eval_index = len(evals) - 1 - inscrits = {i.etudid for i in modimpl.inscriptions} - first_eval_of_mod = True - for e in evals: - col_id = f"eval_{e.id}" - title = f'{modimpl.module.code} {eval_index} {e.jour.isoformat() if e.jour else ""}' - col_classes = ["evaluation"] - if first_eval: - col_classes.append("first") - elif first_eval_of_mod: - col_classes.append("first_of_mod") - first_eval_of_mod = first_eval = False - eval_index -= 1 - notes_db = sco_evaluation_db.do_evaluation_get_all_notes( - e.evaluation_id - ) - for row in table.rows: - etudid = row.id - if etudid in inscrits: - if etudid in notes_db: - val = notes_db[etudid]["value"] - else: - # Note manquante mais prise en compte immédiate: affiche ATT - val = scu.NOTES_ATTENTE - content = table.fmt_note(val) - classes = col_classes + [ - { - "ABS": "abs", - "ATT": "att", - "EXC": "exc", - }.get(content, "") - ] - row.add_cell(col_id, title, content, "", classes=classes) - else: - row.add_cell( - col_id, - title, - "ni", - "", - classes=col_classes + ["non_inscrit"], - ) - - table.get_row_by_id("coef").row[col_id] = e.coefficient - table.get_row_by_id("min").row[col_id] = "0" - table.get_row_by_id("max").row[col_id] = table.fmt_note(e.note_max) - row_descr_eval.add_cell( - col_id, - None, - e.description or "", - target=url_for( - "notes.evaluation_listenotes", - scodoc_dept=g.scodoc_dept, - evaluation_id=e.id, - ), - ) diff --git a/app/scodoc/sco_cache.py b/app/scodoc/sco_cache.py index f83cc0eca..c197079dc 100644 --- a/app/scodoc/sco_cache.py +++ b/app/scodoc/sco_cache.py @@ -210,7 +210,7 @@ class SemInscriptionsCache(ScoDocCache): class TableRecapCache(ScoDocCache): - """Cache table recap (pour get_table_recap) + """Cache table recap (pour TableRecap) Clé: formsemestre_id Valeur: le html (
...
) """ @@ -220,7 +220,7 @@ class TableRecapCache(ScoDocCache): class TableRecapWithEvalsCache(ScoDocCache): - """Cache table recap (pour get_table_recap) + """Cache table recap (pour TableRecap) Clé: formsemestre_id Valeur: le html (
...
) """ diff --git a/app/scodoc/sco_recapcomplet.py b/app/scodoc/sco_recapcomplet.py index f9ffb565d..f1cc6c589 100644 --- a/app/scodoc/sco_recapcomplet.py +++ b/app/scodoc/sco_recapcomplet.py @@ -54,6 +54,7 @@ from app.scodoc import sco_formsemestre from app.scodoc import sco_formsemestre_status from app.scodoc import sco_permissions_check from app.scodoc import sco_preferences +from app.tables.recap import TableRecap def formsemestre_recapcomplet( @@ -421,11 +422,13 @@ def _gen_formsemestre_recapcomplet_html( selected_etudid=None, ) -> str: """Génère le html""" - table = res.get_table_recap( + table = TableRecap( + res, convert_values=True, include_evaluations=include_evaluations, mode_jury=mode_jury, ) + table.data["filename"] = filename table.select_row(selected_etudid) return f""" @@ -458,15 +461,20 @@ def gen_formsemestre_recapcomplet_excel( """ suffix = scu.CSV_SUFFIX if format == "csv" else scu.XLSX_SUFFIX filename += suffix - rows, footer_rows, titles, column_ids = res.get_table_recap( - convert_values=False, include_evaluations=include_evaluations - ) - tab = GenTable( - columns_ids=column_ids, - titles=titles, - rows=rows + footer_rows, + # XXX TODO A ADAPTER XXX !!! !!! + table = TableRecap( + res, + convert_values=False, + include_evaluations=include_evaluations, preferences=sco_preferences.SemPreferences(formsemestre_id=formsemestre.id), ) - return tab.gen(format=format), filename + # tab = GenTable( + # columns_ids=column_ids, + # titles=titles, + # rows=rows + footer_rows, + # preferences=sco_preferences.SemPreferences(formsemestre_id=formsemestre.id), + # ) + + return table.gen(format=format), filename diff --git a/app/scodoc/table_builder.py b/app/tables/table_builder.py similarity index 100% rename from app/scodoc/table_builder.py rename to app/tables/table_builder.py