WIP: reorganisation des calculs
This commit is contained in:
parent
a7324ac634
commit
02989e6c88
@ -147,6 +147,7 @@ class BulletinBUT(ResultatsSemestreBUT):
|
|||||||
def bulletin_etud(self, etud, formsemestre) -> dict:
|
def bulletin_etud(self, etud, formsemestre) -> dict:
|
||||||
"""Le bulletin de l'étudiant dans ce semestre"""
|
"""Le bulletin de l'étudiant dans ce semestre"""
|
||||||
etat_inscription = etud.etat_inscription(formsemestre.id)
|
etat_inscription = etud.etat_inscription(formsemestre.id)
|
||||||
|
nb_inscrits = self.get_inscriptions_counts()[scu.INSCRIT]
|
||||||
d = {
|
d = {
|
||||||
"version": "0",
|
"version": "0",
|
||||||
"type": "BUT",
|
"type": "BUT",
|
||||||
@ -189,7 +190,7 @@ class BulletinBUT(ResultatsSemestreBUT):
|
|||||||
},
|
},
|
||||||
"rang": { # classement wrt moyenne général, indicatif
|
"rang": { # classement wrt moyenne général, indicatif
|
||||||
"value": self.etud_moy_gen_ranks[etud.id],
|
"value": self.etud_moy_gen_ranks[etud.id],
|
||||||
"total": len(self.etuds),
|
"total": nb_inscrits,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@ -212,7 +213,7 @@ class BulletinBUT(ResultatsSemestreBUT):
|
|||||||
"moy": "",
|
"moy": "",
|
||||||
"max": "",
|
"max": "",
|
||||||
},
|
},
|
||||||
"rang": {"value": "DEM", "total": len(self.etuds)},
|
"rang": {"value": "DEM", "total": nb_inscrits},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
d.update(
|
d.update(
|
||||||
|
@ -69,10 +69,11 @@ def bulletin_but_xml_compat(
|
|||||||
% (formsemestre_id, etudid)
|
% (formsemestre_id, etudid)
|
||||||
)
|
)
|
||||||
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||||
etud = Identite.query.get_or_404(etudid)
|
etud: Identite = Identite.query.get_or_404(etudid)
|
||||||
results = bulletin_but.ResultatsSemestreBUT(formsemestre)
|
results = bulletin_but.ResultatsSemestreBUT(formsemestre)
|
||||||
nb_inscrits = len(results.etuds)
|
nb_inscrits = results.get_inscriptions_counts()[scu.INSCRIT]
|
||||||
etat_inscription = etud.etat_inscription(formsemestre.id)
|
# etat_inscription = etud.etat_inscription(formsemestre.id)
|
||||||
|
etat_inscription = results.formsemestre.etuds_inscriptions[etudid].etat
|
||||||
if (not formsemestre.bul_hide_xml) or force_publishing:
|
if (not formsemestre.bul_hide_xml) or force_publishing:
|
||||||
published = 1
|
published = 1
|
||||||
else:
|
else:
|
||||||
|
@ -16,13 +16,13 @@ from app import models
|
|||||||
#
|
#
|
||||||
def df_load_modimpl_inscr(formsemestre) -> pd.DataFrame:
|
def df_load_modimpl_inscr(formsemestre) -> pd.DataFrame:
|
||||||
"""Charge la matrice des inscriptions aux modules du semestre
|
"""Charge la matrice des inscriptions aux modules du semestre
|
||||||
rows: etudid
|
rows: etudid (inscrits au semestre, avec DEM et DEF)
|
||||||
columns: moduleimpl_id (en chaîne)
|
columns: moduleimpl_id (en chaîne)
|
||||||
value: bool (0/1 inscrit ou pas)
|
value: bool (0/1 inscrit ou pas)
|
||||||
"""
|
"""
|
||||||
# méthode la moins lente: une requete par module, merge les dataframes
|
# méthode la moins lente: une requete par module, merge les dataframes
|
||||||
moduleimpl_ids = [m.id for m in formsemestre.modimpls]
|
moduleimpl_ids = [m.id for m in formsemestre.modimpls]
|
||||||
etudids = [i.etudid for i in formsemestre.get_inscrits(include_dem=False)]
|
etudids = [inscr.etudid for inscr in formsemestre.inscriptions]
|
||||||
df = pd.DataFrame(index=etudids, dtype=int)
|
df = pd.DataFrame(index=etudids, dtype=int)
|
||||||
for moduleimpl_id in moduleimpl_ids:
|
for moduleimpl_id in moduleimpl_ids:
|
||||||
ins_df = pd.read_sql_query(
|
ins_df = pd.read_sql_query(
|
||||||
|
@ -75,7 +75,7 @@ class ModuleImplResults:
|
|||||||
"{ evaluation_id: EvaluationEtat }"
|
"{ evaluation_id: EvaluationEtat }"
|
||||||
#
|
#
|
||||||
self.evals_notes = None
|
self.evals_notes = None
|
||||||
"""DataFrame, colonnes: EVALS, Lignes: etudid
|
"""DataFrame, colonnes: EVALS, Lignes: etudid (inscrits au SEMESTRE)
|
||||||
valeur: notes brutes, float ou NOTES_ATTENTE, NOTES_NEUTRALISE,
|
valeur: notes brutes, float ou NOTES_ATTENTE, NOTES_NEUTRALISE,
|
||||||
NOTES_ABSENCE.
|
NOTES_ABSENCE.
|
||||||
Les NaN désignent les notes manquantes (non saisies).
|
Les NaN désignent les notes manquantes (non saisies).
|
||||||
@ -105,7 +105,7 @@ class ModuleImplResults:
|
|||||||
|
|
||||||
Évaluation "complete" (prise en compte dans les calculs) si:
|
Évaluation "complete" (prise en compte dans les calculs) si:
|
||||||
- soit tous les étudiants inscrits au module ont des notes
|
- soit tous les étudiants inscrits au module ont des notes
|
||||||
- soit elle a été déclarée "à prise ne compte immédiate" (publish_incomplete)
|
- soit elle a été déclarée "à prise en compte immédiate" (publish_incomplete)
|
||||||
|
|
||||||
Évaluation "attente" (prise en compte dans les calculs, mais il y
|
Évaluation "attente" (prise en compte dans les calculs, mais il y
|
||||||
manque des notes) ssi il y a des étudiants inscrits au semestre et au module
|
manque des notes) ssi il y a des étudiants inscrits au semestre et au module
|
||||||
@ -178,14 +178,12 @@ class ModuleImplResults:
|
|||||||
return eval_df
|
return eval_df
|
||||||
|
|
||||||
def _etudids(self):
|
def _etudids(self):
|
||||||
"""L'index du dataframe est la liste des étudiants inscrits au semestre,
|
"""L'index du dataframe est la liste de tous les étudiants inscrits au semestre"""
|
||||||
sans les démissionnaires.
|
|
||||||
"""
|
|
||||||
return [
|
return [
|
||||||
e.etudid
|
inscr.etudid
|
||||||
for e in ModuleImpl.query.get(self.moduleimpl_id).formsemestre.get_inscrits(
|
for inscr in ModuleImpl.query.get(
|
||||||
include_dem=False
|
self.moduleimpl_id
|
||||||
)
|
).formsemestre.inscriptions
|
||||||
]
|
]
|
||||||
|
|
||||||
def get_evaluations_coefs(self, moduleimpl: ModuleImpl) -> np.array:
|
def get_evaluations_coefs(self, moduleimpl: ModuleImpl) -> np.array:
|
||||||
|
@ -31,8 +31,10 @@ import numpy as np
|
|||||||
import pandas as pd
|
import pandas as pd
|
||||||
|
|
||||||
|
|
||||||
def compute_sem_moys_apc(etud_moy_ue_df, modimpl_coefs_df):
|
def compute_sem_moys_apc(
|
||||||
"""Calcule la moyenne générale indicative
|
etud_moy_ue_df: pd.DataFrame, modimpl_coefs_df: pd.DataFrame
|
||||||
|
) -> pd.Series:
|
||||||
|
"""Calcule les moyennes générales indicatives de tous les étudiants
|
||||||
= moyenne des moyennes d'UE, pondérée par la somme de leurs coefs
|
= moyenne des moyennes d'UE, pondérée par la somme de leurs coefs
|
||||||
|
|
||||||
etud_moy_ue_df: DataFrame, colonnes ue_id, lignes etudid
|
etud_moy_ue_df: DataFrame, colonnes ue_id, lignes etudid
|
||||||
@ -46,10 +48,11 @@ def compute_sem_moys_apc(etud_moy_ue_df, modimpl_coefs_df):
|
|||||||
return moy_gen
|
return moy_gen
|
||||||
|
|
||||||
|
|
||||||
def comp_ranks_series(notes: pd.Series):
|
def comp_ranks_series(notes: pd.Series) -> dict[int, str]:
|
||||||
"""Calcul rangs à partir d'une séries ("vecteur") de notes (index etudid, valeur numérique)
|
"""Calcul rangs à partir d'une séries ("vecteur") de notes (index etudid, valeur
|
||||||
en tenant compte des ex-aequos
|
numérique) en tenant compte des ex-aequos.
|
||||||
Le resultat est: { etudid : rang } où rang est une chaine decrivant le rang
|
|
||||||
|
Result: { etudid : rang:str } où rang est une chaine decrivant le rang.
|
||||||
"""
|
"""
|
||||||
notes = notes.sort_values(ascending=False) # Serie, tri par ordre décroissant
|
notes = notes.sort_values(ascending=False) # Serie, tri par ordre décroissant
|
||||||
rangs = pd.Series(index=notes.index, dtype=str) # le rang est une chaîne
|
rangs = pd.Series(index=notes.index, dtype=str) # le rang est une chaîne
|
||||||
|
@ -140,9 +140,14 @@ def notes_sem_assemble_cube(modimpls_notes: list[pd.DataFrame]) -> np.ndarray:
|
|||||||
|
|
||||||
|
|
||||||
def notes_sem_load_cube(formsemestre: FormSemestre) -> tuple:
|
def notes_sem_load_cube(formsemestre: FormSemestre) -> tuple:
|
||||||
"""Calcule le cube des notes du semestre
|
"""Construit le "cube" (tenseur) des notes du semestre.
|
||||||
(charge toutes les notes, calcule les moyenne des modules
|
Charge toutes les notes (sql), calcule les moyennes des modules
|
||||||
et assemble le cube)
|
et assemble le cube.
|
||||||
|
|
||||||
|
etuds: tous les inscrits au semestre (avec dem. et def.)
|
||||||
|
modimpls: _tous_ les modimpls de ce semestre
|
||||||
|
UEs: X?X voir quelles sont les UE considérées ici
|
||||||
|
|
||||||
Resultat:
|
Resultat:
|
||||||
sem_cube : ndarray (etuds x modimpls x UEs)
|
sem_cube : ndarray (etuds x modimpls x UEs)
|
||||||
modimpls_evals_poids dict { modimpl.id : evals_poids }
|
modimpls_evals_poids dict { modimpl.id : evals_poids }
|
||||||
@ -174,14 +179,14 @@ def compute_ue_moys_apc(
|
|||||||
) -> pd.DataFrame:
|
) -> pd.DataFrame:
|
||||||
"""Calcul de la moyenne d'UE en mode APC (BUT).
|
"""Calcul de la moyenne d'UE en mode APC (BUT).
|
||||||
La moyenne d'UE est un nombre (note/20), ou NI ou NA ou ERR
|
La moyenne d'UE est un nombre (note/20), ou NI ou NA ou ERR
|
||||||
NI non inscrit à (au moins un) module de cette UE
|
NI non inscrit à (au moins un) module de cette UE
|
||||||
NA pas de notes disponibles
|
NA pas de notes disponibles
|
||||||
ERR erreur dans une formule utilisateur. [XXX pas encore gérées ici]
|
ERR erreur dans une formule utilisateur. [XXX pas encore gérées ici]
|
||||||
|
|
||||||
sem_cube: notes moyennes aux modules
|
sem_cube: notes moyennes aux modules
|
||||||
ndarray (etuds x modimpls x UEs)
|
ndarray (etuds x modimpls x UEs)
|
||||||
(floats avec des NaN)
|
(floats avec des NaN)
|
||||||
etuds : listes des étudiants (dim. 0 du cube)
|
etuds : liste des étudiants (dim. 0 du cube)
|
||||||
modimpls : liste des modules à considérer (dim. 1 du cube)
|
modimpls : liste des modules à considérer (dim. 1 du cube)
|
||||||
ues : liste des UE (dim. 2 du cube)
|
ues : liste des UE (dim. 2 du cube)
|
||||||
modimpl_inscr_df: matrice d'inscription du semestre (etud x modimpl)
|
modimpl_inscr_df: matrice d'inscription du semestre (etud x modimpl)
|
||||||
@ -235,12 +240,12 @@ def compute_ue_moys_classic(
|
|||||||
ues: list,
|
ues: list,
|
||||||
modimpl_inscr_df: pd.DataFrame,
|
modimpl_inscr_df: pd.DataFrame,
|
||||||
modimpl_coefs: np.array,
|
modimpl_coefs: np.array,
|
||||||
) -> tuple:
|
) -> tuple[pd.Series, pd.DataFrame, pd.DataFrame]:
|
||||||
"""Calcul de la moyenne d'UE en mode classique.
|
"""Calcul de la moyenne d'UE en mode classique.
|
||||||
La moyenne d'UE est un nombre (note/20), ou NI ou NA ou ERR
|
La moyenne d'UE est un nombre (note/20), ou NI ou NA ou ERR
|
||||||
NI non inscrit à (au moins un) module de cette UE
|
NI non inscrit à (au moins un) module de cette UE
|
||||||
NA pas de notes disponibles
|
NA pas de notes disponibles
|
||||||
ERR erreur dans une formule utilisateur. [XXX pas encore gérées ici]
|
ERR erreur dans une formule utilisateur. [XXX pas encore gérées ici]
|
||||||
|
|
||||||
sem_matrix: notes moyennes aux modules
|
sem_matrix: notes moyennes aux modules
|
||||||
ndarray (etuds x modimpls)
|
ndarray (etuds x modimpls)
|
||||||
@ -253,6 +258,9 @@ def compute_ue_moys_classic(
|
|||||||
Résultat:
|
Résultat:
|
||||||
- moyennes générales: pd.Series, index etudid
|
- moyennes générales: pd.Series, index etudid
|
||||||
- moyennes d'UE: DataFrame columns UE, rows etudid
|
- moyennes d'UE: DataFrame columns UE, rows etudid
|
||||||
|
- coefficients d'UE: DataFrame, columns UE, rows etudid
|
||||||
|
les coefficients effectifs de chaque UE pour chaque étudiant
|
||||||
|
(sommes de coefs de modules pris en compte)
|
||||||
"""
|
"""
|
||||||
nb_etuds, nb_modules = sem_matrix.shape
|
nb_etuds, nb_modules = sem_matrix.shape
|
||||||
assert len(modimpl_coefs) == nb_modules
|
assert len(modimpl_coefs) == nb_modules
|
||||||
@ -293,4 +301,9 @@ def compute_ue_moys_classic(
|
|||||||
etud_moy_ue_df = pd.DataFrame(
|
etud_moy_ue_df = pd.DataFrame(
|
||||||
etud_moy_ue, index=modimpl_inscr_df.index, columns=[ue.id for ue in ues]
|
etud_moy_ue, index=modimpl_inscr_df.index, columns=[ue.id for ue in ues]
|
||||||
)
|
)
|
||||||
return etud_moy_gen_s, etud_moy_ue_df
|
etud_coef_ue_df = pd.DataFrame(
|
||||||
|
coefs.sum(axis=2).T,
|
||||||
|
index=modimpl_inscr_df.index, # etudids
|
||||||
|
columns=[ue.id for ue in ues],
|
||||||
|
)
|
||||||
|
return etud_moy_gen_s, etud_moy_ue_df, etud_coef_ue_df
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
"""Résultats semestres BUT
|
"""Résultats semestres BUT
|
||||||
"""
|
"""
|
||||||
|
import pandas as pd
|
||||||
|
|
||||||
from app.comp import moy_ue, moy_sem, inscr_mod
|
from app.comp import moy_ue, moy_sem, inscr_mod
|
||||||
from app.comp.res_common import NotesTableCompat
|
from app.comp.res_common import NotesTableCompat
|
||||||
@ -49,6 +50,10 @@ class ResultatsSemestreBUT(NotesTableCompat):
|
|||||||
self.modimpl_inscr_df,
|
self.modimpl_inscr_df,
|
||||||
self.modimpl_coefs_df,
|
self.modimpl_coefs_df,
|
||||||
)
|
)
|
||||||
|
# Les coefficients d'UE ne sont pas utilisés en APC
|
||||||
|
self.etud_coef_ue_df = pd.DataFrame(
|
||||||
|
1.0, index=self.etud_moy_ue.index, columns=self.etud_moy_ue.columns
|
||||||
|
)
|
||||||
self.etud_moy_gen = moy_sem.compute_sem_moys_apc(
|
self.etud_moy_gen = moy_sem.compute_sem_moys_apc(
|
||||||
self.etud_moy_ue, self.modimpl_coefs_df
|
self.etud_moy_ue, self.modimpl_coefs_df
|
||||||
)
|
)
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
"""
|
"""
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
|
|
||||||
from app.comp import moy_mod, moy_ue, moy_sem, inscr_mod
|
from app.comp import moy_mod, moy_ue, moy_sem, inscr_mod
|
||||||
from app.comp.res_common import NotesTableCompat
|
from app.comp.res_common import NotesTableCompat
|
||||||
from app.models.formsemestre import FormSemestre
|
from app.models.formsemestre import FormSemestre
|
||||||
@ -45,7 +46,11 @@ class ResultatsSemestreClassic(NotesTableCompat):
|
|||||||
self.modimpl_idx = {m.id: i for i, m in enumerate(self.formsemestre.modimpls)}
|
self.modimpl_idx = {m.id: i for i, m in enumerate(self.formsemestre.modimpls)}
|
||||||
"l'idx de la colonne du mod modimpl.id est modimpl_idx[modimpl.id]"
|
"l'idx de la colonne du mod modimpl.id est modimpl_idx[modimpl.id]"
|
||||||
|
|
||||||
self.etud_moy_gen, self.etud_moy_ue = moy_ue.compute_ue_moys_classic(
|
(
|
||||||
|
self.etud_moy_gen,
|
||||||
|
self.etud_moy_ue,
|
||||||
|
self.etud_coef_ue_df,
|
||||||
|
) = moy_ue.compute_ue_moys_classic(
|
||||||
self.formsemestre,
|
self.formsemestre,
|
||||||
self.sem_matrix,
|
self.sem_matrix,
|
||||||
self.ues,
|
self.ues,
|
||||||
|
@ -4,13 +4,13 @@
|
|||||||
# See LICENSE
|
# See LICENSE
|
||||||
##############################################################################
|
##############################################################################
|
||||||
|
|
||||||
from collections import defaultdict
|
from collections import defaultdict, Counter
|
||||||
from functools import cached_property
|
from functools import cached_property
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
from app.comp.aux import StatsMoyenne
|
from app.comp.aux import StatsMoyenne
|
||||||
from app.comp.moy_mod import ModuleImplResults
|
from app.comp.moy_mod import ModuleImplResults
|
||||||
from app.models import FormSemestre, ModuleImpl
|
from app.models import FormSemestre, Identite, ModuleImpl
|
||||||
from app.models.ues import UniteEns
|
from app.models.ues import UniteEns
|
||||||
from app.scodoc import sco_utils as scu
|
from app.scodoc import sco_utils as scu
|
||||||
from app.scodoc.sco_cache import ResultatsSemestreCache
|
from app.scodoc.sco_cache import ResultatsSemestreCache
|
||||||
@ -32,6 +32,7 @@ class ResultatsSemestre:
|
|||||||
"etud_moy_ue",
|
"etud_moy_ue",
|
||||||
"modimpl_inscr_df",
|
"modimpl_inscr_df",
|
||||||
"modimpls_results",
|
"modimpls_results",
|
||||||
|
"etud_coef_ue_df",
|
||||||
)
|
)
|
||||||
|
|
||||||
def __init__(self, formsemestre: FormSemestre):
|
def __init__(self, formsemestre: FormSemestre):
|
||||||
@ -45,7 +46,10 @@ class ResultatsSemestre:
|
|||||||
self.etud_moy_gen = {}
|
self.etud_moy_gen = {}
|
||||||
self.etud_moy_gen_ranks = {}
|
self.etud_moy_gen_ranks = {}
|
||||||
self.modimpls_results: ModuleImplResults = None
|
self.modimpls_results: ModuleImplResults = None
|
||||||
# TODO
|
self.etud_coef_ue_df = None
|
||||||
|
"""coefs d'UE effectifs pour chaque etudiant (pour form. classiques)"""
|
||||||
|
|
||||||
|
# TODO ?
|
||||||
|
|
||||||
def load_cached(self) -> bool:
|
def load_cached(self) -> bool:
|
||||||
"Load cached dataframes, returns False si pas en cache"
|
"Load cached dataframes, returns False si pas en cache"
|
||||||
@ -68,24 +72,34 @@ class ResultatsSemestre:
|
|||||||
# voir ce qui est chargé / calculé ici et dans les sous-classes
|
# voir ce qui est chargé / calculé ici et dans les sous-classes
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
@cached_property
|
def get_inscriptions_counts(self) -> Counter:
|
||||||
def etuds(self):
|
"""Nombre d'inscrits, défaillants, démissionnaires.
|
||||||
"Liste des inscrits au semestre, sans les démissionnaires"
|
|
||||||
# nb: si la liste des inscrits change, ResultatsSemestre devient invalide
|
Exemple: res.get_inscriptions_counts()[scu.INSCRIT]
|
||||||
return self.formsemestre.get_inscrits(include_dem=False)
|
|
||||||
|
Result: a collections.Counter instance
|
||||||
|
"""
|
||||||
|
return Counter(ins.etat for ins in self.formsemestre.inscriptions)
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def etud_index(self):
|
def etuds(self) -> list[Identite]:
|
||||||
|
"Liste des inscrits au semestre, avec les démissionnaires et les défaillants"
|
||||||
|
# nb: si la liste des inscrits change, ResultatsSemestre devient invalide
|
||||||
|
return self.formsemestre.get_inscrits(include_demdef=True)
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def etud_index(self) -> dict[int, int]:
|
||||||
"dict { etudid : indice dans les inscrits }"
|
"dict { etudid : indice dans les inscrits }"
|
||||||
return {e.id: idx for idx, e in enumerate(self.etuds)}
|
return {e.id: idx for idx, e in enumerate(self.etuds)}
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def etuds_dict(self):
|
def etuds_dict(self) -> dict[int, Identite]:
|
||||||
"dict { etudid : Identite } inscrits au semestre, sans les démissionnaires"
|
"""dict { etudid : Identite } inscrits au semestre,
|
||||||
|
avec les démissionnaires et defs."""
|
||||||
return {etud.id: etud for etud in self.etuds}
|
return {etud.id: etud for etud in self.etuds}
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def ues(self) -> list:
|
def ues(self) -> list[UniteEns]:
|
||||||
"""Liste des UEs du semestre
|
"""Liste des UEs du semestre
|
||||||
(indices des DataFrames)
|
(indices des DataFrames)
|
||||||
"""
|
"""
|
||||||
@ -153,6 +167,7 @@ class NotesTableCompat(ResultatsSemestre):
|
|||||||
|
|
||||||
def __init__(self, formsemestre: FormSemestre):
|
def __init__(self, formsemestre: FormSemestre):
|
||||||
super().__init__(formsemestre)
|
super().__init__(formsemestre)
|
||||||
|
|
||||||
nb_etuds = len(self.etuds)
|
nb_etuds = len(self.etuds)
|
||||||
self.bonus = defaultdict(lambda: 0.0) # XXX TODO
|
self.bonus = defaultdict(lambda: 0.0) # XXX TODO
|
||||||
self.ue_rangs = {u.id: (defaultdict(lambda: 0.0), nb_etuds) for u in self.ues}
|
self.ue_rangs = {u.id: (defaultdict(lambda: 0.0), nb_etuds) for u in self.ues}
|
||||||
@ -178,12 +193,18 @@ class NotesTableCompat(ResultatsSemestre):
|
|||||||
|
|
||||||
return [x["etudid"] for x in self.inscrlist]
|
return [x["etudid"] for x in self.inscrlist]
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def sem(self) -> dict:
|
||||||
|
"""le formsemestre, comme un dict (nt.sem)"""
|
||||||
|
return self.formsemestre.to_dict()
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def inscrlist(self) -> list[dict]: # utilisé par PE seulement
|
def inscrlist(self) -> list[dict]: # utilisé par PE seulement
|
||||||
"""Liste de dict etud, avec démissionnaires
|
"""Liste des inscrits au semestre (avec DEM et DEF),
|
||||||
|
sous forme de dict etud,
|
||||||
classée dans l'ordre alphabétique de noms.
|
classée dans l'ordre alphabétique de noms.
|
||||||
"""
|
"""
|
||||||
etuds = self.formsemestre.get_inscrits(include_dem=True)
|
etuds = self.formsemestre.get_inscrits(include_demdef=True)
|
||||||
etuds.sort(key=lambda e: e.sort_key)
|
etuds.sort(key=lambda e: e.sort_key)
|
||||||
return [e.to_dict_scodoc7() for e in etuds]
|
return [e.to_dict_scodoc7() for e in etuds]
|
||||||
|
|
||||||
@ -256,9 +277,12 @@ class NotesTableCompat(ResultatsSemestre):
|
|||||||
raise NotImplementedError() # virtual method
|
raise NotImplementedError() # virtual method
|
||||||
|
|
||||||
def get_etud_ue_status(self, etudid: int, ue_id: int):
|
def get_etud_ue_status(self, etudid: int, ue_id: int):
|
||||||
|
coef_ue = self.etud_coef_ue_df[ue_id][etudid]
|
||||||
return {
|
return {
|
||||||
"cur_moy_ue": self.etud_moy_ue[ue_id][etudid],
|
"cur_moy_ue": self.etud_moy_ue[ue_id][etudid],
|
||||||
|
"moy": self.etud_moy_ue[ue_id][etudid],
|
||||||
"is_capitalized": False, # XXX TODO
|
"is_capitalized": False, # XXX TODO
|
||||||
|
"coef_ue": coef_ue, # XXX TODO
|
||||||
}
|
}
|
||||||
|
|
||||||
def get_etud_rang(self, etudid: int):
|
def get_etud_rang(self, etudid: int):
|
||||||
@ -277,18 +301,20 @@ class NotesTableCompat(ResultatsSemestre):
|
|||||||
for e in modimpl.evaluations:
|
for e in modimpl.evaluations:
|
||||||
if self.modimpls_results[moduleimpl_id].evaluations_completes_dict[e.id]:
|
if self.modimpls_results[moduleimpl_id].evaluations_completes_dict[e.id]:
|
||||||
d = e.to_dict()
|
d = e.to_dict()
|
||||||
|
moduleimpl_results = self.modimpls_results[e.moduleimpl_id]
|
||||||
d["heure_debut"] = e.heure_debut # datetime.time
|
d["heure_debut"] = e.heure_debut # datetime.time
|
||||||
d["heure_fin"] = e.heure_fin
|
d["heure_fin"] = e.heure_fin
|
||||||
d["jour"] = e.jour # datetime
|
d["jour"] = e.jour # datetime
|
||||||
d["notes"] = {
|
d["notes"] = {
|
||||||
etud.id: {
|
etud.id: {
|
||||||
"etudid": etud.id,
|
"etudid": etud.id,
|
||||||
"value": self.modimpls_evals_notes[e.moduleimpl_id][e.id][
|
"value": moduleimpl_results.evals_notes[e.id][etud.id],
|
||||||
etud.id
|
|
||||||
],
|
|
||||||
}
|
}
|
||||||
for etud in self.etuds
|
for etud in self.etuds
|
||||||
}
|
}
|
||||||
|
d["etat"] = {
|
||||||
|
"evalattente": moduleimpl_results.evaluations_etat[e.id].nb_attente,
|
||||||
|
}
|
||||||
evals_results.append(d)
|
evals_results.append(d)
|
||||||
return evals_results
|
return evals_results
|
||||||
|
|
||||||
|
@ -117,11 +117,18 @@ class FormSemestre(db.Model):
|
|||||||
d.pop("_sa_instance_state", None)
|
d.pop("_sa_instance_state", None)
|
||||||
# ScoDoc7 output_formators: (backward compat)
|
# ScoDoc7 output_formators: (backward compat)
|
||||||
d["formsemestre_id"] = self.id
|
d["formsemestre_id"] = self.id
|
||||||
d["date_debut"] = (
|
if self.date_debut:
|
||||||
self.date_debut.strftime("%d/%m/%Y") if self.date_debut else ""
|
d["date_debut"] = self.date_debut.strftime("%d/%m/%Y")
|
||||||
)
|
d["date_debut_iso"] = self.date_debut.isoformat()
|
||||||
d["date_fin"] = self.date_fin.strftime("%d/%m/%Y") if self.date_fin else ""
|
else:
|
||||||
|
d["date_debut"] = d["date_debut_iso"] = ""
|
||||||
|
if self.date_fin:
|
||||||
|
d["date_fin"] = self.date_fin.strftime("%d/%m/%Y")
|
||||||
|
d["date_fin_iso"] = self.date_fin.isoformat()
|
||||||
|
else:
|
||||||
|
d["date_fin"] = d["date_fin_iso"] = ""
|
||||||
d["responsables"] = [u.id for u in self.responsables]
|
d["responsables"] = [u.id for u in self.responsables]
|
||||||
|
|
||||||
return d
|
return d
|
||||||
|
|
||||||
def query_ues(self, with_sport=False) -> flask_sqlalchemy.BaseQuery:
|
def query_ues(self, with_sport=False) -> flask_sqlalchemy.BaseQuery:
|
||||||
@ -271,18 +278,19 @@ class FormSemestre(db.Model):
|
|||||||
etudid, self.date_debut.isoformat(), self.date_fin.isoformat()
|
etudid, self.date_debut.isoformat(), self.date_fin.isoformat()
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_inscrits(self, include_dem=False) -> list[Identite]:
|
def get_inscrits(self, include_demdef=False) -> list[Identite]:
|
||||||
"""Liste des étudiants inscrits à ce semestre
|
"""Liste des étudiants inscrits à ce semestre
|
||||||
Si all, tous les étudiants, avec les démissionnaires.
|
Si include_demdef, tous les étudiants, avec les démissionnaires
|
||||||
|
et défaillants.
|
||||||
"""
|
"""
|
||||||
if include_dem:
|
if include_demdef:
|
||||||
return [ins.etud for ins in self.inscriptions]
|
return [ins.etud for ins in self.inscriptions]
|
||||||
else:
|
else:
|
||||||
return [ins.etud for ins in self.inscriptions if ins.etat == scu.INSCRIT]
|
return [ins.etud for ins in self.inscriptions if ins.etat == scu.INSCRIT]
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def etuds_inscriptions(self) -> dict:
|
def etuds_inscriptions(self) -> dict:
|
||||||
"""Map { etudid : inscription }"""
|
"""Map { etudid : inscription } (incluant DEM et DEF)"""
|
||||||
return {ins.etud.id: ins for ins in self.inscriptions}
|
return {ins.etud.id: ins for ins in self.inscriptions}
|
||||||
|
|
||||||
|
|
||||||
|
@ -62,7 +62,7 @@ Pour modifier les moyennes d'UE:
|
|||||||
|
|
||||||
La valeur retournée est:
|
La valeur retournée est:
|
||||||
- formations classiques: ajoutée à la moyenne générale
|
- formations classiques: ajoutée à la moyenne générale
|
||||||
- BUT: ajoutée à chaque UE si le coef XXX
|
- BUT: valeur multipliée par la somme des coefs modules sport ajoutée à chaque UE.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@ -171,6 +171,7 @@ class NotesTable:
|
|||||||
|
|
||||||
def __init__(self, formsemestre_id):
|
def __init__(self, formsemestre_id):
|
||||||
log(f"NotesTable( formsemestre_id={formsemestre_id} )")
|
log(f"NotesTable( formsemestre_id={formsemestre_id} )")
|
||||||
|
# raise NotImplementedError() # XXX
|
||||||
if not formsemestre_id:
|
if not formsemestre_id:
|
||||||
raise ValueError("invalid formsemestre_id (%s)" % formsemestre_id)
|
raise ValueError("invalid formsemestre_id (%s)" % formsemestre_id)
|
||||||
self.formsemestre_id = formsemestre_id
|
self.formsemestre_id = formsemestre_id
|
||||||
@ -409,7 +410,7 @@ class NotesTable:
|
|||||||
return ""
|
return ""
|
||||||
|
|
||||||
def get_etud_etat_html(self, etudid):
|
def get_etud_etat_html(self, etudid):
|
||||||
etat = self.inscrdict[etudid]["etat"]
|
|
||||||
if etat == "I":
|
if etat == "I":
|
||||||
return ""
|
return ""
|
||||||
elif etat == "D":
|
elif etat == "D":
|
||||||
|
@ -48,6 +48,9 @@ import app.scodoc.sco_utils as scu
|
|||||||
from app.scodoc.sco_utils import ModuleType
|
from app.scodoc.sco_utils import ModuleType
|
||||||
import app.scodoc.notesdb as ndb
|
import app.scodoc.notesdb as ndb
|
||||||
from app import log
|
from app import log
|
||||||
|
from app.comp import res_sem
|
||||||
|
from app.comp.res_common import NotesTableCompat
|
||||||
|
from app.models import FormSemestre
|
||||||
from app.scodoc.sco_permissions import Permission
|
from app.scodoc.sco_permissions import Permission
|
||||||
from app.scodoc.sco_exceptions import AccessDenied, ScoValueError
|
from app.scodoc.sco_exceptions import AccessDenied, ScoValueError
|
||||||
from app.scodoc import html_sco_header
|
from app.scodoc import html_sco_header
|
||||||
@ -136,7 +139,9 @@ def formsemestre_bulletinetud_dict(formsemestre_id, etudid, version="long"):
|
|||||||
raise ValueError("invalid version code !")
|
raise ValueError("invalid version code !")
|
||||||
|
|
||||||
prefs = sco_preferences.SemPreferences(formsemestre_id)
|
prefs = sco_preferences.SemPreferences(formsemestre_id)
|
||||||
nt = sco_cache.NotesTableCache.get(formsemestre_id) # > toutes notes
|
# nt = sco_cache.NotesTableCache.get(formsemestre_id) # > toutes notes
|
||||||
|
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||||
|
nt: NotesTableCompat = res_sem.load_formsemestre_result(formsemestre)
|
||||||
if not nt.get_etud_etat(etudid):
|
if not nt.get_etud_etat(etudid):
|
||||||
raise ScoValueError("Etudiant non inscrit à ce semestre")
|
raise ScoValueError("Etudiant non inscrit à ce semestre")
|
||||||
I = scu.DictDefault(defaultvalue="")
|
I = scu.DictDefault(defaultvalue="")
|
||||||
@ -191,7 +196,9 @@ def formsemestre_bulletinetud_dict(formsemestre_id, etudid, version="long"):
|
|||||||
I["decision_sem"] = ""
|
I["decision_sem"] = ""
|
||||||
I.update(infos)
|
I.update(infos)
|
||||||
|
|
||||||
I["etud_etat_html"] = nt.get_etud_etat_html(etudid)
|
I["etud_etat_html"] = _get_etud_etat_html(
|
||||||
|
formsemestre.etuds_inscriptions[etudid].etat
|
||||||
|
)
|
||||||
I["etud_etat"] = nt.get_etud_etat(etudid)
|
I["etud_etat"] = nt.get_etud_etat(etudid)
|
||||||
I["filigranne"] = ""
|
I["filigranne"] = ""
|
||||||
I["demission"] = ""
|
I["demission"] = ""
|
||||||
@ -261,17 +268,18 @@ def formsemestre_bulletinetud_dict(formsemestre_id, etudid, version="long"):
|
|||||||
# notes en attente dans ce semestre
|
# notes en attente dans ce semestre
|
||||||
rang = scu.RANG_ATTENTE_STR
|
rang = scu.RANG_ATTENTE_STR
|
||||||
rang_gr = scu.DictDefault(defaultvalue=scu.RANG_ATTENTE_STR)
|
rang_gr = scu.DictDefault(defaultvalue=scu.RANG_ATTENTE_STR)
|
||||||
|
inscriptions_counts = nt.get_inscriptions_counts()
|
||||||
I["rang"] = rang
|
I["rang"] = rang
|
||||||
I["rang_gr"] = rang_gr
|
I["rang_gr"] = rang_gr
|
||||||
I["gr_name"] = gr_name
|
I["gr_name"] = gr_name
|
||||||
I["ninscrits_gr"] = ninscrits_gr
|
I["ninscrits_gr"] = ninscrits_gr
|
||||||
I["nbetuds"] = len(nt.etud_moy_gen_ranks)
|
I["nbetuds"] = len(nt.etud_moy_gen_ranks)
|
||||||
I["nb_demissions"] = nt.nb_demissions
|
I["nb_demissions"] = inscriptions_counts[scu.DEMISSION]
|
||||||
I["nb_defaillants"] = nt.nb_defaillants
|
I["nb_defaillants"] = inscriptions_counts[scu.DEF]
|
||||||
if prefs["bul_show_rangs"]:
|
if prefs["bul_show_rangs"]:
|
||||||
I["rang_nt"] = "%s / %d" % (
|
I["rang_nt"] = "%s / %d" % (
|
||||||
rang,
|
rang,
|
||||||
I["nbetuds"] - nt.nb_demissions - nt.nb_defaillants,
|
inscriptions_counts[scu.INSCRIT],
|
||||||
)
|
)
|
||||||
I["rang_txt"] = "Rang " + I["rang_nt"]
|
I["rang_txt"] = "Rang " + I["rang_nt"]
|
||||||
else:
|
else:
|
||||||
@ -379,7 +387,8 @@ def formsemestre_bulletinetud_dict(formsemestre_id, etudid, version="long"):
|
|||||||
I["ues"].append(u) # ne montre pas les UE si non inscrit
|
I["ues"].append(u) # ne montre pas les UE si non inscrit
|
||||||
|
|
||||||
# Accès par matieres
|
# Accès par matieres
|
||||||
I["matieres_modules"].update(_sort_mod_by_matiere(modules, nt, etudid))
|
# voir si on supporte encore cela en #sco92 XXX
|
||||||
|
# I["matieres_modules"].update(_sort_mod_by_matiere(modules, nt, etudid))
|
||||||
|
|
||||||
#
|
#
|
||||||
C = make_context_dict(I["sem"], I["etud"])
|
C = make_context_dict(I["sem"], I["etud"])
|
||||||
@ -389,6 +398,18 @@ def formsemestre_bulletinetud_dict(formsemestre_id, etudid, version="long"):
|
|||||||
return C
|
return C
|
||||||
|
|
||||||
|
|
||||||
|
def _get_etud_etat_html(etat: str) -> str:
|
||||||
|
"""chaine html représentant l'état (backward compat sco7)"""
|
||||||
|
if etat == scu.INSCRIT: # "I"
|
||||||
|
return ""
|
||||||
|
elif etat == scu.DEMISSION: # "D"
|
||||||
|
return ' <font color="red">(DEMISSIONNAIRE)</font> '
|
||||||
|
elif etat == scu.DEF: # "DEF"
|
||||||
|
return ' <font color="red">(DEFAILLANT)</font> '
|
||||||
|
else:
|
||||||
|
return ' <font color="red">(%s)</font> ' % etat
|
||||||
|
|
||||||
|
|
||||||
def _sort_mod_by_matiere(modlist, nt, etudid):
|
def _sort_mod_by_matiere(modlist, nt, etudid):
|
||||||
matmod = {} # { matiere_id : [] }
|
matmod = {} # { matiere_id : [] }
|
||||||
for mod in modlist:
|
for mod in modlist:
|
||||||
|
@ -1433,18 +1433,19 @@ def create_etapes_partition(formsemestre_id, partition_name="apo_etapes"):
|
|||||||
|
|
||||||
|
|
||||||
def do_evaluation_listeetuds_groups(
|
def do_evaluation_listeetuds_groups(
|
||||||
evaluation_id, groups=None, getallstudents=False, include_dems=False
|
evaluation_id, groups=None, getallstudents=False, include_demdef=False
|
||||||
):
|
):
|
||||||
"""Donne la liste des etudids inscrits a cette evaluation dans les
|
"""Donne la liste des etudids inscrits a cette evaluation dans les
|
||||||
groupes indiqués.
|
groupes indiqués.
|
||||||
Si getallstudents==True, donne tous les etudiants inscrits a cette
|
Si getallstudents==True, donne tous les etudiants inscrits a cette
|
||||||
evaluation.
|
evaluation.
|
||||||
Si include_dems, compte aussi les etudiants démissionnaires
|
Si include_demdef, compte aussi les etudiants démissionnaires et défaillants
|
||||||
(sinon, par défaut, seulement les 'I')
|
(sinon, par défaut, seulement les 'I')
|
||||||
|
|
||||||
Résultat: [ (etudid, etat) ], où etat='I', 'D', 'DEF'
|
Résultat: [ (etudid, etat) ], où etat='I', 'D', 'DEF'
|
||||||
"""
|
"""
|
||||||
# nb: pour notes_table / do_evaluation_etat, getallstudents est vrai et include_dems faux
|
# nb: pour notes_table / do_evaluation_etat, getallstudents est vrai et
|
||||||
|
# include_demdef faux
|
||||||
fromtables = [
|
fromtables = [
|
||||||
"notes_moduleimpl_inscription Im",
|
"notes_moduleimpl_inscription Im",
|
||||||
"notes_formsemestre_inscription Isem",
|
"notes_formsemestre_inscription Isem",
|
||||||
@ -1476,7 +1477,7 @@ def do_evaluation_listeetuds_groups(
|
|||||||
and E.id = %(evaluation_id)s
|
and E.id = %(evaluation_id)s
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
if not include_dems:
|
if not include_demdef:
|
||||||
req += " and Isem.etat='I'"
|
req += " and Isem.etat='I'"
|
||||||
req += r
|
req += r
|
||||||
cnx = ndb.GetDBConnexion()
|
cnx = ndb.GetDBConnexion()
|
||||||
|
@ -309,7 +309,7 @@ def _make_table_notes(
|
|||||||
anonymous_lst_key = "etudid"
|
anonymous_lst_key = "etudid"
|
||||||
|
|
||||||
etudid_etats = sco_groups.do_evaluation_listeetuds_groups(
|
etudid_etats = sco_groups.do_evaluation_listeetuds_groups(
|
||||||
E["evaluation_id"], groups, include_dems=True
|
E["evaluation_id"], groups, include_demdef=True
|
||||||
)
|
)
|
||||||
for etudid, etat in etudid_etats:
|
for etudid, etat in etudid_etats:
|
||||||
css_row_class = None
|
css_row_class = None
|
||||||
|
@ -307,7 +307,7 @@ class PlacementRunner:
|
|||||||
self.evaluation_id,
|
self.evaluation_id,
|
||||||
self.groups,
|
self.groups,
|
||||||
getallstudents=get_all_students,
|
getallstudents=get_all_students,
|
||||||
include_dems=True,
|
include_demdef=True,
|
||||||
)
|
)
|
||||||
listetud = [] # liste de couples (nom,prenom)
|
listetud = [] # liste de couples (nom,prenom)
|
||||||
for etudid, etat in etudid_etats:
|
for etudid, etat in etudid_etats:
|
||||||
|
@ -306,8 +306,8 @@ def make_formsemestre_recapcomplet(
|
|||||||
)[0]
|
)[0]
|
||||||
parcours = formsemestre.formation.get_parcours()
|
parcours = formsemestre.formation.get_parcours()
|
||||||
|
|
||||||
# nt = sco_cache.NotesTableCache.get(formsemestre_id)
|
# nt = sco_cache.NotesTableCache.get(formsemestre_id) # sco91
|
||||||
# XXX EXPERIMENTAL
|
# sco92 :
|
||||||
nt: NotesTableCompat = res_sem.load_formsemestre_result(formsemestre)
|
nt: NotesTableCompat = res_sem.load_formsemestre_result(formsemestre)
|
||||||
modimpls = nt.get_modimpls_dict()
|
modimpls = nt.get_modimpls_dict()
|
||||||
ues = nt.get_ues_stat_dict() # incluant le(s) UE de sport
|
ues = nt.get_ues_stat_dict() # incluant le(s) UE de sport
|
||||||
@ -434,13 +434,12 @@ def make_formsemestre_recapcomplet(
|
|||||||
|
|
||||||
e["admission"] = {}
|
e["admission"] = {}
|
||||||
if not hidebac:
|
if not hidebac:
|
||||||
if etud_etat == scu.INSCRIT:
|
e["admission"] = nt.etuds_dict[etudid].admission.first()
|
||||||
e["admission"] = nt.etuds_dict[etudid].admission.first()
|
if e["admission"]:
|
||||||
if e["admission"]:
|
bac = nt.etuds_dict[etudid].admission[0].get_bac()
|
||||||
bac = nt.etuds_dict[etudid].admission[0].get_bac()
|
l.append(bac.abbrev())
|
||||||
l.append(bac.abbrev())
|
else:
|
||||||
else:
|
l.append("")
|
||||||
l.append("")
|
|
||||||
|
|
||||||
if format[:3] == "xls" or format == "csv": # tous les groupes
|
if format[:3] == "xls" or format == "csv": # tous les groupes
|
||||||
for partition in partitions:
|
for partition in partitions:
|
||||||
|
@ -310,7 +310,7 @@ def do_evaluation_set_missing(evaluation_id, value, dialog_confirmed=False):
|
|||||||
#
|
#
|
||||||
NotesDB = sco_evaluation_db.do_evaluation_get_all_notes(evaluation_id)
|
NotesDB = sco_evaluation_db.do_evaluation_get_all_notes(evaluation_id)
|
||||||
etudid_etats = sco_groups.do_evaluation_listeetuds_groups(
|
etudid_etats = sco_groups.do_evaluation_listeetuds_groups(
|
||||||
evaluation_id, getallstudents=True, include_dems=False
|
evaluation_id, getallstudents=True, include_demdef=False
|
||||||
)
|
)
|
||||||
notes = []
|
notes = []
|
||||||
for etudid, _ in etudid_etats: # pour tous les inscrits
|
for etudid, _ in etudid_etats: # pour tous les inscrits
|
||||||
@ -482,7 +482,7 @@ def notes_add(
|
|||||||
inscrits = {
|
inscrits = {
|
||||||
x[0]
|
x[0]
|
||||||
for x in sco_groups.do_evaluation_listeetuds_groups(
|
for x in sco_groups.do_evaluation_listeetuds_groups(
|
||||||
evaluation_id, getallstudents=True, include_dems=True
|
evaluation_id, getallstudents=True, include_demdef=True
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
for (etudid, value) in notes:
|
for (etudid, value) in notes:
|
||||||
@ -833,7 +833,7 @@ def feuille_saisie_notes(evaluation_id, group_ids=[]):
|
|||||||
etudids = [
|
etudids = [
|
||||||
x[0]
|
x[0]
|
||||||
for x in sco_groups.do_evaluation_listeetuds_groups(
|
for x in sco_groups.do_evaluation_listeetuds_groups(
|
||||||
evaluation_id, groups, getallstudents=getallstudents, include_dems=True
|
evaluation_id, groups, getallstudents=getallstudents, include_demdef=True
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -1079,7 +1079,7 @@ def _form_saisie_notes(E, M, group_ids, destination=""):
|
|||||||
etudids = [
|
etudids = [
|
||||||
x[0]
|
x[0]
|
||||||
for x in sco_groups.do_evaluation_listeetuds_groups(
|
for x in sco_groups.do_evaluation_listeetuds_groups(
|
||||||
evaluation_id, getallstudents=True, include_dems=True
|
evaluation_id, getallstudents=True, include_demdef=True
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
if not etudids:
|
if not etudids:
|
||||||
|
Loading…
Reference in New Issue
Block a user