1
0
forked from ScoDoc/ScoDoc
ScoDoc/app/tables/bilan_ues.py

198 lines
7.0 KiB
Python

##############################################################################
# ScoDoc
# Copyright (c) 1999 - 2024 Emmanuel Viennet. All rights reserved.
# See LICENSE
##############################################################################
"""Table bilan UEs des étudiants d'un formsemestre
Voir https://git.scodoc.org/ScoDoc/ScoDoc/issues/989
"""
from functools import partial
from collections import defaultdict
from app.models import (
Formation,
FormSemestre,
Identite,
ScolarFormSemestreValidation,
UniteEns,
)
from app.scodoc import sco_formsemestre_validation
from app.scodoc.codes_cursus import BUT_CODES_ORDER
from app.scodoc import sco_utils as scu
from app.tables.list_etuds import TableEtud, RowEtudWithInfos
def _acronym_ues_same_key(ues: set[UniteEns]) -> str:
"""Les UEs de même code pouvant (rarement, espérons) avoir des acronymes différents,
les regroupe dans une chaîne.
"""
# Note: on peut former un ensemble d'UE, voir
# https://stackoverflow.com/questions/8179068/sqlalchemy-id-equality-vs-reference-equality
acronymes = sorted({ue.acronyme for ue in ues})
return acronymes[0] + (
" (" + ", ".join(acronymes[1:]) + ")" if len(acronymes) > 1 else ""
)
def _best_validation(
validations: list[ScolarFormSemestreValidation],
) -> ScolarFormSemestreValidation | None:
"""Retourne le code jury de la meilleure validation
(au sens du code jury puis de la note moyenne)
"""
if not validations:
return None
best_validation = max(
validations, key=lambda v: (BUT_CODES_ORDER.get(v.code, -1), v.moy_ue or 0.0)
)
return best_validation
def get_etud_ue_validations_by_ue_key(
etud: Identite, formation: Formation
) -> dict[str | tuple[int, int], list[ScolarFormSemestreValidation]]:
"""Retourne les validations d'UEs pour un étudiant dans une formation.
Considère toutes les formations de même code que celle indiquée, puis regroupe les validations
par clé d'UE:
- en formations classiques, `ue.ue_code`
- en APC tuple `(ue.semestre_idx, ue.niveau_id)`
"""
validations = sco_formsemestre_validation.get_etud_ue_validations(etud, formation)
etud_ue_validations_by_ue_key = defaultdict(list)
if formation.is_apc():
for v in validations:
etud_ue_validations_by_ue_key[
(v.ue.semestre_idx, v.ue.niveau_competence_id)
].append(v)
else:
for v in validations:
etud_ue_validations_by_ue_key[v.ue.ue_code].append(v)
return etud_ue_validations_by_ue_key
class TableBilanUEs(TableEtud):
"""Table listant des étudiants avec bilan UEs"""
def __init__(
self,
formsemestre: FormSemestre,
convert_values=True,
classes: list[str] = None,
row_class=None,
with_foot_titles=False,
**kwargs,
):
self.formsemestre = formsemestre
etuds = formsemestre.etuds
if convert_values:
self.fmt_note = scu.fmt_note
else:
self.fmt_note = partial(scu.fmt_note, keep_numeric=True)
cursus = formsemestre.formation.get_cursus()
self.barre_moy = cursus.BARRE_MOY - scu.NOTES_TOLERANCE
self.is_apc = formsemestre.formation.is_apc()
self.validations_by_ue_key_by_etudid: dict[
int, dict[str | tuple[int, int] : list[ScolarFormSemestreValidation]]
] = {}
"validations d'UEs par étudiant, indicées par clé d'UE (code ou sem/niv. de comp.)"
self.ues_by_key: defaultdict[str | tuple[int, int] : set[UniteEns]] = (
defaultdict(set)
)
"ensemble des UEs associées à chaque clé"
for etud in etuds:
self.validations_by_ue_key_by_etudid[etud.id] = (
get_etud_ue_validations_by_ue_key(etud, formsemestre.formation)
)
# Le titre de chaque clé d'UE
for key, validations in self.validations_by_ue_key_by_etudid[
etud.id
].items():
self.ues_by_key[key].update({v.ue for v in validations})
# Titres des UEs
self.ue_titles = {
key: _acronym_ues_same_key(ues) for key, ues in self.ues_by_key.items()
}
# prend une UE pour chaque clé pour déterminer l'ordre des UEs
ue_by_key = {key: next(iter(ues)) for key, ues in self.ues_by_key.items()}
self.sorted_ues_keys = sorted(
self.ue_titles.keys(),
key=lambda k: (
ue_by_key[k].guess_semestre_idx(),
ue_by_key[k].numero,
),
)
"clés d'UEs triées par semestre/numero"
super().__init__(
etuds=etuds,
classes=classes or ["gt_table", "gt_left", "with-highlight", "table_recap"],
row_class=row_class or RowEtudWithUEs,
with_foot_titles=with_foot_titles,
**kwargs,
)
def add_etuds(self, etuds: list[Identite]):
"Ajoute des étudiants à la table"
for etud in etuds:
row = self.row_class(
self,
etud,
self.formsemestre,
self.formsemestre.etuds_inscriptions.get(etud.id),
)
row.add_etud_cols()
self.add_row(row)
class RowEtudWithUEs(RowEtudWithInfos):
"""Ligne de la table d'étudiants avec colonnes UEs"""
with_boursier = False # inutile dans cette table
with_departement = False
with_formsemestre = False
with_ine = True
def add_etud_cols(self):
"""Ajoute colonnes pour cet étudiant"""
super().add_etud_cols()
# Ajoute les colonnes d'UEs
for key in self.table.sorted_ues_keys:
self.add_etud_ue_cols(key)
def add_etud_ue_cols(self, key):
"colonnes pour une clé d'UE pour l'étudiant"
validations = self.table.validations_by_ue_key_by_etudid[self.etud.id].get(
key, []
)
if not validations:
return
best_validation = _best_validation(validations)
moy_ue = best_validation.moy_ue
ue_semestre_idx = best_validation.ue.guess_semestre_idx()
note_class = ""
if moy_ue is None:
moy_ue = scu.NO_NOTE_STR
if isinstance(moy_ue, float) and moy_ue < self.table.barre_moy:
note_class = "moy_ue_warning" # en rouge
self.add_cell(
str(key) + "_note",
self.table.ue_titles[key],
self.table.fmt_note(moy_ue),
data={"order": moy_ue},
raw_content=moy_ue,
classes=[note_class],
column_classes={"note_ue"},
group=f"col_s{ue_semestre_idx}",
)
if self.table.is_apc:
class_cursus = f"cursus_BUT{(ue_semestre_idx+1)//2}"
else:
class_cursus = ""
self.add_cell(
str(key) + "_code",
self.table.ue_titles[key],
best_validation.code,
column_classes={"code_ue", class_cursus},
group=f"col_s{ue_semestre_idx}",
)