forked from ScoDoc/ScoDoc
WIP: table jury.
This commit is contained in:
parent
d4da019c2e
commit
724d01c36a
@ -4,7 +4,7 @@
|
||||
# See LICENSE
|
||||
##############################################################################
|
||||
|
||||
"""Jury BUT: table recap annuelle et liens saisie
|
||||
"""Jury BUT et classiques: table recap annuelle et liens saisie
|
||||
"""
|
||||
|
||||
import collections
|
||||
@ -19,15 +19,15 @@ from app.but.jury_but import (
|
||||
DecisionsProposeesUE,
|
||||
)
|
||||
from app.comp.res_but import ResultatsSemestreBUT
|
||||
from app.comp.res_compat import NotesTableCompat
|
||||
from app.comp import res_sem
|
||||
from app.models import UniteEns
|
||||
from app.models.etudiants import Identite
|
||||
from app.scodoc.sco_exceptions import ScoNoReferentielCompetences
|
||||
from app.models.formsemestre import FormSemestre
|
||||
from app.scodoc import html_sco_header
|
||||
from app.scodoc.sco_codes_parcours import (
|
||||
BUT_BARRE_RCUE,
|
||||
BUT_BARRE_UE,
|
||||
BUT_BARRE_UE8,
|
||||
BUT_RCUE_SUFFISANT,
|
||||
)
|
||||
from app.scodoc import sco_formsemestre_status
|
||||
@ -39,16 +39,97 @@ from app.tables.recap import RowRecap, TableRecap
|
||||
class TableJury(TableRecap):
|
||||
"""Cette table recap reprend les colonnes du tableau recap, sauf les évaluations,
|
||||
et ajoute:
|
||||
- les RCUEs
|
||||
Pour le BUT:
|
||||
- les RCUEs (moyenne et code décision)
|
||||
Pour toutes les formations:
|
||||
- les codes de décisions jury sur les UEs
|
||||
- le lien de saisie ou modif de la décision de jury
|
||||
"""
|
||||
|
||||
def __init__(self, *args, row_class: str = None, read_only=True, **kwargs):
|
||||
super().__init__(
|
||||
*args, row_class=row_class or RowJury, finalize=False, **kwargs
|
||||
)
|
||||
# redéclare pour VSCode
|
||||
self.rows: list["RowJury"] = self.rows
|
||||
self.res: NotesTableCompat = self.res
|
||||
self.read_only = read_only
|
||||
# Stats jury: fréquence de chaque code enregistré
|
||||
self.freq_codes_annuels = collections.Counter()
|
||||
# Ajout colonnes spécifiques à la table jury:
|
||||
|
||||
if self.res.is_apc:
|
||||
self.add_rcues()
|
||||
self.add_jury()
|
||||
# Termine la table
|
||||
self.finalize()
|
||||
self.add_groups_header()
|
||||
|
||||
def add_rcues(self):
|
||||
"""Ajoute les colonnes indiquant le nb de RCUEs et chaque RCUE
|
||||
pour tous les étudiants de la table.
|
||||
La table contient des rows avec la clé etudid.
|
||||
Les colonnes ont la classe css "rcue".
|
||||
"""
|
||||
self.insert_group("rcue", before="col_ues_validables")
|
||||
for row in self.rows:
|
||||
etud: Identite = row.etud
|
||||
deca = row.deca
|
||||
if deca.code_valide:
|
||||
self.freq_codes_annuels[deca.code_valide] += 1
|
||||
row.add_nb_rcues_cell()
|
||||
# --- Les RCUEs
|
||||
for rcue in deca.rcues_annee:
|
||||
dec_rcue = deca.dec_rcue_by_ue.get(rcue.ue_1.id)
|
||||
if dec_rcue is not None: # None si l'UE n'est pas associée à un niveau
|
||||
row.add_rcue_cols(dec_rcue)
|
||||
|
||||
def add_jury(self):
|
||||
"""Ajoute la colonne code jury et le lien.
|
||||
Le code jury est celui du semestre: cette colonne n'est montrée
|
||||
que pour les formations classiques, ce code n'est pas utilisé en BUT.
|
||||
"""
|
||||
res = self.res
|
||||
if res.validations:
|
||||
for row in self.rows:
|
||||
etud = row.etud
|
||||
if not res.is_apc:
|
||||
# formations classiques: code semestre
|
||||
dec_sem = res.validations.decisions_jury.get(etud.id)
|
||||
jury_code_sem = dec_sem["code"] if dec_sem else ""
|
||||
row.add_cell(
|
||||
"jury_code_sem", "Jury", jury_code_sem, group="jury_code_sem"
|
||||
)
|
||||
row.add_cell(
|
||||
"jury_link",
|
||||
"",
|
||||
f"""{("modifier" if res.validations.has_decision(etud) else "saisir")
|
||||
if res.formsemestre.etat else "voir"} décisions""",
|
||||
group="col_jury_link",
|
||||
target=url_for(
|
||||
"notes.formsemestre_validation_etud_form",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
formsemestre_id=res.formsemestre.id,
|
||||
etudid=etud.id,
|
||||
),
|
||||
target_attrs={"class": "stdlink"},
|
||||
)
|
||||
|
||||
|
||||
class RowJury(RowRecap):
|
||||
"Ligne de la table saisie jury"
|
||||
|
||||
def add_nb_rcues_cell(self, deca: DecisionsProposeesAnnee):
|
||||
def __init__(self, table: TableJury, etud: Identite, *args, **kwargs):
|
||||
self.table: TableJury = table
|
||||
super().__init__(table, etud, *args, **kwargs)
|
||||
# Conserve le deca de cet étudiant:
|
||||
self.deca = jury_but.DecisionsProposeesAnnee(
|
||||
self.etud, self.table.res.formsemestre
|
||||
)
|
||||
|
||||
def add_nb_rcues_cell(self):
|
||||
"cell avec nb niveaux validables / total"
|
||||
deca = self.deca
|
||||
classes = ["col_rcue", "col_rcues_validables"]
|
||||
if deca.nb_rcues_under_8 > 0:
|
||||
classes.append("moy_ue_warning")
|
||||
@ -78,42 +159,71 @@ class RowJury(RowRecap):
|
||||
"RCUEs",
|
||||
f"""{deca.nb_validables}/{deca.nb_competences}"""
|
||||
+ ((" " + scu.EMO_WARNING) if deca.nb_rcues_under_8 > 0 else ""),
|
||||
group="rcues_validables",
|
||||
raw_content=f"""{deca.nb_validables}/{deca.nb_competences}""",
|
||||
group="rcue",
|
||||
classes=classes,
|
||||
data={"order": order},
|
||||
)
|
||||
|
||||
def add_ue_cells(self, dec_ue: DecisionsProposeesUE):
|
||||
"cell de moyenne d'UE"
|
||||
col_id = f"moy_ue_{dec_ue.ue.id}"
|
||||
def add_ue_cols(self, ue: UniteEns, ue_status: dict):
|
||||
"Ajoute 2 colonnes: moyenne d'UE et code jury"
|
||||
super().add_ue_cols(ue, ue_status) # table recap standard
|
||||
dues = self.table.res.get_etud_decision_ues(self.etud.id)
|
||||
if not dues:
|
||||
return
|
||||
due = dues.get(ue.id)
|
||||
if not due:
|
||||
return
|
||||
col_id = f"moy_ue_{ue.id}_code"
|
||||
self.add_cell(
|
||||
col_id,
|
||||
"", # titre vide
|
||||
due["code"],
|
||||
raw_content=due["code"],
|
||||
group="col_ue",
|
||||
classes=["recorded_code"],
|
||||
column_classes={"col_jury", "col_ue_code"},
|
||||
target_attrs={
|
||||
"title": f"""enregistrée le {due['event_date']}, {
|
||||
(due["ects"] or 0):.3g} ECTS."""
|
||||
},
|
||||
)
|
||||
|
||||
def add_rcue_cols(self, dec_rcue: DecisionsProposeesRCUE):
|
||||
"2 cells: moyenne du RCUE, code enregistré"
|
||||
self.table.group_titles["rcue"] = "RCUEs en cours"
|
||||
rcue = dec_rcue.rcue
|
||||
col_id = f"moy_rcue_{rcue.ue_1.niveau_competence_id}" # le niveau_id
|
||||
note_class = ""
|
||||
val = dec_ue.moy_ue
|
||||
val = rcue.moy_rcue
|
||||
if isinstance(val, float):
|
||||
if val < BUT_BARRE_UE:
|
||||
note_class = "moy_inf"
|
||||
elif val >= BUT_BARRE_UE:
|
||||
if val < BUT_BARRE_RCUE:
|
||||
note_class = "moy_ue_inf"
|
||||
elif val >= BUT_BARRE_RCUE:
|
||||
note_class = "moy_ue_valid"
|
||||
if val < BUT_BARRE_UE8:
|
||||
if val < BUT_RCUE_SUFFISANT:
|
||||
note_class = "moy_ue_warning" # notes très basses
|
||||
self.add_cell(
|
||||
col_id,
|
||||
dec_ue.ue.acronyme,
|
||||
self.fmt_note(val),
|
||||
group="col_ue",
|
||||
classes="col_ue" + note_class,
|
||||
column_class="col_ue",
|
||||
f"<div>{rcue.ue_1.acronyme}</div><div>{rcue.ue_2.acronyme}</div>",
|
||||
self.table.fmt_note(val),
|
||||
raw_content=val,
|
||||
group="rcue",
|
||||
classes=[note_class],
|
||||
column_classes={"col_rcue"},
|
||||
)
|
||||
self.add_cell(
|
||||
col_id + "_code",
|
||||
dec_ue.ue.acronyme,
|
||||
dec_ue.code_valide or "",
|
||||
classes="col_ue_code recorded_code",
|
||||
column_class="col_ue",
|
||||
f"<div>{rcue.ue_1.acronyme}</div><div>{rcue.ue_2.acronyme}</div>",
|
||||
dec_rcue.code_valide or "",
|
||||
group="rcue",
|
||||
classes=["col_rcue_code", "recorded_code"],
|
||||
column_classes={"col_rcue"},
|
||||
)
|
||||
|
||||
|
||||
def formsemestre_saisie_jury_but(
|
||||
formsemestre2: FormSemestre,
|
||||
formsemestre: FormSemestre,
|
||||
read_only: bool = False,
|
||||
selected_etudid: int = None,
|
||||
mode="jury",
|
||||
@ -125,7 +235,6 @@ def formsemestre_saisie_jury_but(
|
||||
|
||||
Si mode == "recap", table recap des codes, sans liens de saisie.
|
||||
"""
|
||||
# Quick & Dirty
|
||||
# pour chaque etud de res2 trié
|
||||
# S1: UE1, ..., UEn
|
||||
# S2: UE1, ..., UEn
|
||||
@ -137,32 +246,40 @@ def formsemestre_saisie_jury_but(
|
||||
# Pour le 1er etud, faire un check_ues_ready_jury(self) -> page d'erreur
|
||||
# -> rcue .ue_1, .ue_2 -> stroe moy ues, rcue.moy_rcue, etc
|
||||
|
||||
if formsemestre2.formation.referentiel_competence is None:
|
||||
raise ScoNoReferentielCompetences(formation=formsemestre2.formation)
|
||||
if formsemestre.formation.referentiel_competence is None:
|
||||
raise ScoNoReferentielCompetences(formation=formsemestre.formation)
|
||||
|
||||
rows, titles, column_ids, jury_stats = get_jury_but_table(
|
||||
formsemestre2, read_only=read_only, mode=mode
|
||||
res: ResultatsSemestreBUT = res_sem.load_formsemestre_results(formsemestre)
|
||||
table = TableJury(
|
||||
res,
|
||||
convert_values=True,
|
||||
mode_jury=True,
|
||||
read_only=read_only,
|
||||
classes=[
|
||||
"table_jury_but_bilan" if mode == "recap" else "",
|
||||
"table_recap",
|
||||
"apc",
|
||||
"jury table_jury_but",
|
||||
],
|
||||
selected_row_id=selected_etudid,
|
||||
)
|
||||
if not rows:
|
||||
if table.is_empty():
|
||||
return (
|
||||
'<div class="table_recap"><div class="message">aucun étudiant !</div></div>'
|
||||
)
|
||||
filename = scu.sanitize_filename(
|
||||
f"""jury-but-{formsemestre2.titre_num()}-{time.strftime("%Y-%m-%d")}"""
|
||||
)
|
||||
klass = "table_jury_but_bilan" if mode == "recap" else ""
|
||||
table_html = build_table_jury_but_html(
|
||||
filename, rows, titles, column_ids, selected_etudid=selected_etudid, klass=klass
|
||||
table.data["filename"] = scu.sanitize_filename(
|
||||
f"""jury-but-{formsemestre.titre_num()}-{time.strftime("%Y-%m-%d")}"""
|
||||
)
|
||||
table_html = table.html()
|
||||
H = [
|
||||
html_sco_header.sco_header(
|
||||
page_title=f"{formsemestre2.sem_modalite()}: jury BUT annuel",
|
||||
page_title=f"{formsemestre.sem_modalite()}: jury BUT",
|
||||
no_side_bar=True,
|
||||
init_qtip=True,
|
||||
javascripts=["js/etud_info.js", "js/table_recap.js"],
|
||||
),
|
||||
sco_formsemestre_status.formsemestre_status_head(
|
||||
formsemestre_id=formsemestre2.id
|
||||
formsemestre_id=formsemestre.id
|
||||
),
|
||||
]
|
||||
if mode == "recap":
|
||||
@ -173,12 +290,12 @@ def formsemestre_saisie_jury_but(
|
||||
<ul>
|
||||
<li><a href="{url_for(
|
||||
"notes.pvjury_table_but",
|
||||
scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre2.id)
|
||||
scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre.id)
|
||||
}" class="stdlink">Tableau PV de jury</a>
|
||||
</li>
|
||||
<li><a href="{url_for(
|
||||
"notes.formsemestre_lettres_individuelles",
|
||||
scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre2.id)
|
||||
scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre.id)
|
||||
}" class="stdlink">Courriers individuels (classeur pdf)</a>
|
||||
</li>
|
||||
</div>
|
||||
@ -187,8 +304,9 @@ def formsemestre_saisie_jury_but(
|
||||
)
|
||||
H.append(
|
||||
f"""
|
||||
|
||||
<div class="table_recap">
|
||||
{table_html}
|
||||
</div>
|
||||
|
||||
<div class="table_jury_but_links">
|
||||
"""
|
||||
@ -199,7 +317,7 @@ def formsemestre_saisie_jury_but(
|
||||
f"""
|
||||
<p><a class="stdlink" href="{url_for(
|
||||
"notes.formsemestre_saisie_jury",
|
||||
scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre2.id)
|
||||
scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre.id)
|
||||
}">Saisie des décisions du jury</a>
|
||||
</p>"""
|
||||
)
|
||||
@ -208,12 +326,12 @@ def formsemestre_saisie_jury_but(
|
||||
f"""
|
||||
<p><a class="stdlink" href="{url_for(
|
||||
"notes.formsemestre_validation_auto_but",
|
||||
scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre2.id)
|
||||
scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre.id)
|
||||
}">Calcul automatique des décisions du jury</a>
|
||||
</p>
|
||||
<p><a class="stdlink" href="{url_for(
|
||||
"notes.formsemestre_jury_but_recap",
|
||||
scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre2.id)
|
||||
scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre.id)
|
||||
}">Tableau récapitulatif des décisions du jury</a>
|
||||
</p>
|
||||
"""
|
||||
@ -224,19 +342,19 @@ def formsemestre_saisie_jury_but(
|
||||
|
||||
<div class="jury_stats">
|
||||
<div>Nb d'étudiants avec décision annuelle:
|
||||
{sum(jury_stats["codes_annuels"].values())} / {jury_stats["nb_etuds"]}
|
||||
{sum(table.freq_codes_annuels.values())} / {len(table)}
|
||||
</div>
|
||||
<div><b>Codes annuels octroyés:</b></div>
|
||||
<table class="jury_stats_codes">
|
||||
"""
|
||||
)
|
||||
for code in sorted(jury_stats["codes_annuels"].keys()):
|
||||
for code in sorted(table.freq_codes_annuels.keys()):
|
||||
H.append(
|
||||
f"""<tr>
|
||||
<td>{code}</td>
|
||||
<td style="text-align:right">{jury_stats["codes_annuels"][code]}</td>
|
||||
<td style="text-align:right">{table.freq_codes_annuels[code]}</td>
|
||||
<td style="text-align:right">{
|
||||
(100*jury_stats["codes_annuels"][code] / jury_stats["nb_etuds"]):2.1f}%
|
||||
(100*table.freq_codes_annuels[code] / len(table)):2.1f}%
|
||||
</td>
|
||||
</tr>"""
|
||||
)
|
||||
@ -346,43 +464,16 @@ class RowCollector:
|
||||
self.column_classes[col_id] = column_class
|
||||
self.idx += 1
|
||||
|
||||
def add_rcue_cells(self, dec_rcue: DecisionsProposeesRCUE):
|
||||
"2 cells: moyenne du RCUE, code enregistré"
|
||||
rcue = dec_rcue.rcue
|
||||
col_id = f"moy_rcue_{rcue.ue_1.niveau_competence_id}" # le niveau_id
|
||||
note_class = ""
|
||||
val = rcue.moy_rcue
|
||||
if isinstance(val, float):
|
||||
if val < BUT_BARRE_RCUE:
|
||||
note_class = " moy_ue_inf"
|
||||
elif val >= BUT_BARRE_RCUE:
|
||||
note_class = " moy_ue_valid"
|
||||
if val < BUT_RCUE_SUFFISANT:
|
||||
note_class = " moy_ue_warning" # notes très basses
|
||||
self.add_cell(
|
||||
col_id,
|
||||
f"<div>{rcue.ue_1.acronyme}</div><div>{rcue.ue_2.acronyme}</div>",
|
||||
self.fmt_note(val),
|
||||
"col_rcue" + note_class,
|
||||
column_class="col_rcue",
|
||||
)
|
||||
self.add_cell(
|
||||
col_id + "_code",
|
||||
f"<div>{rcue.ue_1.acronyme}</div><div>{rcue.ue_2.acronyme}</div>",
|
||||
dec_rcue.code_valide or "",
|
||||
"col_rcue_code recorded_code",
|
||||
column_class="col_rcue",
|
||||
)
|
||||
|
||||
|
||||
def get_jury_but_table(
|
||||
def get_jury_but_table( # XXX A SUPPRIMER apres avoir recupéré les stats
|
||||
formsemestre2: FormSemestre, read_only: bool = False, mode="jury", with_links=True
|
||||
) -> tuple[list[dict], list[str], list[str], dict]:
|
||||
"""Construit la table des résultats annuels pour le jury BUT
|
||||
=> rows_dict, titles, column_ids, jury_stats
|
||||
où jury_stats est un dict donnant des comptages sur le jury.
|
||||
"""
|
||||
res2: ResultatsSemestreBUT = res_sem.load_formsemestre_results(formsemestre2)
|
||||
|
||||
# /////// XXX /////// XXX //////
|
||||
titles = {} # column_id : title
|
||||
jury_stats = {
|
||||
"nb_etuds": len(formsemestre2.etuds_inscriptions),
|
||||
|
@ -9,7 +9,7 @@
|
||||
import pandas as pd
|
||||
|
||||
from app import db
|
||||
from app.models import FormSemestre, ScolarFormSemestreValidation, UniteEns
|
||||
from app.models import FormSemestre, Identite, ScolarFormSemestreValidation, UniteEns
|
||||
from app.comp.res_cache import ResultatsCache
|
||||
from app.scodoc import sco_cache
|
||||
from app.scodoc import sco_codes_parcours
|
||||
@ -53,7 +53,7 @@ class ValidationsSemestre(ResultatsCache):
|
||||
self.comp_decisions_jury()
|
||||
|
||||
def comp_decisions_jury(self):
|
||||
"""Cherche les decisions du jury pour le semestre (pas les UE).
|
||||
"""Cherche les decisions du jury pour le semestre (pas les RCUE).
|
||||
Calcule les attributs:
|
||||
decisions_jury = { etudid : { 'code' : None|ATT|..., 'assidu' : 0|1 }}
|
||||
decision_jury_ues={ etudid :
|
||||
@ -102,6 +102,12 @@ class ValidationsSemestre(ResultatsCache):
|
||||
|
||||
self.decisions_jury_ues = decisions_jury_ues
|
||||
|
||||
def has_decision(self, etud: Identite) -> bool:
|
||||
"""Vrai si etud a au moins une décision enregistrée depuis
|
||||
ce semestre (quelle qu'elle soit)
|
||||
"""
|
||||
return (etud.id in self.decisions_jury_ues) or (etud.id in self.decisions_jury)
|
||||
|
||||
|
||||
def formsemestre_get_ue_capitalisees(formsemestre: FormSemestre) -> pd.DataFrame:
|
||||
"""Liste des UE capitalisées (ADM) utilisables dans ce formsemestre
|
||||
|
@ -4020,6 +4020,11 @@ table.table_recap tbody td:hover {
|
||||
text-decoration: dashed underline;
|
||||
}
|
||||
|
||||
table.table_recap tfoot tr td {
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
/* col moy gen en gras seulement pour les form. classiques */
|
||||
table.table_recap.classic td.col_moy_gen {
|
||||
font-weight: bold;
|
||||
@ -4039,10 +4044,10 @@ table.table_recap .cursus {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
table.table_recap .col_ue,
|
||||
table.table_recap .col_ue_code,
|
||||
table.table_recap .col_moy_gen,
|
||||
table.table_recap .group {
|
||||
table.table_recap td.col_ue,
|
||||
table.table_recap td.col_ue_code,
|
||||
table.table_recap td.col_moy_gen,
|
||||
table.table_recap td.group {
|
||||
border-left: 1px solid blue;
|
||||
}
|
||||
|
||||
@ -4050,7 +4055,7 @@ table.table_recap .col_ue {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
table.table_recap.jury .col_ue {
|
||||
table.table_recap.jury td.col_ue {
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
@ -4059,29 +4064,53 @@ table.table_recap.jury .col_rcue_code {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
table.table_recap.jury tr.even td.col_rcue,
|
||||
table.table_recap.jury tr.even td.col_rcue_code {
|
||||
background-color: #b0d4f8;
|
||||
}
|
||||
|
||||
table.table_recap.jury tr.odd td.col_rcue,
|
||||
table.table_recap.jury tr.odd td.col_rcue_code {
|
||||
background-color: #abcdef;
|
||||
background-color: #e0eeff;
|
||||
}
|
||||
|
||||
table.table_recap.jury tr.odd td.col_rcues_validables {
|
||||
background-color: #e1d3c5 !important;
|
||||
/* table.table_recap.jury tr.even td.col_rcue,
|
||||
table.table_recap.jury tr.even td.col_rcue_code {
|
||||
background-color: #e5f2ff;
|
||||
} */
|
||||
|
||||
/* table.table_recap.jury tr.odd td.col_rcues_validables {
|
||||
background-color: #d5eaff !important;
|
||||
}
|
||||
|
||||
table.table_recap.jury tr.even td.col_rcues_validables {
|
||||
background-color: #fcebda !important;
|
||||
}
|
||||
background-color: #e5f2ff !important;
|
||||
} */
|
||||
|
||||
|
||||
|
||||
table.table_recap .group {
|
||||
border-left: 1px dashed rgb(160, 160, 160);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
table.table_recap thead th {
|
||||
border-left: 1px solid rgb(200, 200, 200);
|
||||
border-right: 1px solid rgb(200, 200, 200);
|
||||
}
|
||||
|
||||
table.table_recap tr.groups_header th {
|
||||
border-bottom: none;
|
||||
font-weight: normal;
|
||||
font-style: italic;
|
||||
text-align: center;
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
|
||||
table.table_recap thead tr.titles th {
|
||||
padding-top: 0px;
|
||||
}
|
||||
|
||||
table.table_recap tr td.col_ue_code,
|
||||
table.table_recap tr th.col_ue_code {
|
||||
border-left: none;
|
||||
}
|
||||
|
||||
table.table_recap .admission {
|
||||
white-space: nowrap;
|
||||
color: rgb(6, 73, 6);
|
||||
@ -4250,7 +4279,7 @@ table.table_recap tr.apo td {
|
||||
max-width: 1px;
|
||||
}
|
||||
|
||||
table.table_recap tr.type_col {
|
||||
table.table_recap tr.type_col td {
|
||||
font-size: 40%;
|
||||
font-family: monospace;
|
||||
overflow-wrap: anywhere;
|
||||
|
@ -10,8 +10,6 @@ $(function () {
|
||||
if (mode_jury_but_bilan) {
|
||||
// table bilan décisions: cache les notes
|
||||
hidden_colums = hidden_colums.concat(["col_ue", "col_rcue", "col_lien_saisie_but"]);
|
||||
} else {
|
||||
hidden_colums = hidden_colums.concat(["recorded_code"]);
|
||||
}
|
||||
// Etat (tri des colonnes) de la table:
|
||||
|
||||
|
@ -54,8 +54,12 @@ class TableRecap(tb.Table):
|
||||
convert_values=False,
|
||||
include_evaluations=False,
|
||||
mode_jury=False,
|
||||
row_class=None,
|
||||
finalize=True,
|
||||
**kwargs,
|
||||
):
|
||||
super().__init__()
|
||||
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
|
||||
@ -72,13 +76,12 @@ class TableRecap(tb.Table):
|
||||
# couples (modimpl, ue) effectivement présents dans la table:
|
||||
self.modimpl_ue_ids = set()
|
||||
|
||||
etuds_inscriptions = res.formsemestre.etuds_inscriptions
|
||||
ues = res.formsemestre.query_ues(with_sport=True) # avec bonus
|
||||
ues_sans_bonus = [ue for ue in ues if ue.type != UE_SPORT]
|
||||
|
||||
for etudid in etuds_inscriptions:
|
||||
for etudid in res.formsemestre.etuds_inscriptions:
|
||||
etud = Identite.query.get(etudid)
|
||||
row = RowRecap(self, etud)
|
||||
row = self.row_class(self, etud)
|
||||
self.add_row(row)
|
||||
row.add_etud_cols()
|
||||
row.add_moyennes_cols(ues_sans_bonus)
|
||||
@ -100,11 +103,14 @@ class TableRecap(tb.Table):
|
||||
if include_evaluations:
|
||||
self.add_evaluations()
|
||||
|
||||
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 style "col_empty" aux colonnes de modules vides"""
|
||||
"""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:
|
||||
@ -275,11 +281,12 @@ class TableRecap(tb.Table):
|
||||
|
||||
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"
|
||||
Les colonnes ont la classe css "partition".
|
||||
"""
|
||||
self.insert_group("partition", after="identite_court")
|
||||
self.group_titles["partition"] = "Partitions"
|
||||
partitions, partitions_etud_groups = sco_groups.get_formsemestre_groups(
|
||||
self.res.formsemestre.id
|
||||
)
|
||||
@ -404,6 +411,7 @@ class TableRecap(tb.Table):
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
evaluation_id=e.id,
|
||||
),
|
||||
target_attrs={"class": "stdlink"},
|
||||
)
|
||||
|
||||
def add_admissions(self):
|
||||
@ -478,6 +486,13 @@ class RowRecap(tb.Row):
|
||||
"""Ajoute colonnes étudiant: codes, noms"""
|
||||
res = self.table.res
|
||||
etud = self.etud
|
||||
self.table.group_titles.update(
|
||||
{
|
||||
"etud_codes": "Codes",
|
||||
"identite_detail": "",
|
||||
"identite_court": "",
|
||||
}
|
||||
)
|
||||
# --- Codes (seront cachés, mais exportés en excel)
|
||||
self.add_cell("etudid", "etudid", etud.id, "etud_codes")
|
||||
self.add_cell(
|
||||
@ -614,35 +629,11 @@ class RowRecap(tb.Row):
|
||||
data={"order": self.nb_ues_validables}, # tri
|
||||
)
|
||||
|
||||
if table.mode_jury and res.validations:
|
||||
if res.is_apc:
|
||||
# formations BUT: pas de code semestre, concatene ceux des UEs
|
||||
dec_ues = res.validations.decisions_jury_ues.get(etud.id)
|
||||
if dec_ues:
|
||||
jury_code_sem = ",".join(
|
||||
[dec_ues[ue_id].get("code", "") for ue_id in dec_ues]
|
||||
)
|
||||
else:
|
||||
jury_code_sem = ""
|
||||
else:
|
||||
# formations classiques: code semestre
|
||||
dec_sem = res.validations.decisions_jury.get(etud.id)
|
||||
jury_code_sem = dec_sem["code"] if dec_sem else ""
|
||||
self.add_cell("jury_code_sem", "Jury", jury_code_sem, "jury_code_sem")
|
||||
self.add_cell(
|
||||
"jury_link",
|
||||
"",
|
||||
f"""<a href="{url_for('notes.formsemestre_validation_etud_form',
|
||||
scodoc_dept=g.scodoc_dept, formsemestre_id=res.formsemestre.id, etudid=etud.id
|
||||
)
|
||||
}">{("saisir" if not jury_code_sem else "modifier")
|
||||
if res.formsemestre.etat else "voir"} décisions</a>""",
|
||||
"col_jury_link",
|
||||
)
|
||||
|
||||
def add_ue_cols(self, ue: UniteEns, ue_status: dict):
|
||||
"Ajoute résultat UE au row (colonne col_ue)"
|
||||
# sous-classé par JuryRow pour ajouter les codes
|
||||
table = self.table
|
||||
table.group_titles["col_ue"] = "UEs du semestre"
|
||||
col_id = f"moy_ue_{ue.id}"
|
||||
val = ue_status["moy"]
|
||||
note_class = ""
|
||||
@ -659,9 +650,9 @@ class RowRecap(tb.Row):
|
||||
col_id,
|
||||
ue.acronyme,
|
||||
table.fmt_note(val),
|
||||
group=f"col_ue_{ue.id}",
|
||||
group="col_ue",
|
||||
classes=[note_class],
|
||||
column_classes={"col_ue", "col_moy_ue"},
|
||||
column_classes={f"col_ue_{ue.id}", "col_moy_ue"},
|
||||
)
|
||||
table.foot_title_row.cells[col_id].target_attrs[
|
||||
"title"
|
||||
|
@ -73,8 +73,10 @@ class Table(Element):
|
||||
classes: list[str] = None,
|
||||
attrs: dict[str, str] = None,
|
||||
data: dict = None,
|
||||
row_class=None,
|
||||
):
|
||||
super().__init__("table", classes=classes, attrs=attrs, data=data)
|
||||
self.row_class = row_class or Row
|
||||
self.rows: list["Row"] = []
|
||||
"ordered list of Rows"
|
||||
self.row_by_id: dict[str, "Row"] = {}
|
||||
@ -82,6 +84,8 @@ class Table(Element):
|
||||
"ordered list of columns ids"
|
||||
self.groups = []
|
||||
"ordered list of column groups names"
|
||||
self.group_titles = {}
|
||||
"title (in header top row) for the group"
|
||||
self.head = []
|
||||
self.foot = []
|
||||
self.column_group = {}
|
||||
@ -92,8 +96,12 @@ class Table(Element):
|
||||
"l'id de la ligne sélectionnée"
|
||||
self.titles = {}
|
||||
"Column title: { col_id : titre }"
|
||||
self.head_title_row: "Row" = Row(self, "title_head", cell_elt="th")
|
||||
self.foot_title_row: "Row" = Row(self, "title_foot", cell_elt="th")
|
||||
self.head_title_row: "Row" = Row(
|
||||
self, "title_head", cell_elt="th", classes=["titles"]
|
||||
)
|
||||
self.foot_title_row: "Row" = Row(
|
||||
self, "title_foot", cell_elt="th", classes=["titles"]
|
||||
)
|
||||
self.empty_cell = Cell.empty()
|
||||
|
||||
def _prepare(self):
|
||||
@ -109,6 +117,10 @@ class Table(Element):
|
||||
"return the row, or None"
|
||||
return self.row_by_id.get(row_id)
|
||||
|
||||
def __len__(self):
|
||||
"nombre de lignes dans le corps de la table"
|
||||
return len(self.rows)
|
||||
|
||||
def is_empty(self) -> bool:
|
||||
"true if table has no rows"
|
||||
return len(self.rows) == 0
|
||||
@ -166,7 +178,6 @@ class Table(Element):
|
||||
|
||||
def add_head_row(self, row: "Row") -> "Row":
|
||||
"Add a row to table head"
|
||||
# row = Row(self, cell_elt="th", category="head")
|
||||
self.head.append(row)
|
||||
self.row_by_id[row.id] = row
|
||||
return row
|
||||
@ -177,6 +188,16 @@ class Table(Element):
|
||||
self.row_by_id[row.id] = row
|
||||
return row
|
||||
|
||||
def add_groups_header(self):
|
||||
"""Insert a header line at the top of the table
|
||||
with a multicolumn th cell per group
|
||||
"""
|
||||
self.sort_columns()
|
||||
groups_header = RowGroupsHeader(
|
||||
self, "groups_header", classes=["groups_header"]
|
||||
)
|
||||
self.head.insert(0, groups_header)
|
||||
|
||||
def sort_rows(self, key: callable, reverse: bool = False):
|
||||
"""Sort table rows"""
|
||||
self.rows.sort(key=key, reverse=reverse)
|
||||
@ -418,3 +439,33 @@ class Cell(Element):
|
||||
return f"<a {href} {target_attrs_str}>{super().html_content()}</a>"
|
||||
|
||||
return super().html_content()
|
||||
|
||||
|
||||
class RowGroupsHeader(Row):
|
||||
"""Header line at the top of the table
|
||||
with a multicolumn th cell per group
|
||||
"""
|
||||
|
||||
def html_content(self):
|
||||
"""Le contenu est généré intégralement ici: un th par groupe contigu
|
||||
Note: les colonnes doivent avoir déjà été triées par table.sort_columns()
|
||||
"""
|
||||
column_ids = self.table.column_ids
|
||||
nb_cols = len(self.table.column_ids)
|
||||
elements = []
|
||||
idx = 0
|
||||
while idx < nb_cols:
|
||||
group_title = self.table.group_titles.get(
|
||||
self.table.column_group.get(column_ids[idx])
|
||||
)
|
||||
colspan = 1
|
||||
idx += 1
|
||||
# on groupe tant que les TITRES des groupes sont identiques
|
||||
while idx < nb_cols and group_title == self.table.group_titles.get(
|
||||
self.table.column_group.get(column_ids[idx])
|
||||
):
|
||||
idx += 1
|
||||
colspan += 1
|
||||
elements.append(f"""<th colspan="{colspan}">{group_title or ""}</th>""")
|
||||
|
||||
return "\n".join(elements) if len(elements) > 1 else ""
|
||||
|
Loading…
Reference in New Issue
Block a user