forked from ScoDoc/ScoDoc
850 lines
32 KiB
Python
850 lines
32 KiB
Python
##############################################################################
|
|
# ScoDoc
|
|
# Copyright (c) 1999 - 2024 Emmanuel Viennet. All rights reserved.
|
|
# See LICENSE
|
|
##############################################################################
|
|
|
|
"""Table récapitulatif des résultats d'un semestre
|
|
"""
|
|
from functools import partial
|
|
|
|
from flask import g, url_for
|
|
import numpy as np
|
|
|
|
from app import db
|
|
from app.auth.models import User
|
|
from app.comp.res_common import ResultatsSemestre
|
|
from app.models import Identite, Evaluation, FormSemestre, UniteEns
|
|
from app.scodoc.codes_cursus import UE_SPORT, DEF
|
|
from app.scodoc import sco_evaluation_db
|
|
from app.scodoc import sco_groups
|
|
from app.scodoc import sco_utils as scu
|
|
from app.tables import table_builder as tb
|
|
|
|
|
|
class TableRecap(tb.Table):
|
|
"""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_<ue_id>, ..., les moyennes d'UE
|
|
moy_res_<modimpl_id>_<ue_id>, ... les moyennes de ressources dans l'UE
|
|
moy_sae_<modimpl_id>_<ue_id>, ... les moyennes de SAE dans l'UE
|
|
|
|
On ajoute aussi des classes:
|
|
- 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_<ue_id>
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
res: ResultatsSemestre,
|
|
convert_values=False,
|
|
include_evaluations=False,
|
|
mode_jury=False,
|
|
row_class=None,
|
|
finalize=True,
|
|
read_only: bool = True,
|
|
**kwargs,
|
|
):
|
|
self.rows: list["RowRecap"] = [] # juste pour que VSCode nous aide sur .rows
|
|
super().__init__(row_class=row_class or RowRecap, **kwargs)
|
|
self.res = res
|
|
self.include_evaluations = include_evaluations
|
|
self.mode_jury = mode_jury
|
|
self.read_only = read_only # utilisé seulement dans sous-classes
|
|
cursus = res.formsemestre.formation.get_cursus()
|
|
self.barre_moy = cursus.BARRE_MOY - scu.NOTES_TOLERANCE
|
|
self.barre_valid_ue = cursus.NOTES_BARRE_VALID_UE
|
|
self.barre_warning_ue = cursus.BARRE_UE_DISPLAY_WARNING
|
|
self.cache_nomcomplet = {} # cache uid : nomcomplet
|
|
if convert_values:
|
|
self.fmt_note = scu.fmt_note
|
|
else:
|
|
self.fmt_note = partial(scu.fmt_note, keep_numeric=True)
|
|
# couples (modimpl, ue) effectivement présents dans la table:
|
|
self.modimpl_ue_ids = set()
|
|
|
|
ues = res.formsemestre.get_ues(with_sport=True) # avec bonus
|
|
ues_sans_bonus = [ue for ue in ues if ue.type != UE_SPORT]
|
|
|
|
if res.formsemestre.etuds_inscriptions: # table non vide
|
|
# Fixe l'ordre des groupes de colonnes communs:
|
|
groups = [
|
|
"etud_codes",
|
|
"rang",
|
|
"identite_court",
|
|
"identite_detail",
|
|
"partition",
|
|
"cursus",
|
|
"col_ues_validables",
|
|
]
|
|
if not res.formsemestre.block_moyenne_generale:
|
|
groups.append("col_moy_gen")
|
|
groups.append("abs")
|
|
self.set_groups(groups)
|
|
|
|
for etudid in res.formsemestre.etuds_inscriptions:
|
|
etud = Identite.get_etud(etudid)
|
|
row = self.row_class(self, etud)
|
|
self.add_row(row)
|
|
row.add_etud_cols()
|
|
row.add_moyennes_cols(ues_sans_bonus)
|
|
row.add_abs()
|
|
|
|
self.add_partitions()
|
|
self.add_cursus()
|
|
self.add_admissions()
|
|
|
|
# Tri par rang croissant
|
|
if not res.formsemestre.block_moyenne_generale:
|
|
self.sort_rows(key=lambda row: row.rang_order or 10000000)
|
|
else:
|
|
self.sort_rows(key=lambda row: row.nb_ues_validables or 0, reverse=True)
|
|
|
|
# Lignes footer (min, max, ects, apo, ...)
|
|
self.add_bottom_rows(ues_sans_bonus)
|
|
|
|
# Evaluations:
|
|
if include_evaluations:
|
|
self.add_evaluations()
|
|
|
|
if finalize:
|
|
self.finalize()
|
|
|
|
def finalize(self):
|
|
"""Termine la table: ajoute ligne avec les types,
|
|
et ajoute classe sur les colonnes vides"""
|
|
self.mark_empty_cols()
|
|
self.add_type_row()
|
|
|
|
def mark_empty_cols(self):
|
|
"""Ajoute classe "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].add("col_empty")
|
|
|
|
def add_type_row(self):
|
|
"""Ligne avec la classe de chaque colonne recap."""
|
|
# récupère le type à partir du groupe (enlève le préfixe "col_" si présent)
|
|
row_type = tb.BottomRow(
|
|
self,
|
|
"type_col",
|
|
left_title="Type col.",
|
|
left_title_col_ids=["prenom", "nom_short"],
|
|
category="bottom_infos",
|
|
classes=["bottom_info", "type_col"],
|
|
)
|
|
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", "apo"],
|
|
)
|
|
|
|
# --- 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)
|
|
|
|
# la colonne où placer les valeurs agrégats
|
|
col_id = "moy_gen" if "moy_gen" in self.column_ids else "code_cursus"
|
|
col_group = "col_moy_gen" if "moy_gen" in self.column_ids else "cursus"
|
|
row_ects.add_cell(
|
|
col_id,
|
|
None,
|
|
sum([ue.ects or 0 for ue in ues if ue.type != UE_SPORT]),
|
|
group=col_group,
|
|
)
|
|
# --- MIN, MAX, MOY, APO
|
|
row_min.add_cell(
|
|
col_id,
|
|
None,
|
|
self.fmt_note(res.etud_moy_gen.min()),
|
|
group=col_group,
|
|
)
|
|
row_max.add_cell(
|
|
col_id,
|
|
None,
|
|
self.fmt_note(res.etud_moy_gen.max()),
|
|
group=col_group,
|
|
)
|
|
row_moy.add_cell(
|
|
col_id,
|
|
None,
|
|
self.fmt_note(res.etud_moy_gen.mean()),
|
|
group=col_group,
|
|
)
|
|
|
|
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()),
|
|
classes=["col_ue", "col_moy_ue"],
|
|
)
|
|
row_max.add_cell(
|
|
col_id,
|
|
None,
|
|
self.fmt_note(res.etud_moy_ue[ue.id].max()),
|
|
classes=["col_ue", "col_moy_ue"],
|
|
)
|
|
row_moy.add_cell(
|
|
col_id,
|
|
None,
|
|
self.fmt_note(res.etud_moy_ue[ue.id].mean()),
|
|
classes=["col_ue", "col_moy_ue"],
|
|
)
|
|
row_apo.add_cell(
|
|
col_id,
|
|
None,
|
|
ue.code_apogee or "",
|
|
classes=["col_ue", "col_moy_ue"],
|
|
)
|
|
|
|
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, "")
|
|
row_max.add_cell(col_id, None, "")
|
|
moy = ""
|
|
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 (moy == "" or np.isnan(moy)) else ""],
|
|
)
|
|
row_apo.add_cell(col_id, None, modimpl.module.code_apogee or "")
|
|
|
|
def add_partitions(self):
|
|
"""Ajoute les colonnes indiquant les groupes
|
|
pour tous les étudiants de la table.
|
|
La table contient des rows avec la clé etudid.
|
|
Les colonnes ont la classe css "partition".
|
|
"""
|
|
self.group_titles["partition"] = "Partitions"
|
|
partitions, partitions_etud_groups = sco_groups.get_formsemestre_groups(
|
|
self.res.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 self.rows:
|
|
etudid = row.id
|
|
group = None # group (dict) de l'étudiant dans cette partition
|
|
# dans NotesTableCompat, à revoir
|
|
etud_etat = self.res.get_etud_etat(row.id) # row.id == etudid
|
|
if etud_etat == scu.DEMISSION:
|
|
gr_name = "Dém."
|
|
row.add_class("dem")
|
|
elif etud_etat == DEF:
|
|
gr_name = "Déf."
|
|
row.add_class("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.res.moy_gen_rangs_by_group)
|
|
):
|
|
rang = self.res.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", "partition_rangs"],
|
|
)
|
|
|
|
def add_evaluations(self):
|
|
"""Ajoute les colonnes avec les notes aux évaluations
|
|
pour tous les étudiants de la table.
|
|
Les colonnes ont la classe css "evaluation"
|
|
"""
|
|
self.group_titles["eval"] = "Évaluations"
|
|
# nouvelle ligne pour description évaluations:
|
|
row_descr_eval = tb.BottomRow(
|
|
self,
|
|
"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.res.formsemestre.modimpls_sorted:
|
|
evals = self.res.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.date_debut.strftime(scu.DATE_FMT) if e.date_debut else ""
|
|
}"""
|
|
col_classes = []
|
|
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 self.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
|
|
if e.evaluation_type != Evaluation.EVALUATION_BONUS
|
|
else ""
|
|
)
|
|
content = self.fmt_note(val, note_max=e.note_max)
|
|
if e.evaluation_type != Evaluation.EVALUATION_BONUS:
|
|
classes = col_classes + [
|
|
{
|
|
"ABS": "abs",
|
|
"ATT": "att",
|
|
"EXC": "exc",
|
|
}.get(content, "")
|
|
]
|
|
else:
|
|
classes = col_classes + ["col_eval_bonus"]
|
|
row.add_cell(
|
|
col_id, title, content, group="eval", classes=classes
|
|
)
|
|
else:
|
|
row.add_cell(
|
|
col_id,
|
|
title,
|
|
"ni",
|
|
group="eval",
|
|
classes=col_classes + ["non_inscrit"],
|
|
)
|
|
|
|
row_coef = self.get_row_by_id("coef")
|
|
row_coef.add_cell(
|
|
col_id,
|
|
None,
|
|
self.fmt_note(e.coefficient),
|
|
group="eval",
|
|
)
|
|
row_min = self.get_row_by_id("min")
|
|
row_min.add_cell(
|
|
col_id,
|
|
None,
|
|
0,
|
|
group="eval",
|
|
)
|
|
row_max = self.get_row_by_id("max")
|
|
row_max.add_cell(
|
|
col_id,
|
|
None,
|
|
self.fmt_note(e.note_max),
|
|
group="eval",
|
|
)
|
|
row_descr_eval.add_cell(
|
|
col_id,
|
|
None,
|
|
e.description,
|
|
target=url_for(
|
|
"notes.evaluation_listenotes",
|
|
scodoc_dept=g.scodoc_dept,
|
|
evaluation_id=e.id,
|
|
),
|
|
target_attrs={"class": "stdlink"},
|
|
)
|
|
|
|
def add_admissions(self):
|
|
"""Ajoute les colonnes "admission" pour tous les étudiants de la table
|
|
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 = self.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 self.rows:
|
|
etud = row.etud
|
|
admission = etud.admission
|
|
if admission:
|
|
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 add_cursus(self):
|
|
"""Ajoute colonne avec code cursus, eg 'S1 S2 S1'
|
|
pour tous les étudiants de la table"""
|
|
cid = "code_cursus"
|
|
formation_code = self.res.formsemestre.formation.formation_code
|
|
for row in self.rows:
|
|
row.add_cell(
|
|
cid,
|
|
"Cursus",
|
|
" ".join(
|
|
[
|
|
f"S{ins.formsemestre.semestre_id}"
|
|
for ins in reversed(row.etud.inscriptions())
|
|
if ins.formsemestre.formation.formation_code == formation_code
|
|
]
|
|
),
|
|
group="cursus",
|
|
)
|
|
|
|
def html(self, extra_classes: list[str] = None) -> str:
|
|
"""HTML: pour les tables recap, un div au contenu variable"""
|
|
return f"""
|
|
<div class="table_recap">
|
|
{
|
|
'<div class="message">aucun étudiant !</div>'
|
|
if self.is_empty()
|
|
else super().html(
|
|
extra_classes=[
|
|
"table_recap",
|
|
"apc" if self.res.formsemestre.formation.is_apc() else "classic",
|
|
"jury" if self.mode_jury else "",
|
|
"with_evaluations" if self.include_evaluations else "",
|
|
])
|
|
}
|
|
</div>
|
|
"""
|
|
|
|
|
|
class RowRecap(tb.Row):
|
|
"Ligne de la table recap, pour un étudiant"
|
|
|
|
def __init__(self, table: TableRecap, etud: Identite, *args, **kwargs):
|
|
super().__init__(table, etud.id, *args, **kwargs)
|
|
self.etud = etud
|
|
self.rang_order = None
|
|
"valeur pour tri rangs"
|
|
self.nb_ues_validables = None
|
|
self.nb_ues_warning = None
|
|
self.nb_ues_etud_parcours = None
|
|
|
|
def add_etud_cols(self):
|
|
"""Ajoute colonnes étudiant: codes, noms"""
|
|
res = self.table.res
|
|
etud = self.etud
|
|
self.table.group_titles.update(
|
|
{
|
|
"etud_codes": "Codes",
|
|
"identite_detail": "",
|
|
"identite_court": "",
|
|
"rang": "",
|
|
}
|
|
)
|
|
# --- 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]
|
|
self.add_cell(
|
|
"rang",
|
|
"Rg",
|
|
res.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,
|
|
},
|
|
no_excel=True,
|
|
target=url_bulletin,
|
|
target_attrs={"class": "etudinfo", "id": str(etud.id)},
|
|
)
|
|
|
|
def add_abs(self):
|
|
"Ajoute les colonnes absences"
|
|
# Absences (nb d'abs non just. dans ce semestre)
|
|
_, nbabsjust, nbabs = self.table.res.formsemestre.get_abs_count(self.etud.id)
|
|
self.add_cell("nbabs", "Abs", f"{nbabs:1.0f}", "abs", raw_content=nbabs)
|
|
self.add_cell(
|
|
"nbabsjust",
|
|
"Just.",
|
|
f"{nbabsjust:1.0f}",
|
|
"abs",
|
|
raw_content=nbabsjust,
|
|
)
|
|
|
|
def add_moyennes_cols(
|
|
self,
|
|
ues_sans_bonus: list[UniteEns],
|
|
):
|
|
"""Ajoute cols moy_gen moy_ue et tous les modules..."""
|
|
etud = self.etud
|
|
table: TableRecap = self.table
|
|
res = table.res
|
|
# --- Si DEM ou DEF, ne montre aucun résultat d'UE ni moy. gen.
|
|
if res.get_etud_etat(etud.id) != scu.INSCRIT:
|
|
return
|
|
# --- 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
|
|
self.add_cell(
|
|
"moy_gen",
|
|
"Moy",
|
|
table.fmt_note(moy_gen),
|
|
group="col_moy_gen",
|
|
classes=[note_class],
|
|
)
|
|
# Ajoute bulle sur titre du pied de table:
|
|
cell = table.foot_title_row.cells.get("moy_gen")
|
|
if cell:
|
|
cell.target_attrs["title"] = (
|
|
"Moyenne générale indicative"
|
|
if res.is_apc
|
|
else "Moyenne générale du semestre"
|
|
)
|
|
self.add_ues_cols(ues_sans_bonus)
|
|
|
|
def add_ues_cols(self, ues_sans_bonus: list[UniteEns]):
|
|
"""Ajout des colonnes concernant les UEs
|
|
- pour chaque UE :
|
|
- sa moyenne (et son code en mode jury)
|
|
- les moyennes de modules dans l'UE
|
|
- la colonne UEs avec le nombre d'UEs validables
|
|
"""
|
|
etudid = self.etud.id
|
|
table: TableRecap = self.table
|
|
res = table.res
|
|
self.nb_ues_validables, self.nb_ues_warning = 0, 0
|
|
for ue in ues_sans_bonus:
|
|
ue_status = res.get_etud_ue_status(etudid, ue.id)
|
|
if ue_status is not None:
|
|
self.add_ue_cols(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][etudid] or ""
|
|
val_fmt = val_fmt_html = table.fmt_note(val)
|
|
if val:
|
|
val_fmt_html = f"""<span class="green-arrow-up"></span><span class="sp2l">{
|
|
val_fmt
|
|
}</span>"""
|
|
self.add_cell(
|
|
f"bonus_ue_{ue.id}",
|
|
f"Bonus {ue.acronyme}",
|
|
val_fmt_html,
|
|
raw_content=val_fmt,
|
|
group=f"col_ue_{ue.id}",
|
|
column_classes={"col_ue_bonus", "col_res"},
|
|
)
|
|
# Les moyennes des modules (ou ressources et SAÉs) dans cette UE
|
|
self.add_ue_modimpls_cols(ue, ue_status["is_capitalized"])
|
|
|
|
self.nb_ues_etud_parcours = len(res.etud_parcours_ues_ids(etudid))
|
|
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
|
|
cell_class = ""
|
|
if self.nb_ues_warning:
|
|
cell_class = "moy_ue_warning"
|
|
elif self.nb_ues_validables < self.nb_ues_etud_parcours: # len(ues_sans_bonus):
|
|
cell_class = "moy_inf"
|
|
self.add_cell(
|
|
"ues_validables",
|
|
"UEs",
|
|
ue_valid_txt_html,
|
|
group="col_ues_validables",
|
|
classes=[cell_class],
|
|
column_classes={"col_ue"},
|
|
raw_content=ue_valid_txt,
|
|
data={"order": self.nb_ues_validables}, # tri
|
|
)
|
|
|
|
def add_ue_cols(self, ue: UniteEns, ue_status: dict, col_group: str = None):
|
|
"Ajoute résultat d'une UE au row (colonne col_ue)"
|
|
# sous-classé par JuryRow pour ajouter les codes
|
|
table: TableRecap = self.table
|
|
formsemestre: FormSemestre = table.res.formsemestre
|
|
table.group_titles["col_ue"] = (
|
|
f"UEs du S{formsemestre.semestre_id} {formsemestre.annee_scolaire()}"
|
|
)
|
|
col_id = f"moy_ue_{ue.id}"
|
|
val = (
|
|
ue_status["moy"]
|
|
if (self.etud.id, ue.id) not in table.res.dispense_ues
|
|
else "="
|
|
)
|
|
cell_classes = []
|
|
# Cette UE est-elle dans le parcours de l'étudiant (dans ce semestre) ?
|
|
ue_hors_parcours = ue.id not in self.table.res.etud_parcours_ues_ids(
|
|
self.etud.id
|
|
)
|
|
|
|
target_attrs = {} # pour décorer la cellule (bulle)
|
|
if isinstance(val, float):
|
|
if val < table.barre_moy:
|
|
cell_classes = ["moy_inf"]
|
|
elif val >= table.barre_valid_ue:
|
|
cell_classes = ["moy_ue_valid"]
|
|
if not ue_hors_parcours:
|
|
self.nb_ues_validables += 1
|
|
if val < table.barre_warning_ue:
|
|
cell_classes = ["moy_ue_warning"] # notes très basses
|
|
self.nb_ues_warning += 1
|
|
if ue_status["is_capitalized"]:
|
|
cell_classes.append("ue_capitalized")
|
|
if ue_hors_parcours:
|
|
cell_classes.append("ue_hors_parcours")
|
|
target_attrs["title"] = "UE hors du parcours actuel de l'étudiant"
|
|
self.add_cell(
|
|
col_id,
|
|
ue.acronyme,
|
|
table.fmt_note(val),
|
|
group=col_group or f"col_ue_{ue.id}",
|
|
classes=cell_classes,
|
|
column_classes={f"col_ue_{ue.id}", "col_moy_ue", "col_ue"},
|
|
target_attrs=target_attrs,
|
|
)
|
|
table.foot_title_row.cells[col_id].target_attrs[
|
|
"title"
|
|
] = f"""{ue.titre or ue.acronyme} S{ue.semestre_idx or '?'}"""
|
|
|
|
def add_ue_modimpls_cols(self, ue: UniteEns, is_capitalized: bool):
|
|
"""Ajoute à row les moyennes des modules (ou ressources et SAÉs) dans l'UE"""
|
|
# pylint: disable=invalid-unary-operand-type
|
|
etud = self.etud
|
|
table = self.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épendantes 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"""<span class="sp2l">-{
|
|
val_fmt
|
|
}</span>"""
|
|
else:
|
|
val_fmt_html = f"""<span class="sp2l malus_negatif">+{
|
|
table.fmt_note(-val)}</span>"""
|
|
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",
|
|
column_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 = db.session.get(User, 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))
|