Tableau recap: bonus et malus

This commit is contained in:
Emmanuel Viennet 2022-04-03 16:20:16 +02:00
parent 0d726aa428
commit 795de44c0c
5 changed files with 143 additions and 36 deletions

View File

@ -426,47 +426,53 @@ class ResultatsSemestre(ResultatsCache):
NO_NOTE = "-" # contenu des cellules sans notes
rows = []
# column_id : title
titles = {
"rang": "Rg",
# ordre des colonnes de gauche à droite:
"_civilite_str_col_order": 2,
"_nom_disp_col_order": 3,
"_prenom_col_order": 4,
"_nom_short_col_order": 5,
"_rang_col_order": 6,
# les colonnes des groupes sont à la position 10
"_ues_validables_col_order": 20,
}
titles = {}
# les titres en footer: les mêmes, mais avec des bulles et liens:
titles_bot = {}
def add_cell(
row: dict, col_id: str, title: str, content: str, classes: str = ""
row: dict,
col_id: str,
title: str,
content: str,
classes: str = "",
idx: int = 100,
):
"Add a row to our table. classes is a list of css class names"
row[col_id] = content
if classes:
row[f"_{col_id}_class"] = classes
row[f"_{col_id}_class"] = classes + f" c{idx}"
if not col_id in titles:
titles[col_id] = title
titles[f"_{col_id}_col_order"] = idx
if classes:
titles[f"_{col_id}_class"] = classes
return idx + 1
etuds_inscriptions = self.formsemestre.etuds_inscriptions
ues = self.formsemestre.query_ues(with_sport=True) # avec bonus
ues_sans_bonus = [ue for ue in ues if ue.type != UE_SPORT]
modimpl_ids = set() # modimpl effectivement présents dans la table
for etudid in etuds_inscriptions:
idx = 0 # index de la colonne
etud = Identite.query.get(etudid)
row = {"etudid": etudid}
# --- Rang
add_cell(row, "rang", "Rg", self.etud_moy_gen_ranks[etudid], "rang")
idx = add_cell(
row, "rang", "Rg", self.etud_moy_gen_ranks[etudid], "rang", idx
)
row["_rang_order"] = f"{self.etud_moy_gen_ranks_int[etudid]:05d}"
# --- Identité étudiant
add_cell(row, "civilite_str", "Civ.", etud.civilite_str, "identite_detail")
add_cell(row, "nom_disp", "Nom", etud.nom_disp(), "identite_detail")
add_cell(row, "prenom", "Prénom", etud.prenom, "identite_detail")
add_cell(row, "nom_short", "Nom", etud.nom_short, "identite_court")
idx = add_cell(
row, "civilite_str", "Civ.", etud.civilite_str, "identite_detail", idx
)
idx = add_cell(
row, "nom_disp", "Nom", etud.nom_disp(), "identite_detail", idx
)
idx = add_cell(row, "prenom", "Prénom", etud.prenom, "identite_detail", idx)
idx = add_cell(
row, "nom_short", "Nom", etud.nom_short, "identite_court", idx
)
row["_nom_short_target"] = url_for(
"notes.formsemestre_bulletinetud",
scodoc_dept=g.scodoc_dept,
@ -476,6 +482,8 @@ class ResultatsSemestre(ResultatsCache):
row["_nom_short_target_attrs"] = f'class="etudinfo" id="{etudid}"'
row["_nom_disp_target"] = row["_nom_short_target"]
row["_nom_disp_target_attrs"] = row["_nom_short_target_attrs"]
idx = 30 # début des colonnes de notes
# --- Moyenne générale
moy_gen = self.etud_moy_gen.get(etudid, False)
note_class = ""
@ -483,12 +491,13 @@ class ResultatsSemestre(ResultatsCache):
moy_gen = NO_NOTE
elif isinstance(moy_gen, float) and moy_gen < barre_moy:
note_class = " moy_ue_warning" # en rouge
add_cell(
idx = add_cell(
row,
"moy_gen",
"Moy",
fmt_note(moy_gen),
"col_moy_gen" + note_class,
idx,
)
titles_bot["_moy_gen_target_attrs"] = (
'title="moyenne indicative"' if self.is_apc else ""
@ -510,16 +519,32 @@ class ResultatsSemestre(ResultatsCache):
if val < barre_warning_ue:
note_class = " moy_ue_warning" # notes très basses
nb_ues_warning += 1
add_cell(
idx = add_cell(
row,
col_id,
ue.acronyme,
fmt_note(val),
"col_ue" + note_class,
idx,
)
titles_bot[
f"_{col_id}_target_attrs"
] = f"""title="{ue.titre} S{ue.semestre_idx or '?'}" """
# Bonus (sport) dans cette UE ?
# Le bonus sport appliqué sur cette UE
if (self.bonus_ues is not None) and (ue.id in self.bonus_ues):
val = self.bonus_ues[ue.id][etud.id] or ""
val_fmt = fmt_note(val)
if val:
val_fmt = f'<span class="green-arrow-up"></span><span class="sp2l">{val_fmt}</span>'
idx = add_cell(
row,
f"bonus_ue_{ue.id}",
f"Bonus {ue.acronyme}",
val_fmt,
"col_ue_bonus",
idx,
)
# Les moyennes des modules (ou ressources et SAÉs) dans cette UE
for modimpl in self.modimpls_in_ue(ue.id, etudid, with_bonus=False):
if ue_status["is_capitalized"]:
@ -546,13 +571,19 @@ class ResultatsSemestre(ResultatsCache):
col_id = (
f"moy_{modimpl.module.type_abbrv()}_{modimpl.id}_{ue.id}"
)
add_cell(
val_fmt = fmt_note(val)
if modimpl.module.module_type == scu.ModuleType.MALUS:
val_fmt = (
(scu.EMO_RED_TRIANGLE_DOWN + val_fmt) if val else ""
)
idx = add_cell(
row,
col_id,
modimpl.module.code,
fmt_note(val),
val_fmt,
# class col_res mod_ue_123
f"col_{modimpl.module.type_abbrv()} mod_ue_{ue.id}",
idx,
)
titles_bot[f"_{col_id}_target"] = url_for(
"notes.moduleimpl_status",
@ -571,9 +602,10 @@ class ResultatsSemestre(ResultatsCache):
add_cell(
row,
"ues_validables",
"Nb UE",
"UEs",
ue_valid_txt,
"col_ue col_ues_validables",
29, # juste avant moy. gen.
)
if nb_ues_warning:
row["_ues_validables_class"] += " moy_ue_warning"
@ -581,6 +613,7 @@ class ResultatsSemestre(ResultatsCache):
row["_ues_validables_class"] += " moy_inf"
row["_ues_validables_order"] = nb_ues_validables # pour tri
rows.append(row)
self._recap_add_partitions(rows, titles)
self._recap_add_admissions(rows, titles)
# tri par rang croissant
@ -589,6 +622,16 @@ class ResultatsSemestre(ResultatsCache):
# INFOS POUR FOOTER
bottom_infos = self._recap_bottom_infos(ues_sans_bonus, modimpl_ids, fmt_note)
# Ajoute style "col_empty" aux colonnes de modules vides
for col_id in titles:
c_class = f"_{col_id}_class"
if "col_empty" in bottom_infos["moy"].get(c_class, ""):
for row in rows:
row[c_class] += " col_empty"
titles[c_class] += " col_empty"
for row in bottom_infos.values():
row[c_class] = row.get(c_class, "") + " col_empty"
# --- TABLE FOOTER: ECTS, moyennes, min, max...
footer_rows = []
for (bottom_line, row) in bottom_infos.items():
@ -604,7 +647,9 @@ class ResultatsSemestre(ResultatsCache):
titles_bot.update(titles)
footer_rows.append(titles_bot)
column_ids = [title for title in titles if not title.startswith("_")]
column_ids.sort(key=lambda col_id: titles.get("_" + col_id + "_col_order", 100))
column_ids.sort(
key=lambda col_id: titles.get("_" + col_id + "_col_order", 1000)
)
return (rows, footer_rows, titles, column_ids)
def _recap_bottom_infos(self, ues, modimpl_ids: set, fmt_note) -> dict:
@ -651,7 +696,11 @@ class ResultatsSemestre(ResultatsCache):
notes = self.modimpl_notes(modimpl.id, ue.id)
row_min[col_id] = fmt_note(np.nanmin(notes))
row_max[col_id] = fmt_note(np.nanmax(notes))
row_moy[col_id] = fmt_note(np.nanmean(notes))
moy = np.nanmean(notes)
row_moy[col_id] = fmt_note(moy)
if np.isnan(moy):
# aucune note dans ce module
row_moy[f"_{col_id}_class"] = "col_empty"
return { # { key : row } avec key = min, max, moy, coef
"min": row_min,
@ -696,6 +745,14 @@ class ResultatsSemestre(ResultatsCache):
"type_admission": "Type Adm.",
"classement": "Rg. Adm.",
}
first = True
for i, cid in enumerate(fields):
titles[f"_{cid}_col_order"] = 10000 + i # tout à droite
if first:
titles[f"_{cid}_class"] = "admission admission_first"
first = False
else:
titles[f"_{cid}_class"] = "admission"
titles.update(fields)
for row in rows:
etud = Identite.query.get(row["etudid"])
@ -708,8 +765,6 @@ class ResultatsSemestre(ResultatsCache):
first = False
else:
row[f"_{cid}_class"] = "admission"
titles[f"_{cid}_class"] = row[f"_{cid}_class"]
titles[f"_{cid}_col_order"] = 1000 # à la fin
def _recap_add_partitions(self, rows: list[dict], titles: dict):
"""Ajoute les colonnes indiquant les groupes

View File

@ -643,7 +643,7 @@ def make_formsemestre_recapcomplet(
"recap_row_nbeval",
"recap_row_ects",
)[ir - nblines + 6]
cells = '<tr class="%s sortbottom">' % styl
cells = f'<tr class="{styl} sortbottom">'
else:
el = etudlink % {
"formsemestre_id": formsemestre_id,
@ -651,14 +651,14 @@ def make_formsemestre_recapcomplet(
"name": l[1],
}
if ir % 2 == 0:
cells = '<tr class="recap_row_even" id="etudid%s">' % etudid
cells = f'<tr class="recap_row_even" id="etudid{etudid}">'
else:
cells = '<tr class="recap_row_odd" id="etudid%s">' % etudid
cells = f'<tr class="recap_row_odd" id="etudid{etudid}">'
ir += 1
# XXX nsn = [ x.replace('NA', '-') for x in l[:-2] ]
# notes sans le NA:
nsn = l[:-2] # copy
for i in range(len(nsn)):
for i, _ in enumerate(nsn):
if nsn[i] == "NA":
nsn[i] = "-"
try:
@ -1041,7 +1041,7 @@ def gen_formsemestre_recapcomplet_html(
"",
)
H = [
f"""<div class="table_recap"><table class="table_recap {'apc' if formsemestre.formation.is_apc() else ''}">"""
f"""<div class="table_recap"><table class="table_recap {'apc' if formsemestre.formation.is_apc() else 'classic'}">"""
]
# header
H.append(

View File

@ -933,6 +933,7 @@ ICON_XLS = icontag("xlsicon_img", title="Version tableur")
# HTML emojis
EMO_WARNING = "&#9888;&#65039;" # warning /!\
EMO_RED_TRIANGLE_DOWN = "&#128315;" # red triangle pointed down
def sort_dates(L, reverse=False):

View File

@ -3568,6 +3568,11 @@ table.table_recap tbody td:hover {
text-decoration: dashed underline;
}
/* col moy gen en gras seulement pour les form. classiques */
table.table_recap.classic td.col_moy_gen {
font-weight: bold;
}
table.table_recap .identite_court {
white-space: nowrap;
text-align: left;
@ -3637,6 +3642,39 @@ table.table_recap td.col_ues_validables {
font-style: normal !important;
}
.green-arrow-up {
display: inline-block;
width: 0;
height: 0;
border-left: 8px solid transparent;
border-right: 8px solid transparent;
border-bottom: 8px solid rgb(48, 239, 0);
}
table.table_recap td.col_ue_bonus,
table.table_recap th.col_ue_bonus {
font-size: 80%;
font-weight: bold;
color: rgb(0, 128, 11);
}
table.table_recap td.col_ue_bonus>span.sp2l {
margin-left: 2px;
}
table.table_recap td.col_ue_bonus {
white-space: nowrap;
}
table.table_recap td.col_malus,
table.table_recap th.col_malus {
font-size: 80%;
font-weight: bold;
color: rgb(165, 0, 0);
}
table.table_recap tr.ects td {
color: rgb(160, 86, 3);
font-weight: bold;

View File

@ -29,15 +29,19 @@ $(function () {
action: function (e, dt, node, config) {
let visible = dt.columns(".col_res").visible()[0];
dt.columns(".col_res").visible(!visible);
dt.columns(".col_ue_bonus").visible(!visible);
dt.buttons('toggle_res:name').text(visible ? "Montrer les ressources" : "Cacher les ressources");
}
} : {
name: "toggle_mod",
text: "Cacher les modules",
action: function (e, dt, node, config) {
let visible = dt.columns(".col_mod").visible()[0];
dt.columns(".col_mod").visible(!visible);
let visible = dt.columns(".col_mod:not(.col_empty)").visible()[0];
dt.columns(".col_mod:not(.col_empty)").visible(!visible);
dt.columns(".col_ue_bonus").visible(!visible);
dt.buttons('toggle_mod:name').text(visible ? "Montrer les modules" : "Cacher les modules");
visible = dt.columns(".col_empty").visible()[0];
dt.buttons('toggle_col_empty:name').text(visible ? "Cacher mod. vides" : "Montrer mod. vides");
}
}
];
@ -62,6 +66,15 @@ $(function () {
dt.buttons('toggle_admission:name').text(visible ? "Montrer infos admission" : "Cacher infos admission");
}
})
buttons.push({
name: "toggle_col_empty",
text: "Montrer mod. vides",
action: function (e, dt, node, config) {
let visible = dt.columns(".col_empty").visible()[0];
dt.columns(".col_empty").visible(!visible);
dt.buttons('toggle_col_empty:name').text(visible ? "Montrer mod. vides" : "Cacher mod. vides");
}
})
$('table.table_recap').DataTable(
{
paging: false,
@ -77,8 +90,8 @@ $(function () {
colReorder: true,
"columnDefs": [
{
// cache le détail de l'identité et les colonnes admission
"targets": ["identite_detail", "partition_aux", "admission"],
// cache le détail de l'identité, les groupes, les colonnes admission et les vides
"targets": ["identite_detail", "partition_aux", "admission", "col_empty"],
"visible": false,
},
],