WIP: nouveau tableau recap pour le BUT
This commit is contained in:
parent
b68d43454f
commit
13f55d5190
@ -15,10 +15,12 @@ from app.comp import moy_ue, moy_sem, inscr_mod
|
|||||||
from app.comp.res_common import NotesTableCompat
|
from app.comp.res_common import NotesTableCompat
|
||||||
from app.comp.bonus_spo import BonusSport
|
from app.comp.bonus_spo import BonusSport
|
||||||
from app.models import ScoDocSiteConfig
|
from app.models import ScoDocSiteConfig
|
||||||
|
from app.models.etudiants import Identite
|
||||||
from app.models.moduleimpls import ModuleImpl
|
from app.models.moduleimpls import ModuleImpl
|
||||||
from app.models.ues import UniteEns
|
from app.models.ues import UniteEns
|
||||||
from app.scodoc import sco_preferences
|
from app.scodoc import sco_preferences
|
||||||
from app.scodoc.sco_codes_parcours import UE_SPORT
|
from app.scodoc.sco_codes_parcours import UE_SPORT
|
||||||
|
import app.scodoc.sco_utils as scu
|
||||||
|
|
||||||
|
|
||||||
class ResultatsSemestreBUT(NotesTableCompat):
|
class ResultatsSemestreBUT(NotesTableCompat):
|
||||||
@ -32,6 +34,10 @@ class ResultatsSemestreBUT(NotesTableCompat):
|
|||||||
|
|
||||||
def __init__(self, formsemestre):
|
def __init__(self, formsemestre):
|
||||||
super().__init__(formsemestre)
|
super().__init__(formsemestre)
|
||||||
|
self.modimpl_coefs_df = None
|
||||||
|
"""DataFrame, row UEs(sans bonus), cols modimplid, value coef"""
|
||||||
|
self.sem_cube = None
|
||||||
|
"""ndarray (etuds x modimpl x ue)"""
|
||||||
|
|
||||||
if not self.load_cached():
|
if not self.load_cached():
|
||||||
t0 = time.time()
|
t0 = time.time()
|
||||||
@ -145,15 +151,228 @@ class ResultatsSemestreBUT(NotesTableCompat):
|
|||||||
"""
|
"""
|
||||||
return self.modimpl_coefs_df.loc[ue.id].sum()
|
return self.modimpl_coefs_df.loc[ue.id].sum()
|
||||||
|
|
||||||
def modimpls_in_ue(self, ue_id, etudid) -> list[ModuleImpl]:
|
def modimpls_in_ue(self, ue_id, etudid, with_bonus=True) -> list[ModuleImpl]:
|
||||||
"""Liste des modimpl ayant des coefs non nuls vers cette UE
|
"""Liste des modimpl ayant des coefs non nuls vers cette UE
|
||||||
et auxquels l'étudiant est inscrit.
|
et auxquels l'étudiant est inscrit. Inclus modules bonus le cas échéant.
|
||||||
"""
|
"""
|
||||||
# sert pour l'affichage ou non de l'UE sur le bulletin
|
# sert pour l'affichage ou non de l'UE sur le bulletin et la table recap
|
||||||
coefs = self.modimpl_coefs_df # row UE, cols modimpl
|
coefs = self.modimpl_coefs_df # row UE, cols modimpl
|
||||||
return [
|
modimpls = [
|
||||||
modimpl
|
modimpl
|
||||||
for modimpl in self.formsemestre.modimpls_sorted
|
for modimpl in self.formsemestre.modimpls_sorted
|
||||||
if (coefs[modimpl.id][ue_id] != 0)
|
if (coefs[modimpl.id][ue_id] != 0)
|
||||||
and self.modimpl_inscr_df[modimpl.id][etudid]
|
and self.modimpl_inscr_df[modimpl.id][etudid]
|
||||||
]
|
]
|
||||||
|
if not with_bonus:
|
||||||
|
return [
|
||||||
|
modimpl for modimpl in modimpls if modimpl.module.ue.type != UE_SPORT
|
||||||
|
]
|
||||||
|
return modimpls
|
||||||
|
|
||||||
|
def get_table_moyennes_triees(self, convert_values=False) -> list:
|
||||||
|
"""Result: tuple avec
|
||||||
|
- rows: liste de dicts { column_id : value }
|
||||||
|
- titles: { column_id : title }
|
||||||
|
- columns_ids: (liste des id de colonnes)
|
||||||
|
|
||||||
|
. 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 attributs:
|
||||||
|
- pour les lignes:
|
||||||
|
_css_row_class (inutilisé pour le monent)
|
||||||
|
_<column_id>_class classe css:
|
||||||
|
- 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>
|
||||||
|
_<column_id>_order : clé de tri
|
||||||
|
"""
|
||||||
|
|
||||||
|
def fmt_note(x):
|
||||||
|
return scu.fmt_note(x) if convert_values else x
|
||||||
|
|
||||||
|
barre_moy = (
|
||||||
|
self.formsemestre.formation.get_parcours().BARRE_MOY - scu.NOTES_TOLERANCE
|
||||||
|
)
|
||||||
|
barre_valid_ue = self.formsemestre.formation.get_parcours().NOTES_BARRE_VALID_UE
|
||||||
|
NO_NOTE = "-" # contenu des cellules sans notes
|
||||||
|
rows = []
|
||||||
|
titles = {"rang": "Rg"} # column_id : title
|
||||||
|
|
||||||
|
def add_cell(
|
||||||
|
row: dict, col_id: str, title: str, content: str, classes: str = ""
|
||||||
|
):
|
||||||
|
"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
|
||||||
|
if not col_id in titles:
|
||||||
|
titles[col_id] = title
|
||||||
|
if classes:
|
||||||
|
titles[f"_{col_id}_class"] = classes
|
||||||
|
|
||||||
|
etuds_inscriptions = self.formsemestre.etuds_inscriptions
|
||||||
|
ues = self.formsemestre.query_ues(with_sport=True) # avec bonus
|
||||||
|
modimpl_ids = set() # modimpl effectivement présents dans la table
|
||||||
|
for etudid in etuds_inscriptions:
|
||||||
|
etud = Identite.query.get(etudid)
|
||||||
|
row = {"etudid": etudid}
|
||||||
|
# --- Rang
|
||||||
|
add_cell(row, "rang", "Rg", self.etud_moy_gen_ranks[etudid], "rang")
|
||||||
|
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")
|
||||||
|
# --- Moyenne générale
|
||||||
|
moy_gen = self.etud_moy_gen.get(etudid, False)
|
||||||
|
note_class = ""
|
||||||
|
if moy_gen is False:
|
||||||
|
moy_gen = NO_NOTE
|
||||||
|
elif isinstance(moy_gen, float) and moy_gen < barre_moy:
|
||||||
|
note_class = " moy_inf"
|
||||||
|
add_cell(
|
||||||
|
row,
|
||||||
|
"moy_gen",
|
||||||
|
"Moy",
|
||||||
|
fmt_note(moy_gen),
|
||||||
|
"col_moy_gen" + note_class,
|
||||||
|
)
|
||||||
|
# --- Moyenne d'UE
|
||||||
|
for ue in [ue for ue in ues if ue.type != UE_SPORT]:
|
||||||
|
ue_status = self.get_etud_ue_status(etudid, ue.id)
|
||||||
|
if ue_status is not None:
|
||||||
|
col_id = f"moy_ue_{ue.id}"
|
||||||
|
val = ue_status["moy"]
|
||||||
|
note_class = ""
|
||||||
|
if isinstance(val, float):
|
||||||
|
if val < barre_moy:
|
||||||
|
note_class = " moy_inf"
|
||||||
|
elif val >= barre_valid_ue:
|
||||||
|
note_class = " moy_ue_valid"
|
||||||
|
add_cell(
|
||||||
|
row,
|
||||||
|
col_id,
|
||||||
|
ue.acronyme,
|
||||||
|
fmt_note(val),
|
||||||
|
"col_ue" + note_class,
|
||||||
|
)
|
||||||
|
# Les moyennes des 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"]:
|
||||||
|
val = "-c-"
|
||||||
|
else:
|
||||||
|
modimpl_results = self.modimpls_results.get(modimpl.id)
|
||||||
|
if modimpl_results: # pas bonus
|
||||||
|
moys_vers_ue = modimpl_results.etuds_moy_module.get(
|
||||||
|
ue.id
|
||||||
|
)
|
||||||
|
val = (
|
||||||
|
moys_vers_ue.get(etudid, "?")
|
||||||
|
if moys_vers_ue is not None
|
||||||
|
else ""
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
val = ""
|
||||||
|
|
||||||
|
col_id = (
|
||||||
|
f"moy_{modimpl.module.type_abbrv()}_{modimpl.id}_{ue.id}"
|
||||||
|
)
|
||||||
|
add_cell(
|
||||||
|
row,
|
||||||
|
col_id,
|
||||||
|
modimpl.module.code,
|
||||||
|
fmt_note(val),
|
||||||
|
# class col_res mod_ue_123
|
||||||
|
f"col_{modimpl.module.type_abbrv()} mod_ue_{ue.id}",
|
||||||
|
)
|
||||||
|
modimpl_ids.add(modimpl.id)
|
||||||
|
|
||||||
|
rows.append(row)
|
||||||
|
|
||||||
|
# tri par rang croissant
|
||||||
|
rows.sort(key=lambda e: e["_rang_order"])
|
||||||
|
|
||||||
|
# INFOS POUR FOOTER
|
||||||
|
bottom_infos = self._bottom_infos(
|
||||||
|
[ue for ue in ues if ue.type != UE_SPORT], modimpl_ids, fmt_note
|
||||||
|
)
|
||||||
|
|
||||||
|
# --- TABLE FOOTER: ECTS, moyennes, min, max...
|
||||||
|
footer_rows = []
|
||||||
|
for bottom_line in bottom_infos:
|
||||||
|
row = bottom_infos[bottom_line]
|
||||||
|
# Cases vides à styler:
|
||||||
|
row["moy_gen"] = row.get("moy_gen", "")
|
||||||
|
row["_moy_gen_class"] = "col_moy_gen"
|
||||||
|
# titre de la ligne:
|
||||||
|
row["nom_disp"] = row["nom_short"] = bottom_line.capitalize()
|
||||||
|
row["_tr_class"] = bottom_line.lower()
|
||||||
|
footer_rows.append(row)
|
||||||
|
return (
|
||||||
|
rows,
|
||||||
|
footer_rows,
|
||||||
|
titles,
|
||||||
|
[title for title in titles if not title.startswith("_")],
|
||||||
|
)
|
||||||
|
|
||||||
|
def _bottom_infos(self, ues, modimpl_ids: set, fmt_note) -> dict:
|
||||||
|
"""Les informations à mettre en bas de la table: min, max, moy, ECTS"""
|
||||||
|
bottom_infos = { # { key : row } avec key = min, max, moy, coef
|
||||||
|
"min": {},
|
||||||
|
"max": {},
|
||||||
|
"moy": {},
|
||||||
|
"coef": {},
|
||||||
|
}
|
||||||
|
# --- ECTS
|
||||||
|
row = {}
|
||||||
|
for ue in ues:
|
||||||
|
row[f"moy_ue_{ue.id}"] = ue.ects
|
||||||
|
row[f"_moy_ue_{ue.id}_class"] = "col_ue"
|
||||||
|
# style cases vides pour borders verticales
|
||||||
|
bottom_infos["coef"][f"moy_ue_{ue.id}"] = ""
|
||||||
|
bottom_infos["coef"][f"_moy_ue_{ue.id}_class"] = "col_ue"
|
||||||
|
row["moy_gen"] = sum([ue.ects or 0 for ue in ues if ue.type != UE_SPORT])
|
||||||
|
row["_moy_gen_class"] = "col_moy_gen"
|
||||||
|
bottom_infos["ects"] = row
|
||||||
|
|
||||||
|
# --- MIN, MAX, MOY
|
||||||
|
row_min, row_max, row_moy = {}, {}, {}
|
||||||
|
row_min["moy_gen"] = fmt_note(self.etud_moy_gen.min())
|
||||||
|
row_max["moy_gen"] = fmt_note(self.etud_moy_gen.max())
|
||||||
|
row_moy["moy_gen"] = fmt_note(self.etud_moy_gen.mean())
|
||||||
|
for ue in [ue for ue in ues if ue.type != UE_SPORT]:
|
||||||
|
col_id = f"moy_ue_{ue.id}"
|
||||||
|
row_min[col_id] = fmt_note(self.etud_moy_ue[ue.id].min())
|
||||||
|
row_max[col_id] = fmt_note(self.etud_moy_ue[ue.id].max())
|
||||||
|
row_moy[col_id] = fmt_note(self.etud_moy_ue[ue.id].mean())
|
||||||
|
row_min[f"_{col_id}_class"] = "col_ue"
|
||||||
|
row_max[f"_{col_id}_class"] = "col_ue"
|
||||||
|
row_moy[f"_{col_id}_class"] = "col_ue"
|
||||||
|
|
||||||
|
for modimpl in self.formsemestre.modimpls_sorted:
|
||||||
|
if modimpl.id in modimpl_ids:
|
||||||
|
col_id = f"moy_{modimpl.module.type_abbrv()}_{modimpl.id}_{ue.id}"
|
||||||
|
bottom_infos["coef"][col_id] = fmt_note(
|
||||||
|
self.modimpl_coefs_df[modimpl.id][ue.id]
|
||||||
|
)
|
||||||
|
i = self.modimpl_coefs_df.columns.get_loc(modimpl.id)
|
||||||
|
j = self.modimpl_coefs_df.index.get_loc(ue.id)
|
||||||
|
notes = self.sem_cube[:, i, j]
|
||||||
|
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))
|
||||||
|
|
||||||
|
bottom_infos["min"] = row_min
|
||||||
|
bottom_infos["max"] = row_max
|
||||||
|
bottom_infos["moy"] = row_moy
|
||||||
|
return bottom_infos
|
||||||
|
@ -123,6 +123,11 @@ class Identite(db.Model):
|
|||||||
r.append("-".join([x.lower().capitalize() for x in fields]))
|
r.append("-".join([x.lower().capitalize() for x in fields]))
|
||||||
return " ".join(r)
|
return " ".join(r)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def nom_short(self):
|
||||||
|
"Nom et début du prénom pour table recap: 'DUPONT Pi.'"
|
||||||
|
return f"{(self.nom_usuel or self.nom or '?').upper()} {(self.prenom or '')[:2].capitalize()}."
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def sort_key(self) -> tuple:
|
def sort_key(self) -> tuple:
|
||||||
"clé pour tris par ordre alphabétique"
|
"clé pour tris par ordre alphabétique"
|
||||||
|
@ -33,7 +33,7 @@ class Module(db.Model):
|
|||||||
numero = db.Column(db.Integer) # ordre de présentation
|
numero = db.Column(db.Integer) # ordre de présentation
|
||||||
# id de l'element pedagogique Apogee correspondant:
|
# id de l'element pedagogique Apogee correspondant:
|
||||||
code_apogee = db.Column(db.String(APO_CODE_STR_LEN))
|
code_apogee = db.Column(db.String(APO_CODE_STR_LEN))
|
||||||
# Type: ModuleType: DEFAULT, MALUS, RESSOURCE, MODULE_SAE (enum)
|
# Type: ModuleType.STANDARD, MALUS, RESSOURCE, SAE (enum)
|
||||||
module_type = db.Column(db.Integer, nullable=False, default=0, server_default="0")
|
module_type = db.Column(db.Integer, nullable=False, default=0, server_default="0")
|
||||||
# Relations:
|
# Relations:
|
||||||
modimpls = db.relationship("ModuleImpl", backref="module", lazy="dynamic")
|
modimpls = db.relationship("ModuleImpl", backref="module", lazy="dynamic")
|
||||||
@ -76,6 +76,11 @@ class Module(db.Model):
|
|||||||
def type_name(self):
|
def type_name(self):
|
||||||
return scu.MODULE_TYPE_NAMES[self.module_type]
|
return scu.MODULE_TYPE_NAMES[self.module_type]
|
||||||
|
|
||||||
|
def type_abbrv(self):
|
||||||
|
""" "mod", "malus", "res", "sae"
|
||||||
|
(utilisées pour style css)"""
|
||||||
|
return scu.ModuleType.get_abbrev(self.module_type)
|
||||||
|
|
||||||
def set_ue_coef(self, ue, coef: float) -> None:
|
def set_ue_coef(self, ue, coef: float) -> None:
|
||||||
"""Set coef module vers cette UE"""
|
"""Set coef module vers cette UE"""
|
||||||
self.update_ue_coef_dict({ue.id: coef})
|
self.update_ue_coef_dict({ue.id: coef})
|
||||||
|
@ -4,7 +4,6 @@
|
|||||||
from app import db
|
from app import db
|
||||||
from app.models import APO_CODE_STR_LEN
|
from app.models import APO_CODE_STR_LEN
|
||||||
from app.models import SHORT_STR_LEN
|
from app.models import SHORT_STR_LEN
|
||||||
from app.scodoc import notesdb as ndb
|
|
||||||
from app.scodoc import sco_utils as scu
|
from app.scodoc import sco_utils as scu
|
||||||
|
|
||||||
|
|
||||||
|
@ -38,6 +38,7 @@ from flask import make_response, url_for
|
|||||||
from app import log
|
from app import log
|
||||||
from app.but import bulletin_but
|
from app.but import bulletin_but
|
||||||
from app.comp import res_sem
|
from app.comp import res_sem
|
||||||
|
from app.comp.res_but import ResultatsSemestreBUT
|
||||||
from app.comp.res_common import NotesTableCompat
|
from app.comp.res_common import NotesTableCompat
|
||||||
from app.models import FormSemestre
|
from app.models import FormSemestre
|
||||||
from app.models.etudiants import Identite
|
from app.models.etudiants import Identite
|
||||||
@ -108,7 +109,7 @@ def formsemestre_recapcomplet(
|
|||||||
page_title="Récapitulatif",
|
page_title="Récapitulatif",
|
||||||
no_side_bar=True,
|
no_side_bar=True,
|
||||||
init_qtip=True,
|
init_qtip=True,
|
||||||
javascripts=["js/etud_info.js"],
|
javascripts=["js/etud_info.js", "js/table_recap.js"],
|
||||||
),
|
),
|
||||||
sco_formsemestre_status.formsemestre_status_head(
|
sco_formsemestre_status.formsemestre_status_head(
|
||||||
formsemestre_id=formsemestre_id
|
formsemestre_id=formsemestre_id
|
||||||
@ -223,19 +224,28 @@ def do_formsemestre_recapcomplet(
|
|||||||
force_publishing=True,
|
force_publishing=True,
|
||||||
):
|
):
|
||||||
"""Calcule et renvoie le tableau récapitulatif."""
|
"""Calcule et renvoie le tableau récapitulatif."""
|
||||||
data, filename, format = make_formsemestre_recapcomplet(
|
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||||
formsemestre_id=formsemestre_id,
|
if (
|
||||||
format=format,
|
formsemestre.formation.is_apc()
|
||||||
hidemodules=hidemodules,
|
and format not in ("xml", "json")
|
||||||
hidebac=hidebac,
|
and not modejury
|
||||||
xml_nodate=xml_nodate,
|
):
|
||||||
modejury=modejury,
|
data, filename = make_formsemestre_recapcomplet_apc(formsemestre, format=format)
|
||||||
sortcol=sortcol,
|
else:
|
||||||
xml_with_decisions=xml_with_decisions,
|
data, filename, format = make_formsemestre_recapcomplet(
|
||||||
disable_etudlink=disable_etudlink,
|
formsemestre_id=formsemestre_id,
|
||||||
rank_partition_id=rank_partition_id,
|
format=format,
|
||||||
force_publishing=force_publishing,
|
hidemodules=hidemodules,
|
||||||
)
|
hidebac=hidebac,
|
||||||
|
xml_nodate=xml_nodate,
|
||||||
|
modejury=modejury,
|
||||||
|
sortcol=sortcol,
|
||||||
|
xml_with_decisions=xml_with_decisions,
|
||||||
|
disable_etudlink=disable_etudlink,
|
||||||
|
rank_partition_id=rank_partition_id,
|
||||||
|
force_publishing=force_publishing,
|
||||||
|
)
|
||||||
|
# ---
|
||||||
if format == "xml" or format == "html":
|
if format == "xml" or format == "html":
|
||||||
return data
|
return data
|
||||||
elif format == "csv":
|
elif format == "csv":
|
||||||
@ -1004,3 +1014,59 @@ def formsemestres_bulletins(annee_scolaire):
|
|||||||
jslist.append(J)
|
jslist.append(J)
|
||||||
|
|
||||||
return scu.sendJSON(jslist)
|
return scu.sendJSON(jslist)
|
||||||
|
|
||||||
|
|
||||||
|
def _gen_cell(key: str, row: dict, elt="td"):
|
||||||
|
"html table cell"
|
||||||
|
klass = row.get(f"_{key}_class")
|
||||||
|
attrs = f'class="{klass}"' if klass else ""
|
||||||
|
order = row.get("_{key}_order")
|
||||||
|
if order:
|
||||||
|
attrs += f' data-order="{order}"'
|
||||||
|
return f'<{elt} {attrs}>{row.get(key, "")}</{elt}>'
|
||||||
|
|
||||||
|
|
||||||
|
def _gen_row(keys: list[str], row, elt="td"):
|
||||||
|
klass = row.get("_tr_class")
|
||||||
|
tr_class = f'class="{klass}"' if klass else ""
|
||||||
|
return f'<tr {tr_class}>{"".join([_gen_cell(key, row, elt) for key in keys])}</tr>'
|
||||||
|
|
||||||
|
|
||||||
|
def make_formsemestre_recapcomplet_apc(formsemestre: FormSemestre, format="html"):
|
||||||
|
"""Construit table recap pour le BUT
|
||||||
|
Return: data, filename
|
||||||
|
"""
|
||||||
|
res: ResultatsSemestreBUT = res_sem.load_formsemestre_results(formsemestre)
|
||||||
|
rows, footer_rows, titles, column_ids = res.get_table_moyennes_triees(
|
||||||
|
convert_values=True
|
||||||
|
)
|
||||||
|
H = ['<div class="table_recap"><table class="table_recap">']
|
||||||
|
# header
|
||||||
|
H.append(
|
||||||
|
f"""
|
||||||
|
<thead>
|
||||||
|
{_gen_row(column_ids, titles, "th")}
|
||||||
|
</thead>
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
# body
|
||||||
|
H.append("<tbody>")
|
||||||
|
for row in rows:
|
||||||
|
H.append(f"{_gen_row(column_ids, row)}\n")
|
||||||
|
H.append("</tbody>\n")
|
||||||
|
# footer
|
||||||
|
H.append("<tfoot>")
|
||||||
|
for row in footer_rows:
|
||||||
|
H.append(f"{_gen_row(column_ids, row)}\n")
|
||||||
|
H.append(
|
||||||
|
f"""
|
||||||
|
{_gen_row(column_ids, titles, "th")}
|
||||||
|
</tfoot>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
return (
|
||||||
|
"".join(H),
|
||||||
|
f'recap-{formsemestre.titre_num().replace(" ", "_")}-{time.strftime("%d-%m-%Y")}',
|
||||||
|
) # suffix ?
|
||||||
|
@ -87,6 +87,19 @@ class ModuleType(IntEnum):
|
|||||||
RESSOURCE = 2 # BUT
|
RESSOURCE = 2 # BUT
|
||||||
SAE = 3 # BUT
|
SAE = 3 # BUT
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_abbrev(cls, code) -> str:
|
||||||
|
"""Chaine abregée décrivant le type de module à partir du code integer:
|
||||||
|
"mod", "malus", "res", "sae"
|
||||||
|
(utilisées pour style css)
|
||||||
|
"""
|
||||||
|
return {
|
||||||
|
ModuleType.STANDARD: "mod",
|
||||||
|
ModuleType.MALUS: "malus",
|
||||||
|
ModuleType.RESSOURCE: "res",
|
||||||
|
ModuleType.SAE: "sae",
|
||||||
|
}.get(code, "???")
|
||||||
|
|
||||||
|
|
||||||
MODULE_TYPE_NAMES = {
|
MODULE_TYPE_NAMES = {
|
||||||
ModuleType.STANDARD: "Module",
|
ModuleType.STANDARD: "Module",
|
||||||
|
@ -3237,4 +3237,46 @@ table.dataTable tr.gt_lastrow th {
|
|||||||
}
|
}
|
||||||
table.dataTable td.etudinfo, table.dataTable td.group {
|
table.dataTable td.etudinfo, table.dataTable td.group {
|
||||||
text-align: left;
|
text-align: left;
|
||||||
|
}
|
||||||
|
/* Nouveau tableau recap */
|
||||||
|
div.table_recap table.table_recap {
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
table.table_recap .identite_court {
|
||||||
|
white-space:nowrap;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
table.table_recap .rang {
|
||||||
|
white-space:nowrap;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
table.table_recap .col_ue, table.table_recap .col_moy_gen {
|
||||||
|
border-left: 1px solid blue;
|
||||||
|
}
|
||||||
|
table.table_recap tfoot th, table.table_recap thead th {
|
||||||
|
text-align: left;
|
||||||
|
padding-left: 10px !important;
|
||||||
|
}
|
||||||
|
table.table_recap td.moy_inf {
|
||||||
|
font-weight: bold;
|
||||||
|
color: rgb(255,0,0);
|
||||||
|
}
|
||||||
|
table.table_recap td.moy_ue_valid {
|
||||||
|
font-weight: bold;
|
||||||
|
color: rgb(0,140,0);
|
||||||
|
}
|
||||||
|
table.table_recap tr.ects td {
|
||||||
|
color: rgb(160, 86, 3);
|
||||||
|
font-weight: bold;
|
||||||
|
border-bottom: 1px solid blue;
|
||||||
|
}
|
||||||
|
table.table_recap tr.coef td {
|
||||||
|
font-style: italic;
|
||||||
|
color: #9400d3;
|
||||||
|
}
|
||||||
|
table.table_recap tr.coef td, table.table_recap tr.min td,
|
||||||
|
table.table_recap tr.max td, table.table_recap tr.moy td {
|
||||||
|
font-size: 80%;
|
||||||
|
padding-top: 3px;
|
||||||
|
padding-bottom: 3px;
|
||||||
}
|
}
|
67
app/static/js/table_recap.js
Normal file
67
app/static/js/table_recap.js
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
// Tableau recap notes
|
||||||
|
$(function () {
|
||||||
|
$(function () {
|
||||||
|
$('table.table_recap').DataTable(
|
||||||
|
{
|
||||||
|
paging: false,
|
||||||
|
searching: true,
|
||||||
|
info: false,
|
||||||
|
autoWidth: false,
|
||||||
|
fixedHeader: {
|
||||||
|
header: true,
|
||||||
|
footer: true
|
||||||
|
},
|
||||||
|
orderCellsTop: true, // cellules ligne 1 pour tri
|
||||||
|
aaSorting: [], // Prevent initial sorting
|
||||||
|
colReorder: true,
|
||||||
|
"columnDefs": [
|
||||||
|
{
|
||||||
|
// cache le détail de l'identité (pas réussi à le faire avec le sélecteur css)
|
||||||
|
"targets": [1, 2, 3], // ".identite_detail",
|
||||||
|
"visible": false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
dom: 'Bfrtip',
|
||||||
|
buttons: [
|
||||||
|
'copy', 'excel', 'pdf',
|
||||||
|
{
|
||||||
|
extend: 'collection',
|
||||||
|
text: 'Réglages affichage',
|
||||||
|
autoClose: true,
|
||||||
|
buttons: [
|
||||||
|
{
|
||||||
|
name: "toggle_ident",
|
||||||
|
text: "Civ/Nom/Prénom",
|
||||||
|
action: function (e, dt, node, config) {
|
||||||
|
let visible = dt.columns(".identite_detail").visible()[0];
|
||||||
|
dt.columns(".identite_detail").visible(!visible);
|
||||||
|
dt.columns(".identite_court").visible(visible);
|
||||||
|
dt.buttons('toggle_ident:name').text(visible ? "Civ/Nom/Prénom" : "Nom");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "toggle_res",
|
||||||
|
text: "Cacher les ressources",
|
||||||
|
action: function (e, dt, node, config) {
|
||||||
|
let visible = dt.columns(".col_res").visible()[0];
|
||||||
|
dt.columns(".col_res").visible(!visible);
|
||||||
|
dt.buttons('toggle_res:name').text(visible ? "Montrer les ressources" : "Cacher les ressources");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "toggle_sae",
|
||||||
|
text: "Cacher les SAÉs",
|
||||||
|
action: function (e, dt, node, config) {
|
||||||
|
let visible = dt.columns(".col_sae").visible()[0];
|
||||||
|
dt.columns(".col_sae").visible(!visible);
|
||||||
|
dt.buttons('toggle_sae:name').text(visible ? "Montrer les SAÉs" : "Cacher les SAÉs");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
@ -1,7 +1,7 @@
|
|||||||
# -*- mode: python -*-
|
# -*- mode: python -*-
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
SCOVERSION = "9.1.86"
|
SCOVERSION = "9.2-86"
|
||||||
|
|
||||||
SCONAME = "ScoDoc"
|
SCONAME = "ScoDoc"
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user