Merge branch 'master' of https://scodoc.org/git/viennet/ScoDoc into dev92
This commit is contained in:
commit
23ea53294d
@ -40,13 +40,11 @@ def compute_mat_moys_classic(
|
||||
modimpl_mask = np.array(
|
||||
[m.module.matiere.id == matiere_id for m in formsemestre.modimpls_sorted]
|
||||
)
|
||||
etud_moy_gen, _, _ = moy_ue.compute_ue_moys_classic(
|
||||
formsemestre,
|
||||
etud_moy_mat = moy_ue.compute_mat_moys_classic(
|
||||
sem_matrix=sem_matrix,
|
||||
ues=ues,
|
||||
modimpl_inscr_df=modimpl_inscr_df,
|
||||
modimpl_coefs=modimpl_coefs,
|
||||
modimpl_mask=modimpl_mask,
|
||||
)
|
||||
matiere_moy[matiere_id] = etud_moy_gen
|
||||
matiere_moy[matiere_id] = etud_moy_mat
|
||||
return matiere_moy
|
||||
|
@ -294,7 +294,8 @@ def compute_ue_moys_classic(
|
||||
modimpl_coefs: np.array,
|
||||
modimpl_mask: np.array,
|
||||
) -> tuple[pd.Series, pd.DataFrame, pd.DataFrame]:
|
||||
"""Calcul de la moyenne d'UE en mode classique.
|
||||
"""Calcul de la moyenne d'UE et de la moy. générale en mode classique (DUT, LMD, ...).
|
||||
|
||||
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
|
||||
NA pas de notes disponibles
|
||||
@ -363,7 +364,7 @@ def compute_ue_moys_classic(
|
||||
modimpl_coefs_etuds_no_nan_stacked = np.stack(
|
||||
[modimpl_coefs_etuds_no_nan.T] * nb_ues
|
||||
)
|
||||
# nb_ue x nb_etuds x nb_mods : coefs prenant en compte NaN et inscriptions
|
||||
# nb_ue x nb_etuds x nb_mods : coefs prenant en compte NaN et inscriptions:
|
||||
coefs = (modimpl_coefs_etuds_no_nan_stacked * ue_modules).swapaxes(1, 2)
|
||||
if coefs.dtype == np.object: # arrive sur des tableaux vides
|
||||
coefs = coefs.astype(np.float)
|
||||
@ -408,6 +409,68 @@ def compute_ue_moys_classic(
|
||||
return etud_moy_gen_s, etud_moy_ue_df, etud_coef_ue_df
|
||||
|
||||
|
||||
def compute_mat_moys_classic(
|
||||
sem_matrix: np.array,
|
||||
modimpl_inscr_df: pd.DataFrame,
|
||||
modimpl_coefs: np.array,
|
||||
modimpl_mask: np.array,
|
||||
) -> pd.Series:
|
||||
"""Calcul de la moyenne sur un sous-enemble de modules en formation CLASSIQUE
|
||||
|
||||
La moyenne est un nombre (note/20 ou NaN.
|
||||
|
||||
Le masque modimpl_mask est un tableau de booléens (un par modimpl) qui
|
||||
permet de sélectionner un sous-ensemble de modules (ceux de la matière d'intérêt).
|
||||
|
||||
sem_matrix: notes moyennes aux modules (tous les étuds x tous les modimpls)
|
||||
ndarray (etuds x modimpls)
|
||||
(floats avec des NaN)
|
||||
etuds : listes des étudiants (dim. 0 de la matrice)
|
||||
modimpl_inscr_df: matrice d'inscription du semestre (etud x modimpl)
|
||||
modimpl_coefs: vecteur des coefficients de modules
|
||||
modimpl_mask: masque des modimpls à prendre en compte
|
||||
|
||||
Résultat:
|
||||
- moyennes: pd.Series, index etudid
|
||||
"""
|
||||
if (not len(modimpl_mask)) or (
|
||||
sem_matrix.shape[0] == 0
|
||||
): # aucun module ou aucun étudiant
|
||||
# etud_moy_gen_s, etud_moy_ue_df, etud_coef_ue_df
|
||||
return pd.Series(
|
||||
[0.0] * len(modimpl_inscr_df.index), index=modimpl_inscr_df.index
|
||||
)
|
||||
# Restreint aux modules sélectionnés:
|
||||
sem_matrix = sem_matrix[:, modimpl_mask]
|
||||
modimpl_inscr = modimpl_inscr_df.values[:, modimpl_mask]
|
||||
modimpl_coefs = modimpl_coefs[modimpl_mask]
|
||||
|
||||
nb_etuds, nb_modules = sem_matrix.shape
|
||||
assert len(modimpl_coefs) == nb_modules
|
||||
|
||||
# Enlève les NaN du numérateur:
|
||||
sem_matrix_no_nan = np.nan_to_num(sem_matrix, nan=0.0)
|
||||
# Ne prend pas en compte les notes des étudiants non inscrits au module:
|
||||
# Annule les notes:
|
||||
sem_matrix_inscrits = np.where(modimpl_inscr, sem_matrix_no_nan, 0.0)
|
||||
# Annule les coefs des modules où l'étudiant n'est pas inscrit:
|
||||
modimpl_coefs_etuds = np.where(
|
||||
modimpl_inscr, np.stack([modimpl_coefs.T] * nb_etuds), 0.0
|
||||
)
|
||||
# Annule les coefs des modules NaN (nb_etuds x nb_mods)
|
||||
modimpl_coefs_etuds_no_nan = np.where(
|
||||
np.isnan(sem_matrix), 0.0, modimpl_coefs_etuds
|
||||
)
|
||||
if modimpl_coefs_etuds_no_nan.dtype == np.object: # arrive sur des tableaux vides
|
||||
modimpl_coefs_etuds_no_nan = modimpl_coefs_etuds_no_nan.astype(np.float)
|
||||
|
||||
etud_moy_mat = (modimpl_coefs_etuds_no_nan * sem_matrix_inscrits).sum(
|
||||
axis=1
|
||||
) / modimpl_coefs_etuds_no_nan.sum(axis=1)
|
||||
|
||||
return pd.Series(etud_moy_mat, index=modimpl_inscr_df.index)
|
||||
|
||||
|
||||
def compute_malus(
|
||||
formsemestre: FormSemestre,
|
||||
sem_modimpl_moys: np.array,
|
||||
|
@ -8,11 +8,13 @@
|
||||
"""
|
||||
from flask import g
|
||||
|
||||
from app import db
|
||||
from app.comp.jury import ValidationsSemestre
|
||||
from app.comp.res_common import ResultatsSemestre
|
||||
from app.comp.res_classic import ResultatsSemestreClassic
|
||||
from app.comp.res_but import ResultatsSemestreBUT
|
||||
from app.models.formsemestre import FormSemestre
|
||||
from app.scodoc import sco_cache
|
||||
|
||||
|
||||
def load_formsemestre_results(formsemestre: FormSemestre) -> ResultatsSemestre:
|
||||
@ -23,6 +25,13 @@ def load_formsemestre_results(formsemestre: FormSemestre) -> ResultatsSemestre:
|
||||
Search in local cache (g.formsemestre_result_cache)
|
||||
If not in cache, build it and cache it.
|
||||
"""
|
||||
is_apc = formsemestre.formation.is_apc()
|
||||
if is_apc and formsemestre.semestre_id == -1:
|
||||
formsemestre.semestre_id = 1
|
||||
db.session.add(formsemestre)
|
||||
db.session.commit()
|
||||
sco_cache.invalidate_formsemestre(formsemestre.id)
|
||||
|
||||
# --- Try local cache (within the same request context)
|
||||
if not hasattr(g, "formsemestre_results_cache"):
|
||||
g.formsemestre_results_cache = {}
|
||||
@ -30,11 +39,7 @@ def load_formsemestre_results(formsemestre: FormSemestre) -> ResultatsSemestre:
|
||||
if formsemestre.id in g.formsemestre_results_cache:
|
||||
return g.formsemestre_results_cache[formsemestre.id]
|
||||
|
||||
klass = (
|
||||
ResultatsSemestreBUT
|
||||
if formsemestre.formation.is_apc()
|
||||
else ResultatsSemestreClassic
|
||||
)
|
||||
klass = ResultatsSemestreBUT if is_apc else ResultatsSemestreClassic
|
||||
g.formsemestre_results_cache[formsemestre.id] = klass(formsemestre)
|
||||
return g.formsemestre_results_cache[formsemestre.id]
|
||||
|
||||
|
@ -33,17 +33,12 @@
|
||||
"""
|
||||
|
||||
|
||||
# API ScoDoc8 pour les caches:
|
||||
# sco_cache.NotesTableCache.get( formsemestre_id)
|
||||
# => sco_cache.NotesTableCache.get(formsemestre_id)
|
||||
# API pour les caches:
|
||||
# sco_cache.MyCache.get( formsemestre_id)
|
||||
# => sco_cache.MyCache.get(formsemestre_id)
|
||||
#
|
||||
# sco_core.inval_cache(formsemestre_id=None, pdfonly=False, formsemestre_id_list=None)
|
||||
# => deprecated, NotesTableCache.invalidate_formsemestre(formsemestre_id=None, pdfonly=False)
|
||||
#
|
||||
#
|
||||
# Nouvelles fonctions:
|
||||
# sco_cache.NotesTableCache.delete(formsemestre_id)
|
||||
# sco_cache.NotesTableCache.delete_many(formsemestre_id_list)
|
||||
# sco_cache.MyCache.delete(formsemestre_id)
|
||||
# sco_cache.MyCache.delete_many(formsemestre_id_list)
|
||||
#
|
||||
# Bulletins PDF:
|
||||
# sco_cache.SemBulletinsPDFCache.get(formsemestre_id, version)
|
||||
@ -203,49 +198,6 @@ class SemInscriptionsCache(ScoDocCache):
|
||||
duration = 12 * 60 * 60 # ttl 12h
|
||||
|
||||
|
||||
class NotesTableCache(ScoDocCache):
|
||||
"""Cache pour les NotesTable
|
||||
Clé: formsemestre_id
|
||||
Valeur: NotesTable instance
|
||||
"""
|
||||
|
||||
prefix = "NT"
|
||||
|
||||
@classmethod
|
||||
def get(cls, formsemestre_id, compute=True):
|
||||
"""Returns NotesTable for this formsemestre
|
||||
Search in local cache (g.nt_cache) or global app cache (eg REDIS)
|
||||
If not in cache:
|
||||
If compute is True, build it and cache it
|
||||
Else return None
|
||||
"""
|
||||
# try local cache (same request)
|
||||
if not hasattr(g, "nt_cache"):
|
||||
g.nt_cache = {}
|
||||
else:
|
||||
if formsemestre_id in g.nt_cache:
|
||||
return g.nt_cache[formsemestre_id]
|
||||
# try REDIS
|
||||
key = cls._get_key(formsemestre_id)
|
||||
nt = CACHE.get(key)
|
||||
if nt:
|
||||
g.nt_cache[formsemestre_id] = nt # cache locally (same request)
|
||||
return nt
|
||||
if not compute:
|
||||
return None
|
||||
# Recompute requested table:
|
||||
from app.scodoc import notes_table
|
||||
|
||||
t0 = time.time()
|
||||
nt = notes_table.NotesTable(formsemestre_id)
|
||||
t1 = time.time()
|
||||
_ = cls.set(formsemestre_id, nt) # cache in REDIS
|
||||
t2 = time.time()
|
||||
log(f"cached formsemestre_id={formsemestre_id} ({(t1-t0):g}s +{(t2-t1):g}s)")
|
||||
g.nt_cache[formsemestre_id] = nt
|
||||
return nt
|
||||
|
||||
|
||||
def invalidate_formsemestre( # was inval_cache(formsemestre_id=None, pdfonly=False)
|
||||
formsemestre_id=None, pdfonly=False
|
||||
):
|
||||
@ -278,22 +230,24 @@ def invalidate_formsemestre( # was inval_cache(formsemestre_id=None, pdfonly=Fa
|
||||
|
||||
if not pdfonly:
|
||||
# Delete cached notes and evaluations
|
||||
NotesTableCache.delete_many(formsemestre_ids)
|
||||
if formsemestre_id:
|
||||
for fid in formsemestre_ids:
|
||||
EvaluationCache.invalidate_sem(fid)
|
||||
if hasattr(g, "nt_cache") and fid in g.nt_cache:
|
||||
del g.nt_cache[fid]
|
||||
if (
|
||||
hasattr(g, "formsemestre_results_cache")
|
||||
and fid in g.formsemestre_results_cache
|
||||
):
|
||||
del g.formsemestre_results_cache[fid]
|
||||
|
||||
else:
|
||||
# optimization when we invalidate all evaluations:
|
||||
EvaluationCache.invalidate_all_sems()
|
||||
if hasattr(g, "nt_cache"):
|
||||
del g.nt_cache
|
||||
if hasattr(g, "formsemestre_results_cache"):
|
||||
del g.formsemestre_results_cache
|
||||
SemInscriptionsCache.delete_many(formsemestre_ids)
|
||||
|
||||
ResultatsSemestreCache.delete_many(formsemestre_ids)
|
||||
ValidationsSemestreCache.delete_many(formsemestre_ids)
|
||||
SemBulletinsPDFCache.invalidate_sems(formsemestre_ids)
|
||||
ResultatsSemestreCache.delete_many(formsemestre_ids)
|
||||
ValidationsSemestreCache.delete_many(formsemestre_ids)
|
||||
|
||||
|
||||
class DefferedSemCacheManager:
|
||||
|
@ -51,6 +51,7 @@ import fcntl
|
||||
import subprocess
|
||||
import requests
|
||||
|
||||
from flask import flash
|
||||
from flask_login import current_user
|
||||
|
||||
import app.scodoc.notesdb as ndb
|
||||
@ -124,6 +125,7 @@ def sco_dump_and_send_db():
|
||||
fcntl.flock(x, fcntl.LOCK_UN)
|
||||
|
||||
log("sco_dump_and_send_db: done.")
|
||||
flash("Données envoyées au serveur d'assistance")
|
||||
return "\n".join(H) + html_sco_header.sco_footer()
|
||||
|
||||
|
||||
|
@ -78,7 +78,7 @@ def formsemestre_createwithmodules():
|
||||
H = [
|
||||
html_sco_header.sco_header(
|
||||
page_title="Création d'un semestre",
|
||||
javascripts=["libjs/AutoSuggest.js"],
|
||||
javascripts=["libjs/AutoSuggest.js", "js/formsemestre_edit.js"],
|
||||
cssstyles=["css/autosuggest_inquisitor.css"],
|
||||
bodyOnLoad="init_tf_form('')",
|
||||
),
|
||||
@ -99,7 +99,7 @@ def formsemestre_editwithmodules(formsemestre_id):
|
||||
H = [
|
||||
html_sco_header.html_sem_header(
|
||||
"Modification du semestre",
|
||||
javascripts=["libjs/AutoSuggest.js"],
|
||||
javascripts=["libjs/AutoSuggest.js", "js/formsemestre_edit.js"],
|
||||
cssstyles=["css/autosuggest_inquisitor.css"],
|
||||
bodyOnLoad="init_tf_form('')",
|
||||
)
|
||||
@ -213,7 +213,10 @@ def do_formsemestre_createwithmodules(edit=False):
|
||||
# en APC, ne permet pas de changer de semestre
|
||||
semestre_id_list = [formsemestre.semestre_id]
|
||||
else:
|
||||
semestre_id_list = [-1] + list(range(1, NB_SEM + 1))
|
||||
semestre_id_list = list(range(1, NB_SEM + 1))
|
||||
if not formation.is_apc():
|
||||
# propose "pas de semestre" seulement en classique
|
||||
semestre_id_list.insert(0, -1)
|
||||
|
||||
semestre_id_labels = []
|
||||
for sid in semestre_id_list:
|
||||
@ -341,6 +344,9 @@ def do_formsemestre_createwithmodules(edit=False):
|
||||
"explanation": "en BUT, on ne peut pas modifier le semestre après création"
|
||||
if formation.is_apc()
|
||||
else "",
|
||||
"attributes": ['onchange="change_semestre_id();"']
|
||||
if formation.is_apc()
|
||||
else "",
|
||||
},
|
||||
),
|
||||
)
|
||||
@ -493,7 +499,8 @@ def do_formsemestre_createwithmodules(edit=False):
|
||||
{
|
||||
"input_type": "boolcheckbox",
|
||||
"title": "",
|
||||
"explanation": "Autoriser tous les enseignants associés à un module à y créer des évaluations",
|
||||
"explanation": """Autoriser tous les enseignants associés
|
||||
à un module à y créer des évaluations""",
|
||||
},
|
||||
),
|
||||
(
|
||||
@ -534,11 +541,19 @@ def do_formsemestre_createwithmodules(edit=False):
|
||||
]
|
||||
|
||||
nbmod = 0
|
||||
if edit:
|
||||
templ_sep = "<tr><td>%(label)s</td><td><b>Responsable</b></td><td><b>Inscrire</b></td></tr>"
|
||||
else:
|
||||
templ_sep = "<tr><td>%(label)s</td><td><b>Responsable</b></td></tr>"
|
||||
|
||||
for semestre_id in semestre_ids:
|
||||
if formation.is_apc():
|
||||
# pour restreindre l'édition aux module du semestre sélectionné
|
||||
tr_class = 'class="sem{semestre_id}"'
|
||||
else:
|
||||
tr_class = ""
|
||||
if edit:
|
||||
templ_sep = f"""<tr {tr_class}><td>%(label)s</td><td><b>Responsable</b></td><td><b>Inscrire</b></td></tr>"""
|
||||
else:
|
||||
templ_sep = (
|
||||
f"""<tr {tr_class}><td>%(label)s</td><td><b>Responsable</b></td></tr>"""
|
||||
)
|
||||
modform.append(
|
||||
(
|
||||
"sep",
|
||||
@ -588,12 +603,12 @@ def do_formsemestre_createwithmodules(edit=False):
|
||||
)
|
||||
fcg += "</select>"
|
||||
itemtemplate = (
|
||||
"""<tr><td class="tf-fieldlabel">%(label)s</td><td class="tf-field">%(elem)s</td><td>"""
|
||||
f"""<tr {tr_class}><td class="tf-fieldlabel">%(label)s</td><td class="tf-field">%(elem)s</td><td>"""
|
||||
+ fcg
|
||||
+ "</td></tr>"
|
||||
)
|
||||
else:
|
||||
itemtemplate = """<tr><td class="tf-fieldlabel">%(label)s</td><td class="tf-field">%(elem)s</td></tr>"""
|
||||
itemtemplate = f"""<tr {tr_class}><td class="tf-fieldlabel">%(label)s</td><td class="tf-field">%(elem)s</td></tr>"""
|
||||
modform.append(
|
||||
(
|
||||
"MI" + str(mod["module_id"]),
|
||||
|
@ -987,7 +987,6 @@ Il y a des notes en attente ! Le classement des étudiants n'a qu'une valeur ind
|
||||
def formsemestre_status(formsemestre_id=None):
|
||||
"""Tableau de bord semestre HTML"""
|
||||
# porté du DTML
|
||||
cnx = ndb.GetDBConnexion()
|
||||
sem = sco_formsemestre.get_formsemestre(formsemestre_id, raise_soft_exc=True)
|
||||
modimpls = sco_moduleimpl.moduleimpl_withmodule_list(
|
||||
formsemestre_id=formsemestre_id
|
||||
|
@ -1297,7 +1297,7 @@ th.formsemestre_status_inscrits {
|
||||
text-align: center;
|
||||
}
|
||||
td.formsemestre_status_code {
|
||||
width: 2em;
|
||||
/* width: 2em; */
|
||||
padding-right: 1em;
|
||||
}
|
||||
|
||||
|
14
app/static/js/formsemestre_edit.js
Normal file
14
app/static/js/formsemestre_edit.js
Normal file
@ -0,0 +1,14 @@
|
||||
// Formulaire formsemestre_createwithmodules
|
||||
|
||||
function change_semestre_id() {
|
||||
var semestre_id = $("#tf_semestre_id")[0].value;
|
||||
for (var i = -1; i < 12; i++) {
|
||||
$(".sem" + i).hide();
|
||||
}
|
||||
$(".sem" + semestre_id).show();
|
||||
}
|
||||
|
||||
|
||||
$(window).on('load', function () {
|
||||
change_semestre_id();
|
||||
});
|
Loading…
Reference in New Issue
Block a user