Dispenses d'UE: corrige affichage en table recap. Intègre aux tests unitaires cursus. Légendes.

This commit is contained in:
Emmanuel Viennet 2023-03-27 23:57:10 +02:00
parent 5103f162a7
commit c889ba3d1d
8 changed files with 112 additions and 15 deletions

View File

@ -260,8 +260,10 @@ class UniteEns(db.Model):
class DispenseUE(db.Model):
"""Dispense d'UE
Utilisé en APC (BUT) pour indiquer les étudiants redoublants avec une UE capitalisée
qu'ils ne refont pas.
Utilisé en APC (BUT) pour indiquer
- les étudiants redoublants avec une UE capitalisée qu'ils ne refont pas.
- les étudiants "non inscrit" à une UE car elle ne fait pas partie de leur Parcours.
La dispense d'UE n'est PAS une validation:
- elle n'est pas affectée par les décisions de jury (pas effacée)
- elle est associée à un formsemestre

View File

@ -613,11 +613,16 @@ def _list_but_ue_inscriptions(res: NotesTableCompat, read_only: bool = True) ->
</table>
</form>
<div class="help">
L'inscription ou désinscription aux UEs du BUT n'affecte pas les inscriptions aux modules
<p>L'inscription ou désinscription aux UEs du BUT n'affecte pas les inscriptions aux modules
mais permet de "dispenser" un étudiant de suivre certaines UEs de son parcours.
Il peut s'agit d'étudiants redoublants ayant déjà acquis l'UE, ou d'autres cas particuliers.
La dispense d'UE est réversible à tout moment (avant le jury de fin de semestre)
</p>
<p>Il peut s'agit d'étudiants redoublants ayant déjà acquis l'UE, ou d'une UE
présente dans le semestre mais pas dans le parcours de l'étudiant, ou bien d'autres
cas particuliers.
</p>
<p>La dispense d'UE est réversible à tout moment (avant le jury de fin de semestre)
et n'affecte pas les notes saisies.
</p>
</div>
</div>
"""

View File

@ -242,7 +242,19 @@ def formsemestre_recapcomplet(
</div>
"""
)
# Légende
H.append(
"""
<div class="table_recap_caption">
<div class="title">Codes utilisés dans cette table:</div>
<div class="captions">
<div><tt>~</tt></div><div>valeur manquante</div>
<div><tt>=</tt></div><div>UE dispensée</div>
<div><tt>nan</tt></div><div>valeur non disponible</div>
</div>
</div>
"""
)
H.append(html_sco_header.sco_footer())
# HTML or binary data ?
if len(H) > 1:

View File

@ -2672,6 +2672,30 @@ table.notes_recapcomplet a:hover {
text-decoration: underline;
}
div.table_recap_caption {
width: fit-content;
padding: 8px;
border-radius: 8px;
background-color: rgb(202, 255, 180);
}
div.table_recap_caption div.title {
font-weight: bold;
}
div.table_recap_caption div.captions {
display: grid;
grid-template-columns: 48px 200px;
}
div.table_recap_caption div.captions div:nth-child(odd) {
text-align: center;
}
div.table_recap_caption div.captions div:nth-child(even) {
font-style: italic;
}
/* bulletin */
div.notes_bulletin {
margin-right: 5px;

View File

@ -154,7 +154,7 @@ class TableJury(TableRecap):
niveau: ApcNiveau = validation_rcue.niveau()
titre = f"C{niveau.competence.numero}" # à voir (nommer les compétences...)
row.add_cell(
f"c_{competence_id}_annee",
f"c_{competence_id}_{annee}",
titre,
validation_rcue.code,
group="cursus_" + annee,

View File

@ -285,9 +285,9 @@ class TableRecap(tb.Table):
notes = res.modimpl_notes(modimpl.id, ue.id)
if np.isnan(notes).all():
# aucune note valide
row_min.add_cell(col_id, None, np.nan)
row_max.add_cell(col_id, None, np.nan)
moy = np.nan
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)))
@ -297,7 +297,7 @@ class TableRecap(tb.Table):
None,
self.fmt_note(moy),
# aucune note dans ce module ?
classes=["col_empty" if np.isnan(moy) else ""],
classes=["col_empty" if (moy == "" or np.isnan(moy)) else ""],
)
row_apo.add_cell(col_id, None, modimpl.module.code_apogee or "")
@ -618,7 +618,7 @@ class RowRecap(tb.Row):
):
"""Ajoute cols moy_gen moy_ue et tous les modules..."""
etud = self.etud
table = self.table
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:
@ -701,13 +701,17 @@ class RowRecap(tb.Row):
def add_ue_cols(self, ue: UniteEns, ue_status: dict, col_group: str = None):
"Ajoute résultat UE au row (colonne col_ue)"
# sous-classé par JuryRow pour ajouter les codes
table = self.table
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"]
val = (
ue_status["moy"]
if (self.etud.id, ue.id) not in table.res.dispense_ues
else "="
)
note_classes = []
if isinstance(val, float):
if val < table.barre_moy:

View File

@ -134,7 +134,7 @@ FormSemestres:
codes_parcours: ['BAT', 'TP']
Etudiants:
A_ok: # Etudiant qui va tout valider directement
A_ok: # Etudiant parcours BAT qui va tout valider directement
prenom: Étudiant_BAT
civilite: M
formsemestres:
@ -157,9 +157,41 @@ Etudiants:
S5:
parcours: BAT
dispense_ues: ['UE5.2']
notes_modules:
"R5.01": 15 # toutes UE
"SAÉ 5.BAT.01": 10 # UE5.1
"SAÉ 5.BAT.02": 11 # UE5.4
S6:
parcours: BAT
B_ok: # Etudiant parcours TP qui va tout valider directement
prenom: Étudiant_TP
civilite: M
formsemestres:
S1:
parcours: TP
notes_modules:
"R1.01": 11 # toutes UEs
S2:
parcours: TP
notes_modules:
"R2.01": 12 # toutes UEs
S3:
parcours: TP
notes_modules:
"R3.01": 13 # toutes UEs
S4:
parcours: TP
notes_modules:
"R4.01": 14 # toutes UE
S5:
parcours: TP
dispense_ues: ['UE5.1']
notes_modules:
"R5.01": 15 # toutes UE
"SAÉ 5.BAT.01": 10 # UE5.1
"SAÉ 5.BAT.02": 11 # UE5.4
S6:
parcours: TP

View File

@ -53,12 +53,14 @@ from app.auth.models import User
from app.models import (
ApcParcours,
DispenseUE,
Evaluation,
Formation,
FormSemestre,
Identite,
Module,
ModuleImpl,
UniteEns,
)
from app.scodoc import sco_formations
@ -263,6 +265,9 @@ def inscrit_les_etudiants(formation: Formation, doc: dict):
group_ids = [group.id]
else:
group_ids = []
# Génère des dispenses d'UEs
if "dispense_ues" in sem_infos:
etud_dispense_ues(formsemestre, etud, sem_infos["dispense_ues"])
sco_formsemestre_inscriptions.do_formsemestre_inscription_with_modules(
formsemestre.id,
etud.id,
@ -275,6 +280,19 @@ def inscrit_les_etudiants(formation: Formation, doc: dict):
formsemestre.update_inscriptions_parcours_from_groups()
def etud_dispense_ues(
formsemestre: FormSemestre, etud: Identite, ue_acronymes: list[str]
):
"""Génère des dispenses d'UE"""
for ue_acronyme in set(ue_acronymes):
ue: UniteEns = formsemestre.formation.ues.filter_by(
acronyme=ue_acronyme
).first()
assert ue
disp = DispenseUE(formsemestre_id=formsemestre.id, ue_id=ue.id, etudid=etud.id)
db.session.add(disp)
def setup_from_yaml(filename: str) -> dict:
"""Lit le fichier yaml et construit l'ensemble des objets"""
with open(filename, encoding="utf-8") as f: