ScoDoc-Lille/app/tables/visu_assiduites.py

223 lines
7.4 KiB
Python

##############################################################################
# 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 :
{
"<etat>" : [<Etat version lisible>, <nb total etat>, <nb just etat>]
}
"""
# 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)