Refactoring + nouvelle table recap pour classiques (WIP)

This commit is contained in:
Emmanuel Viennet 2022-03-27 22:25:00 +02:00
parent 13d4da3fea
commit 9596c8cf6f
36 changed files with 343 additions and 743 deletions

View File

@ -17,7 +17,7 @@ from app import models
def df_load_modimpl_inscr(formsemestre) -> pd.DataFrame:
"""Charge la matrice des inscriptions aux modules du semestre
rows: etudid (inscrits au semestre, avec DEM et DEF)
columns: moduleimpl_id (en chaîne)
columns: moduleimpl_id
value: bool (0/1 inscrit ou pas)
"""
# méthode la moins lente: une requete par module, merge les dataframes

View File

@ -10,21 +10,15 @@ import time
import numpy as np
import pandas as pd
from flask import g, url_for
from app import log
from app.comp import moy_ue, moy_sem, inscr_mod
from app.comp.res_common import NotesTableCompat
from app.comp.res_compat import NotesTableCompat
from app.comp.bonus_spo import BonusSport
from app.models import ScoDocSiteConfig
from app.models.etudiants import Identite
from app.models.moduleimpls import ModuleImpl
from app.models.ues import UniteEns
from app.scodoc import sco_codes_parcours
from app.scodoc.sco_codes_parcours import UE_SPORT
from app.scodoc import sco_groups
from app.scodoc import sco_preferences
from app.scodoc import sco_users
import app.scodoc.sco_utils as scu
@ -39,7 +33,6 @@ class ResultatsSemestreBUT(NotesTableCompat):
def __init__(self, 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)"""
@ -174,265 +167,11 @@ class ResultatsSemestreBUT(NotesTableCompat):
]
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 modimpl_notes(self, modimpl_id: int, ue_id: int) -> np.ndarray:
"""Les notes moyennes des étudiants du sem. à ce modimpl dans cette ue.
Utile pour stats bottom tableau recap.
Résultat: 1d array of float
"""
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
# 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 = ""
):
"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")
row["_nom_short_target"] = url_for(
"notes.formsemestre_bulletinetud",
scodoc_dept=g.scodoc_dept,
formsemestre_id=self.formsemestre.id,
etudid=etudid,
)
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"]
self._recap_etud_groups_infos(etudid, row, titles)
# --- 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,
)
titles_bot["_moy_gen_target_attrs"] = (
'title="moyenne indicative"' if self.is_apc else ""
)
# --- 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,
)
titles_bot[
f"_{col_id}_target_attrs"
] = f"""title="{ue.titre} S{ue.semestre_idx or '?'}" """
# 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}",
)
titles_bot[f"_{col_id}_target"] = url_for(
"notes.moduleimpl_status",
scodoc_dept=g.scodoc_dept,
moduleimpl_id=modimpl.id,
)
titles_bot[
f"_{col_id}_target_attrs"
] = f"""
title="{modimpl.module.titre}
({sco_users.user_info(modimpl.responsable_id)['nomcomplet']})" """
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._recap_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["prenom"] = row["nom_short"] = bottom_line.capitalize()
row["_tr_class"] = bottom_line.lower()
footer_rows.append(row)
titles_bot.update(titles)
footer_rows.append(titles_bot)
return (
rows,
footer_rows,
titles,
[title for title in titles if not title.startswith("_")],
)
def _recap_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
def _recap_etud_groups_infos(self, etudid: int, row: dict, titles: dict):
"""Ajoute à row les colonnes sur les groupes pour etud"""
# XXX à remonter dans res_common
# dec = self.get_etud_decision_sem(etudid)
# if dec:
# codes_nb[dec["code"]] += 1
row_class = ""
etud_etat = self.get_etud_etat(etudid)
if etud_etat == sco_codes_parcours.DEM:
gr_name = "Dém."
row_class = "dem"
elif etud_etat == sco_codes_parcours.DEF:
gr_name = "Déf."
row_class = "def"
else:
# XXX probablement à revoir pour utiliser données cachées,
# via get_etud_groups_in_partition ou autre
group = sco_groups.get_etud_main_group(etudid, self.formsemestre.id)
gr_name = group["group_name"] or ""
row["group"] = gr_name
row["_group_class"] = "group"
if row_class:
row["_tr_class"] = " ".join([row.get("_tr_class", ""), row_class])
titles["group"] = "Gr"
i = self.modimpl_coefs_df.columns.get_loc(modimpl_id)
j = self.modimpl_coefs_df.index.get_loc(ue_id)
return self.sem_cube[:, i, j]

View File

@ -16,7 +16,7 @@ from flask import g, url_for
from app import db
from app import log
from app.comp import moy_mat, moy_mod, moy_sem, moy_ue, inscr_mod
from app.comp.res_common import NotesTableCompat
from app.comp.res_compat import NotesTableCompat
from app.comp.bonus_spo import BonusSport
from app.models import ScoDocSiteConfig
from app.models.etudiants import Identite
@ -40,6 +40,8 @@ class ResultatsSemestreClassic(NotesTableCompat):
def __init__(self, formsemestre):
super().__init__(formsemestre)
self.sem_matrix: np.ndarray = None
"sem_matrix : 2d-array (etuds x modimpls)"
if not self.load_cached():
t0 = time.time()
@ -189,6 +191,19 @@ class ResultatsSemestreClassic(NotesTableCompat):
),
}
def modimpl_notes(
self,
modimpl_id: int,
ue_id: int = None,
) -> np.ndarray:
"""Les notes moyennes des étudiants du sem. à ce modimpl dans cette ue.
Utile pour stats bottom tableau recap.
ue_id n'est pas utilisé ici (formations classiques)
Résultat: 1d array of float
"""
i = self.modimpl_idx[modimpl_id]
return self.sem_matrix[:, i]
def compute_moyennes_matieres(self):
"""Calcul les moyennes par matière. Doit être appelée au besoin, en fin de compute."""
self.moyennes_matieres = moy_mat.compute_mat_moys_classic(

View File

@ -9,11 +9,8 @@ from functools import cached_property
import numpy as np
import pandas as pd
from flask import g, flash, url_for
from flask import g, url_for
from app import log
from app.comp.aux_stats import StatsMoyenne
from app.comp import moy_sem
from app.comp.res_cache import ResultatsCache
from app.comp import res_sem
from app.comp.moy_mod import ModuleImplResults
@ -21,10 +18,13 @@ from app.models import FormSemestre, FormSemestreUECoef
from app.models import Identite
from app.models import ModuleImpl, ModuleImplInscription
from app.models.ues import UniteEns
from app.scodoc import sco_utils as scu
from app.scodoc.sco_cache import ResultatsSemestreCache
from app.scodoc.sco_codes_parcours import UE_SPORT, DEF
from app.scodoc.sco_codes_parcours import UE_SPORT, DEF, DEM
from app.scodoc.sco_exceptions import ScoValueError
from app.scodoc import sco_groups
from app.scodoc import sco_users
from app.scodoc import sco_utils as scu
# Il faut bien distinguer
# - ce qui est caché de façon persistente (via redis):
@ -36,6 +36,10 @@ from app.scodoc.sco_exceptions import ScoValueError
# qui sont notamment les attributs décorés par `@cached_property``
#
class ResultatsSemestre(ResultatsCache):
"""Les résultats (notes, ...) d'un formsemestre
Classe commune à toutes les formations (classiques, BUT)
"""
_cached_attrs = (
"etud_moy_gen_ranks",
"etud_moy_gen",
@ -54,13 +58,18 @@ class ResultatsSemestre(ResultatsCache):
# ResultatsSemestreBUT ou ResultatsSemestreClassic
self.etud_moy_ue = {}
"etud_moy_ue: DataFrame columns UE, rows etudid"
self.etud_moy_gen = {}
self.etud_moy_gen: pd.Series = None
self.etud_moy_gen_ranks = {}
self.etud_moy_gen_ranks_int = {}
self.modimpl_inscr_df: pd.DataFrame = None
"Inscriptions: row etudid, col modimlpl_id"
self.modimpls_results: ModuleImplResults = None
"Résultats de chaque modimpl: dict { modimpl.id : ModuleImplResults(Classique ou BUT) }"
self.etud_coef_ue_df = None
"""coefs d'UE effectifs pour chaque étudiant (pour form. classiques)"""
self.modimpl_coefs_df: pd.DataFrame = None
"""Coefs APC: rows = UEs (sans bonus), columns = modimpl, value = coef."""
self.validations = None
self.moyennes_matieres = {}
"""Moyennes de matières, si calculées. { matiere_id : Series, index etudid }"""
@ -151,17 +160,24 @@ class ResultatsSemestre(ResultatsCache):
ues = sorted(list(ues), key=lambda x: x.numero or 0)
return ues
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 de cette UE auxquels l'étudiant est inscrit.
Utile en formations classiques, surchargée pour le BUT.
Inclus modules bonus le cas échéant.
"""
# sert pour l'affichage ou non de l'UE sur le bulletin
return [
# Méthode surchargée en BUT
modimpls = [
modimpl
for modimpl in self.formsemestre.modimpls_sorted
if modimpl.module.ue.id == ue_id
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
@cached_property
def ue_au_dessus(self, seuil=10.0) -> pd.DataFrame:
@ -341,437 +357,275 @@ class ResultatsSemestre(ResultatsCache):
# somme des coefs des modules de l'UE auxquels il est inscrit
return self.compute_etud_ue_coef(etudid, ue)
# --- TABLEAU RECAP
# Pour raccorder le code des anciens codes qui attendent une NoteTable
class NotesTableCompat(ResultatsSemestre):
"""Implementation partielle de NotesTable WIP TODO
def get_table_recap(self, convert_values=False):
"""Result: tuple avec
- rows: liste de dicts { column_id : value }
- titles: { column_id : title }
- columns_ids: (liste des id de colonnes)
Les méthodes définies dans cette classe sont
pour conserver la compatibilité abvec les codes anciens et
il n'est pas recommandé de les utiliser dans de nouveaux
développements (API malcommode et peu efficace).
"""
. 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
_cached_attrs = ResultatsSemestre._cached_attrs + (
"bonus",
"bonus_ues",
"malus",
"etud_moy_gen_ranks",
"etud_moy_gen_ranks_int",
"ue_rangs",
)
def __init__(self, formsemestre: FormSemestre):
super().__init__(formsemestre)
nb_etuds = len(self.etuds)
self.bonus = None # virtuel
self.bonus_ues = None # virtuel
self.ue_rangs = {u.id: (None, nb_etuds) for u in self.ues}
self.mod_rangs = None # sera surchargé en Classic, mais pas en APC
"""{ modimpl_id : (rangs, effectif) }"""
self.moy_min = "NA"
self.moy_max = "NA"
self.moy_moy = "NA"
self.expr_diagnostics = ""
self.parcours = self.formsemestre.formation.get_parcours()
def get_inscrits(self, include_demdef=True, order_by=False) -> list[Identite]:
"""Liste des étudiants inscrits
order_by = False|'nom'|'moy' tri sur nom ou sur moyenne générale (indicative)
Note: pour récupérer les etudids des inscrits, non triés, il est plus efficace
d'utiliser `[ ins.etudid for ins in nt.formsemestre.inscriptions ]`
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
"""
etuds = self.formsemestre.get_inscrits(
include_demdef=include_demdef, order=(order_by == "nom")
)
if order_by == "moy":
etuds.sort(
key=lambda e: (
self.etud_moy_gen_ranks_int.get(e.id, 100000),
e.sort_key,
)
)
return etuds
def get_etudids(self) -> list[int]:
"""(deprecated)
Liste des etudids inscrits, incluant les démissionnaires.
triée par ordre alphabetique de NOM
(à éviter: renvoie les etudids, mais est moins efficace que get_inscrits)
"""
# Note: pour avoir les inscrits non triés,
# utiliser [ ins.etudid for ins in self.formsemestre.inscriptions ]
return [x["etudid"] for x in self.inscrlist]
@cached_property
def sem(self) -> dict:
"""le formsemestre, comme un gros et gras dict (nt.sem)"""
return self.formsemestre.get_infos_dict()
@cached_property
def inscrlist(self) -> list[dict]: # utilisé par PE
"""Liste des inscrits au semestre (avec DEM et DEF),
sous forme de dict etud,
classée dans l'ordre alphabétique de noms.
"""
etuds = self.formsemestre.get_inscrits(include_demdef=True, order=True)
return [e.to_dict_scodoc7() for e in etuds]
@cached_property
def stats_moy_gen(self):
"""Stats (moy/min/max) sur la moyenne générale"""
return StatsMoyenne(self.etud_moy_gen)
def get_ues_stat_dict(
self, filter_sport=False, check_apc_ects=True
) -> list[dict]: # was get_ues()
"""Liste des UEs, ordonnée par numero.
Si filter_sport, retire les UE de type SPORT.
Résultat: liste de dicts { champs UE U stats moyenne UE }
"""
ues = self.formsemestre.query_ues(with_sport=not filter_sport)
ues_dict = []
for ue in ues:
d = ue.to_dict()
if ue.type != UE_SPORT:
moys = self.etud_moy_ue[ue.id]
else:
moys = None
d.update(StatsMoyenne(moys).to_dict())
ues_dict.append(d)
if check_apc_ects and self.is_apc and not hasattr(g, "checked_apc_ects"):
g.checked_apc_ects = True
if None in [ue.ects for ue in ues if ue.type != UE_SPORT]:
flash(
"""Calcul moyenne générale impossible: ECTS des UE manquants !""",
category="danger",
)
return ues_dict
def get_modimpls_dict(self, ue_id=None) -> list[dict]:
"""Liste des modules pour une UE (ou toutes si ue_id==None),
triés par numéros (selon le type de formation)
"""
modimpls_dict = []
for modimpl in self.formsemestre.modimpls_sorted:
if ue_id == None or modimpl.module.ue.id == ue_id:
d = modimpl.to_dict()
# compat ScoDoc < 9.2: ajoute matières
d["mat"] = modimpl.module.matiere.to_dict()
modimpls_dict.append(d)
return modimpls_dict
def compute_rangs(self):
"""Calcule les classements
Moyenne générale: etud_moy_gen_ranks
Par UE (sauf ue bonus)
"""
(
self.etud_moy_gen_ranks,
self.etud_moy_gen_ranks_int,
) = moy_sem.comp_ranks_series(self.etud_moy_gen)
for ue in self.formsemestre.query_ues():
moy_ue = self.etud_moy_ue[ue.id]
self.ue_rangs[ue.id] = (
moy_sem.comp_ranks_series(moy_ue)[0], # juste en chaine
int(moy_ue.count()),
)
# .count() -> nb of non NaN values
def get_etud_ue_rang(self, ue_id, etudid) -> tuple[str, int]:
"""Le rang de l'étudiant dans cette ue
Result: rang:str, effectif:str
"""
rangs, effectif = self.ue_rangs[ue_id]
if rangs is not None:
rang = rangs[etudid]
if convert_values:
fmt_note = scu.fmt_note
else:
return "", ""
return rang, effectif
fmt_note = lambda x: x
def etud_check_conditions_ues(self, etudid):
"""Vrai si les conditions sur les UE sont remplies.
Ne considère que les UE ayant des notes (moyenne calculée).
(les UE sans notes ne sont pas comptées comme sous la barre)
Prend en compte les éventuelles UE capitalisées.
Pour les parcours habituels, cela revient à vérifier que
les moyennes d'UE sont toutes > à leur barre (sauf celles sans notes)
Pour les parcours non standards (LP2014), cela peut être plus compliqué.
Return: True|False, message explicatif
"""
ue_status_list = []
for ue in self.formsemestre.query_ues():
ue_status = self.get_etud_ue_status(etudid, ue.id)
if ue_status:
ue_status_list.append(ue_status)
return self.parcours.check_barre_ues(ue_status_list)
def all_etuds_have_sem_decisions(self):
"""True si tous les étudiants du semestre ont une décision de jury.
Ne regarde pas les décisions d'UE.
"""
for ins in self.formsemestre.inscriptions:
if ins.etat != scu.INSCRIT:
continue # skip démissionnaires
if self.get_etud_decision_sem(ins.etudid) is None:
return False
return True
def etud_has_decision(self, etudid):
"""True s'il y a une décision de jury pour cet étudiant"""
return self.get_etud_decision_ues(etudid) or self.get_etud_decision_sem(etudid)
def get_etud_decision_ues(self, etudid: int) -> dict:
"""Decisions du jury pour les UE de cet etudiant, ou None s'il n'y en pas eu.
Ne tient pas compte des UE capitalisées.
{ ue_id : { 'code' : ADM|CMP|AJ, 'event_date' : }
Ne renvoie aucune decision d'UE pour les défaillants
"""
if self.get_etud_etat(etudid) == DEF:
return {}
else:
if not self.validations:
self.validations = res_sem.load_formsemestre_validations(
self.formsemestre
)
return self.validations.decisions_jury_ues.get(etudid, None)
def get_etud_decision_sem(self, etudid: int) -> dict:
"""Decision du jury prise pour cet etudiant, ou None s'il n'y en pas eu.
{ 'code' : None|ATT|..., 'assidu' : 0|1, 'event_date' : , compense_formsemestre_id }
Si état défaillant, force le code a DEF
"""
if self.get_etud_etat(etudid) == DEF:
return {
"code": DEF,
"assidu": False,
"event_date": "",
"compense_formsemestre_id": None,
}
else:
if not self.validations:
self.validations = res_sem.load_formsemestre_validations(
self.formsemestre
)
return self.validations.decisions_jury.get(etudid, None)
def get_etud_etat(self, etudid: int) -> str:
"Etat de l'etudiant: 'I', 'D', DEF ou '' (si pas connu dans ce semestre)"
ins = self.formsemestre.etuds_inscriptions.get(etudid, None)
if ins is None:
return ""
return ins.etat
def get_etud_mat_moy(self, matiere_id: int, etudid: int) -> str:
"""moyenne d'un étudiant dans une matière (ou NA si pas de notes)"""
if not self.moyennes_matieres:
return "nd"
return (
self.moyennes_matieres[matiere_id].get(etudid, "-")
if matiere_id in self.moyennes_matieres
else "-"
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
# les titres en footer: les mêmes, mais avec des bulles et liens:
titles_bot = {}
def get_etud_mod_moy(self, moduleimpl_id: int, etudid: int) -> float:
"""La moyenne de l'étudiant dans le moduleimpl
En APC, il s'agira d'une moyenne indicative sans valeur.
Result: valeur float (peut être naN) ou chaîne "NI" (non inscrit ou DEM)
"""
raise NotImplementedError() # virtual method
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
def get_etud_moy_gen(self, etudid): # -> float | str
"""Moyenne générale de cet etudiant dans ce semestre.
Prend en compte les UE capitalisées.
Si apc, moyenne indicative.
Si pas de notes: 'NA'
"""
return self.etud_moy_gen[etudid]
def get_etud_ects_pot(self, etudid: int) -> dict:
"""
Un dict avec les champs
ects_pot : (float) nb de crédits ECTS qui seraient validés (sous réserve de validation par le jury),
ects_pot_fond: (float) nb d'ECTS issus d'UE fondamentales (non électives)
Ce sont les ECTS des UE au dessus de la barre (10/20 en principe), avant le jury (donc non
encore enregistrées).
"""
# was nt.get_etud_moy_infos
# XXX pour compat nt, à remplacer ultérieurement
ues = self.get_etud_ue_validables(etudid)
ects_pot = 0.0
for ue in ues:
if (
ue.id in self.etud_moy_ue
and ue.ects is not None
and self.etud_moy_ue[ue.id][etudid] > self.parcours.NOTES_BARRE_VALID_UE
):
ects_pot += ue.ects
return {
"ects_pot": ects_pot,
"ects_pot_fond": 0.0, # not implemented (anciennemment pour école ingé)
}
def get_etud_rang(self, etudid: int):
return self.etud_moy_gen_ranks.get(etudid, 99999) # XXX
def get_etud_rang_group(self, etudid: int, group_id: int):
return (None, 0) # XXX unimplemented TODO
def get_evals_in_mod(self, moduleimpl_id: int) -> list[dict]:
"""Liste d'informations (compat NotesTable) sur évaluations completes
de ce module.
Évaluation "complete" ssi toutes notes saisies ou en attente.
"""
modimpl = ModuleImpl.query.get(moduleimpl_id)
modimpl_results = self.modimpls_results.get(moduleimpl_id)
if not modimpl_results:
return [] # safeguard
evals_results = []
for e in modimpl.evaluations:
if modimpl_results.evaluations_completes_dict.get(e.id, False):
d = e.to_dict()
d["heure_debut"] = e.heure_debut # datetime.time
d["heure_fin"] = e.heure_fin
d["jour"] = e.jour # datetime
d["notes"] = {
etud.id: {
"etudid": etud.id,
"value": modimpl_results.evals_notes[e.id][etud.id],
}
for etud in self.etuds
}
d["etat"] = {
"evalattente": modimpl_results.evaluations_etat[e.id].nb_attente,
}
evals_results.append(d)
elif e.id not in modimpl_results.evaluations_completes_dict:
# ne devrait pas arriver ? XXX
log(
f"Warning: 220213 get_evals_in_mod {e.id} not in mod {moduleimpl_id} ?"
)
return evals_results
def get_evaluations_etats(self):
"""[ {...evaluation et son etat...} ]"""
# TODO: à moderniser
from app.scodoc import sco_evaluations
if not hasattr(self, "_evaluations_etats"):
self._evaluations_etats = sco_evaluations.do_evaluation_list_in_sem(
self.formsemestre.id
)
return self._evaluations_etats
def get_mod_evaluation_etat_list(self, moduleimpl_id) -> list[dict]:
"""Liste des états des évaluations de ce module"""
# XXX TODO à moderniser: lent, recharge des donénes que l'on a déjà...
return [
e
for e in self.get_evaluations_etats()
if e["moduleimpl_id"] == moduleimpl_id
]
def get_moduleimpls_attente(self):
"""Liste des modimpls du semestre ayant des notes en attente"""
return [
modimpl
for modimpl in self.formsemestre.modimpls_sorted
if self.modimpls_results[modimpl.id].en_attente
]
def get_mod_stats(self, moduleimpl_id: int) -> dict:
"""Stats sur les notes obtenues dans un modimpl
Vide en APC
"""
return {
"moy": "-",
"max": "-",
"min": "-",
"nb_notes": "-",
"nb_missing": "-",
"nb_valid_evals": "-",
}
def get_nom_short(self, etudid):
"formatte nom d'un etud (pour table recap)"
etud = self.identdict[etudid]
return (
(etud["nom_usuel"] or etud["nom"]).upper()
+ " "
+ etud["prenom"].capitalize()[:2]
+ "."
)
@cached_property
def T(self):
return self.get_table_moyennes_triees()
def get_table_moyennes_triees(self) -> list:
"""Result: liste de tuples
moy_gen, moy_ue_0, ..., moy_ue_n, moy_mod1, ..., moy_mod_n, etudid
"""
table_moyennes = []
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")
row["_nom_short_target"] = url_for(
"notes.formsemestre_bulletinetud",
scodoc_dept=g.scodoc_dept,
formsemestre_id=self.formsemestre.id,
etudid=etudid,
)
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"]
self._recap_etud_groups_infos(etudid, row, titles)
# --- Moyenne générale
moy_gen = self.etud_moy_gen.get(etudid, False)
note_class = ""
if moy_gen is False:
# pas de moyenne: démissionnaire ou def
t = (
["-"]
+ ["0.00"] * len(self.ues)
+ ["NI"] * len(self.formsemestre.modimpls_sorted)
)
else:
moy_ues = []
ue_is_cap = {}
for ue in ues:
ue_status = self.get_etud_ue_status(etudid, ue.id)
if ue_status:
moy_ues.append(ue_status["moy"])
ue_is_cap[ue.id] = ue_status["is_capitalized"]
else:
moy_ues.append("?")
t = [moy_gen] + list(moy_ues)
# Moyennes modules:
for modimpl in self.formsemestre.modimpls_sorted:
if ue_is_cap.get(modimpl.module.ue.id, False):
val = "-c-"
else:
val = self.get_etud_mod_moy(modimpl.id, etudid)
t.append(val)
t.append(etudid)
table_moyennes.append(t)
# tri par moyennes décroissantes,
# en laissant les démissionnaires à la fin, par ordre alphabetique
etuds = [ins.etud for ins in etuds_inscriptions.values()]
etuds.sort(key=lambda e: e.sort_key)
self._rang_alpha = {e.id: i for i, e in enumerate(etuds)}
table_moyennes.sort(key=self._row_key)
return table_moyennes
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,
)
titles_bot["_moy_gen_target_attrs"] = (
'title="moyenne indicative"' if self.is_apc else ""
)
# --- 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,
)
titles_bot[
f"_{col_id}_target_attrs"
] = f"""title="{ue.titre} S{ue.semestre_idx or '?'}" """
# 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"]:
val = "-c-"
else:
modimpl_results = self.modimpls_results.get(modimpl.id)
if modimpl_results: # pas bonus
if self.is_apc: # BUT
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: # classique: Series indépendante de l'UE
val = modimpl_results.etuds_moy_module.get(
etudid, "?"
)
else:
val = ""
def _row_key(self, x):
"""clé de tri par moyennes décroissantes,
en laissant les demissionnaires à la fin, par ordre alphabetique.
(moy_gen, rang_alpha)
"""
try:
moy = -float(x[0])
except (ValueError, TypeError):
moy = 1000.0
return (moy, self._rang_alpha[x[-1]])
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}",
)
titles_bot[f"_{col_id}_target"] = url_for(
"notes.moduleimpl_status",
scodoc_dept=g.scodoc_dept,
moduleimpl_id=modimpl.id,
)
titles_bot[
f"_{col_id}_target_attrs"
] = f"""
title="{modimpl.module.titre}
({sco_users.user_info(modimpl.responsable_id)['nomcomplet']})" """
modimpl_ids.add(modimpl.id)
@cached_property
def identdict(self) -> dict:
"""{ etudid : etud_dict } pour tous les inscrits au semestre"""
return {
ins.etud.id: ins.etud.to_dict_scodoc7()
for ins in self.formsemestre.inscriptions
rows.append(row)
# tri par rang croissant
rows.sort(key=lambda e: e["_rang_order"])
# INFOS POUR FOOTER
bottom_infos = self._recap_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["prenom"] = row["nom_short"] = bottom_line.capitalize()
row["_tr_class"] = bottom_line.lower()
footer_rows.append(row)
titles_bot.update(titles)
footer_rows.append(titles_bot)
return (
rows,
footer_rows,
titles,
[title for title in titles if not title.startswith("_")],
)
def _recap_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}"
if self.is_apc:
coef = self.modimpl_coefs_df[modimpl.id][ue.id] * (
modimpl.module.coefficient or 0.0
)
else:
coef = modimpl.module.coefficient or 0
bottom_infos["coef"][col_id] = fmt_note(coef)
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))
bottom_infos["min"] = row_min
bottom_infos["max"] = row_max
bottom_infos["moy"] = row_moy
return bottom_infos
def _recap_etud_groups_infos(self, etudid: int, row: dict, titles: dict):
"""Table recap: ajoute à row les colonnes sur les groupes pour cet etud"""
# dec = self.get_etud_decision_sem(etudid)
# if dec:
# codes_nb[dec["code"]] += 1
row_class = ""
etud_etat = self.get_etud_etat(etudid) # dans NotesTableCompat, à revoir
if etud_etat == DEM:
gr_name = "Dém."
row_class = "dem"
elif etud_etat == DEF:
gr_name = "Déf."
row_class = "def"
else:
# XXX probablement à revoir pour utiliser données cachées,
# via get_etud_groups_in_partition ou autre
group = sco_groups.get_etud_main_group(etudid, self.formsemestre.id)
gr_name = group["group_name"] or ""
row["group"] = gr_name
row["_group_class"] = "group"
if row_class:
row["_tr_class"] = " ".join([row.get("_tr_class", ""), row_class])
titles["group"] = "Gr"

View File

@ -47,7 +47,7 @@ import os
from zipfile import ZipFile
from app.comp import res_sem
from app.comp.res_common import NotesTableCompat
from app.comp.res_compat import NotesTableCompat
from app.models import FormSemestre
from app.scodoc.gen_tables import GenTable, SeqGenTable

View File

@ -38,11 +38,10 @@ Created on Fri Sep 9 09:15:05 2016
from app import log
from app.comp import res_sem
from app.comp.res_common import NotesTableCompat
from app.comp.res_compat import NotesTableCompat
from app.models import FormSemestre
from app.models.moduleimpls import ModuleImpl
from app.models.ues import UniteEns
from app.scodoc import sco_codes_parcours
from app.scodoc import sco_tag_module
from app.pe import pe_tagtable

View File

@ -34,7 +34,7 @@ from flask import url_for, g, request, abort
from app import log
from app.comp import res_sem
from app.comp.res_common import NotesTableCompat
from app.comp.res_compat import NotesTableCompat
from app.models import Identite, FormSemestre
import app.scodoc.sco_utils as scu
from app.scodoc import notesdb as ndb

View File

@ -97,7 +97,7 @@ from chardet import detect as chardet_detect
from app import log
from app.comp import res_sem
from app.comp.res_common import NotesTableCompat
from app.comp.res_compat import NotesTableCompat
from app.models import FormSemestre, Identite
from app.models.config import ScoDocSiteConfig
import app.scodoc.sco_utils as scu

View File

@ -39,7 +39,7 @@ from app import email
from app import log
from app.but import bulletin_but
from app.comp import res_sem
from app.comp.res_common import NotesTableCompat
from app.comp.res_compat import NotesTableCompat
from app.models import FormSemestre, Identite, ModuleImplInscription
from app.scodoc.sco_permissions import Permission
from app.scodoc.sco_exceptions import AccessDenied, ScoValueError

View File

@ -33,7 +33,7 @@ import json
from app.but import bulletin_but
from app.comp import res_sem
from app.comp.res_common import NotesTableCompat
from app.comp.res_compat import NotesTableCompat
from app.models.formsemestre import FormSemestre
from app.models.etudiants import Identite

View File

@ -45,7 +45,7 @@ from xml.etree import ElementTree
from xml.etree.ElementTree import Element
from app.comp import res_sem
from app.comp.res_common import NotesTableCompat
from app.comp.res_compat import NotesTableCompat
import app.scodoc.sco_utils as scu
import app.scodoc.notesdb as ndb
from app import log

View File

@ -33,7 +33,7 @@ from flask import url_for, g, request
from app import log
from app.comp import res_sem
from app.comp.res_common import NotesTableCompat
from app.comp.res_compat import NotesTableCompat
from app.models import FormSemestre
import app.scodoc.sco_utils as scu
import app.scodoc.notesdb as ndb

View File

@ -39,7 +39,7 @@ from flask import request
from app import log
from app.comp import res_sem
from app.comp.res_common import NotesTableCompat
from app.comp.res_compat import NotesTableCompat
from app.models import FormSemestre
import app.scodoc.sco_utils as scu

View File

@ -30,7 +30,7 @@
from flask import url_for, g, request
from app.comp import res_sem
from app.comp.res_common import NotesTableCompat
from app.comp.res_compat import NotesTableCompat
from app.models import FormSemestre
import app.scodoc.notesdb as ndb
import app.scodoc.sco_utils as scu

View File

@ -38,7 +38,7 @@ from flask import url_for, g, request
from flask_login import current_user
from app.comp import res_sem
from app.comp.res_common import NotesTableCompat
from app.comp.res_compat import NotesTableCompat
from app.models import FormSemestre
import app.scodoc.sco_utils as scu
import app.scodoc.notesdb as ndb

View File

@ -33,7 +33,7 @@ import flask
from flask import url_for, g, request
from app.comp import res_sem
from app.comp.res_common import NotesTableCompat
from app.comp.res_compat import NotesTableCompat
from app.models import FormSemestre
import app.scodoc.sco_utils as scu
from app import log

View File

@ -36,7 +36,7 @@ from flask_login import current_user
from app import log
from app.comp import res_sem
from app.comp.res_common import NotesTableCompat
from app.comp.res_compat import NotesTableCompat
from app.models import Module
from app.models.formsemestre import FormSemestre
import app.scodoc.sco_utils as scu

View File

@ -37,7 +37,7 @@ import app.scodoc.sco_utils as scu
from app import log
from app.comp import res_sem
from app.comp.res_common import NotesTableCompat
from app.comp.res_compat import NotesTableCompat
from app.models import FormSemestre
from app.models.notes import etud_has_notes_attente

View File

@ -46,7 +46,7 @@ from flask import url_for, make_response
from app import db
from app.comp import res_sem
from app.comp.res_common import NotesTableCompat
from app.comp.res_compat import NotesTableCompat
from app.models import FormSemestre, formsemestre
from app.models import GROUPNAME_STR_LEN, SHORT_STR_LEN
from app.models.groups import Partition

View File

@ -38,7 +38,7 @@ from app import models
from app.comp import res_sem
from app.comp import moy_mod
from app.comp.moy_mod import ModuleImplResults
from app.comp.res_common import NotesTableCompat
from app.comp.res_compat import NotesTableCompat
from app.comp.res_but import ResultatsSemestreBUT
from app.models import FormSemestre
from app.models.evaluations import Evaluation

View File

@ -34,7 +34,7 @@ from flask import url_for, g, request
from flask_login import current_user
from app.comp import res_sem
from app.comp.res_common import NotesTableCompat
from app.comp.res_compat import NotesTableCompat
from app.models import FormSemestre
import app.scodoc.notesdb as ndb

View File

@ -34,8 +34,7 @@ from flask_login import current_user
from app.auth.models import User
from app.comp import res_sem
from app.comp.res_common import NotesTableCompat
from app.models import FormSemestre
from app.comp.res_compat import NotesTableCompat
from app.models import ModuleImpl
from app.models.evaluations import Evaluation
import app.scodoc.sco_utils as scu

View File

@ -29,7 +29,7 @@
"""
from app.comp import res_sem
from app.comp.res_common import NotesTableCompat
from app.comp.res_compat import NotesTableCompat
from app.models import FormSemestre, UniteEns
import app.scodoc.sco_utils as scu

View File

@ -34,7 +34,7 @@ import collections
from flask import url_for, g, request
from app.comp import res_sem
from app.comp.res_common import NotesTableCompat
from app.comp.res_compat import NotesTableCompat
from app.models import FormSemestre
import app.scodoc.sco_utils as scu
from app.scodoc import sco_abs

View File

@ -36,7 +36,7 @@ from flask import request
from flask_login import current_user
from app.comp import res_sem
from app.comp.res_common import NotesTableCompat
from app.comp.res_compat import NotesTableCompat
from app.models import FormSemestre, Identite
from app.scodoc import sco_abs
from app.scodoc import sco_codes_parcours

View File

@ -55,7 +55,7 @@ import flask
from flask import url_for, g, request
from app.comp import res_sem
from app.comp.res_common import NotesTableCompat
from app.comp.res_compat import NotesTableCompat
from app.models import FormSemestre, UniteEns
import app.scodoc.sco_utils as scu

View File

@ -38,15 +38,13 @@ from flask import make_response, url_for
from app import log
from app.but import bulletin_but
from app.comp import res_sem
from app.comp.res_but import ResultatsSemestreBUT
from app.comp.res_common import NotesTableCompat
from app.comp.res_compat import NotesTableCompat
from app.models import FormSemestre
from app.models.etudiants import Identite
from app.models.evaluations import Evaluation
import app.scodoc.sco_utils as scu
from app.scodoc import html_sco_header
from app.scodoc import sco_bac
from app.scodoc import sco_bulletins_json
from app.scodoc import sco_bulletins_xml
from app.scodoc import sco_bulletins, sco_excel
@ -225,12 +223,9 @@ def do_formsemestre_recapcomplet(
):
"""Calcule et renvoie le tableau récapitulatif."""
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
if (
formsemestre.formation.is_apc()
and format not in ("xml", "json")
and not modejury
):
data, filename = make_formsemestre_recapcomplet_apc(formsemestre, format=format)
if format == "html" and not modejury:
res: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
data, filename = gen_formsemestre_recapcomplet_html(formsemestre, res)
else:
data, filename, format = make_formsemestre_recapcomplet(
formsemestre_id=formsemestre_id,
@ -1037,14 +1032,13 @@ def _gen_row(keys: list[str], row, elt="td"):
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"):
def gen_formsemestre_recapcomplet_html(
formsemestre: FormSemestre, res: NotesTableCompat
):
"""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
)
rows, footer_rows, titles, column_ids = res.get_table_recap(convert_values=True)
H = ['<div class="table_recap"><table class="table_recap">']
# header
H.append(

View File

@ -40,7 +40,7 @@ from flask import url_for, g, request
import pydot
from app.comp import res_sem
from app.comp.res_common import NotesTableCompat
from app.comp.res_compat import NotesTableCompat
from app.models import FormSemestre
import app.scodoc.sco_utils as scu

View File

@ -37,7 +37,7 @@ from flask import g, url_for, request
from flask_login import current_user
from app.comp import res_sem
from app.comp.res_common import NotesTableCompat
from app.comp.res_compat import NotesTableCompat
from app.models import FormSemestre
import app.scodoc.sco_utils as scu
from app.scodoc.sco_utils import ModuleType

View File

@ -43,7 +43,7 @@ import flask
from flask import g
from app.comp import res_sem
from app.comp.res_common import NotesTableCompat
from app.comp.res_compat import NotesTableCompat
from app.models import FormSemestre
from app.scodoc import html_sco_header
from app.scodoc import sco_cache

View File

@ -38,7 +38,7 @@ import http
from flask import g, url_for
from app.comp import res_sem
from app.comp.res_common import NotesTableCompat
from app.comp.res_compat import NotesTableCompat
from app.models import FormSemestre
import app.scodoc.sco_utils as scu
import app.scodoc.notesdb as ndb

View File

@ -62,7 +62,7 @@ from flask_login import current_user
from app import log
from app.comp import res_sem
from app.comp.res_common import NotesTableCompat
from app.comp.res_compat import NotesTableCompat
from app.decorators import (
scodoc,
scodoc7func,

View File

@ -42,7 +42,7 @@ from flask_login import current_user
from werkzeug.utils import redirect
from app.comp import res_sem
from app.comp.res_common import NotesTableCompat
from app.comp.res_compat import NotesTableCompat
from app.models.formsemestre import FormSemestre
from app.models.formsemestre import FormSemestreUEComputationExpr
from app.models.ues import UniteEns

View File

@ -14,7 +14,7 @@ from flask import current_app, g
import app
from app import db
from app.comp import res_sem
from app.comp.res_common import NotesTableCompat
from app.comp.res_compat import NotesTableCompat
from app.models import FormSemestre
from app.scodoc import sco_cache
from app.scodoc import sco_evaluations

View File

@ -11,7 +11,7 @@ from flask import g
import app
from app.comp import res_sem
from app.comp.res_common import NotesTableCompat
from app.comp.res_compat import NotesTableCompat
from app.models import FormSemestre
from app.scodoc import sco_bulletins, sco_formsemestre
from app.scodoc import sco_cache

View File

@ -20,7 +20,7 @@ from tests.unit import sco_fake_gen
import app
from app.comp import res_sem
from app.comp.res_common import NotesTableCompat
from app.comp.res_compat import NotesTableCompat
from app.models import FormSemestre
from app.scodoc import sco_formsemestre
from app.scodoc import sco_abs