############################################################################## # ScoDoc # Copyright (c) 1999 - 2024 Emmanuel Viennet. All rights reserved. # See LICENSE ############################################################################## """Liste simple d'étudiants""" import datetime from flask import g, url_for from app import log from app.models import FormSemestre, Identite, Justificatif from app.tables import table_builder as tb from app.scodoc import sco_preferences from app.scodoc import sco_utils as scu import app.scodoc.sco_assiduites as scass from app.scodoc.sco_exceptions import ScoValueError class TableAssi(tb.Table): """Table listant les statistiques d'assiduité des étudiants L'id de la ligne est etudid, et le row stocke etud. On considère les assiduités entre les dates indiquées. Si formsemestre_modimpls est spécifié, restreint aux assiduités associées à des moduleimpls de ce formsemestre. Si convert_values, transforme les nombre en chaines ("12.34"), pour le html. """ def __init__( self, etuds: list[Identite] = None, dates: tuple[str, str] = None, formsemestre: FormSemestre = None, formsemestre_modimpls: FormSemestre | None = None, convert_values=False, **kwargs, ): self.rows: list["RowAssi"] = [] # juste pour que VSCode nous aide sur .rows classes = ["gt_table"] try: self.dates = [ datetime.datetime.fromisoformat(str(dates[0]) + "T00:00"), datetime.datetime.fromisoformat(str(dates[1]) + "T00:00"), ] except ValueError as exc: raise ScoValueError("invalid dates") from exc self.formsemestre = formsemestre self.formsemestre_modimpls = formsemestre_modimpls if convert_values: self.fmt_num = lambda x: f"{x:2.3g}" else: self.fmt_num = lambda x: x super().__init__( row_class=RowAssi, classes=classes, **kwargs, with_foot_titles=False, ) self.add_etuds(etuds) def add_etuds(self, etuds: list[Identite]): "Ajoute des étudiants à la table" for etud in etuds: row = self.row_class(self, etud) row.add_etud_cols() self.add_row(row) class RowAssi(tb.Row): "Ligne de la table assiduité" # pour le moment très simple, extensible (codes, liens bulletins, ...) def __init__(self, table: TableAssi, etud: Identite, *args, **kwargs): # Etat de l'inscription au formsemestre if "classes" not in kwargs: kwargs["classes"] = [] try: inscription = table.formsemestre.etuds_inscriptions[etud.id] if inscription.etat == scu.DEMISSION: kwargs["classes"].append("etuddem") except KeyError: log(f"RowAssi: etudid {etud.id} non inscrit à {table.formsemestre.id}") kwargs["classes"].append("non_inscrit") # ne devrait pas arriver ! super().__init__(table, etud.id, *args, **kwargs) self.etud = etud self.dates = table.dates def add_etud_cols(self): """Ajoute les colonnes""" etud = self.etud fmt_num = self.table.fmt_num self.table.group_titles.update( { "etud_codes": "Codes", "identite_detail": "", "identite_court": "", } ) bilan_etud = url_for( "assiduites.bilan_etud", scodoc_dept=g.scodoc_dept, etudid=etud.id ) self.add_cell( "etudid", "etudid", etud.etudid, "etudinfo", only_excel=True, ) self.add_cell( "code_nip", "code_nip", etud.code_nip, "etudinfo", only_excel=True, ) self.add_cell( "nom_disp", "Nom", etud.nom_disp(), "etudinfo", attrs={"id": str(etud.id)}, data={"order": etud.sort_key}, target=bilan_etud, target_attrs={"class": "discretelink"}, ) self.add_cell( "prenom", "Prénom", etud.prenom_str, "etudinfo", attrs={"id": str(etud.id)}, data={"order": etud.sort_key}, target=bilan_etud, target_attrs={"class": "discretelink"}, ) stats = self._get_etud_stats(etud) for key, value in stats.items(): if key == "present" and sco_preferences.get_preference( "non_present", dept_id=g.scodoc_dept_id, formsemestre_id=self.table.formsemestre.id, ): continue self.add_cell(key, value[0], fmt_num(value[1] - value[2]), "assi_stats") if key != "present": self.add_cell( key + "_justi", value[0] + " Justifiées", fmt_num(value[2]), "assi_stats", ) compte_justificatifs = scass.filter_by_date( etud.justificatifs, Justificatif, self.dates[0], self.dates[1] ) compte_justificatifs_att = compte_justificatifs.filter(Justificatif.etat == 2) self.add_cell( "justificatifs_att", "Justificatifs en Attente", fmt_num(compte_justificatifs_att.count()), ) self.add_cell( "justificatifs", "Justificatifs", fmt_num(compte_justificatifs.count()) ) def _get_etud_stats(self, etud: Identite) -> dict[str, list[str, float, float]]: """ Renvoie le comptage (dans la métrique du département) des différents états d'assiduité d'un étudiant. Considère les dates. Returns : { "" : [, , ] } """ # Préparation du retour retour: dict[str, tuple[str, float, float]] = { "absent": ["Absences", 0.0, 0.0], "retard": ["Retards", 0.0, 0.0], "present": ["Présences", 0.0, 0.0], } # Récupération de la métrique du département assi_metric = scu.translate_assiduites_metric( sco_preferences.get_preference("assi_metrique", dept_id=g.scodoc_dept_id), ) compte_etat: dict[str, dict] = scass.get_assiduites_stats( assiduites=etud.assiduites, metric=assi_metric, filtered={ "date_debut": self.dates[0], "date_fin": self.dates[1], "etat": "absent,present,retard", # pour tout compter d'un coup "formsemestre_modimpls": self.table.formsemestre_modimpls, "split": 1, # afin d'avoir la division des stats en état, etatjust, etatnonjust }, ) # Pour chaque état on met à jour les valeurs de retour for etat, valeur in retour.items(): valeur[1] = compte_etat[etat][assi_metric] if etat != "present": valeur[2] = compte_etat[etat]["justifie"][assi_metric] return retour def etuds_sorted_from_ids(etudids) -> list[Identite]: "Liste triée d'etuds à partir d'une collections d'etudids" etuds = [Identite.get_etud(etudid) for etudid in etudids] return sorted(etuds, key=lambda etud: etud.sort_key)