2024-09-14 16:42:12 +02:00
|
|
|
##############################################################################
|
|
|
|
# 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
|
2024-09-23 14:28:32 +02:00
|
|
|
ue_semestre_idx = best_validation.ue.guess_semestre_idx()
|
2024-09-14 16:42:12 +02:00
|
|
|
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"},
|
2024-09-23 14:28:32 +02:00
|
|
|
group=f"col_s{ue_semestre_idx}",
|
2024-09-14 16:42:12 +02:00
|
|
|
)
|
|
|
|
if self.table.is_apc:
|
2024-09-23 14:28:32 +02:00
|
|
|
class_cursus = f"cursus_BUT{(ue_semestre_idx+1)//2}"
|
2024-09-14 16:42:12 +02:00
|
|
|
else:
|
|
|
|
class_cursus = ""
|
|
|
|
self.add_cell(
|
|
|
|
str(key) + "_code",
|
|
|
|
self.table.ue_titles[key],
|
|
|
|
best_validation.code,
|
|
|
|
column_classes={"code_ue", class_cursus},
|
2024-09-23 14:28:32 +02:00
|
|
|
group=f"col_s{ue_semestre_idx}",
|
2024-09-14 16:42:12 +02:00
|
|
|
)
|