WIP: validations d'UE et de semestres
This commit is contained in:
parent
5467ad2437
commit
e6bd6cf28a
147
app/comp/jury.py
Normal file
147
app/comp/jury.py
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
##############################################################################
|
||||||
|
# ScoDoc
|
||||||
|
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||||
|
# See LICENSE
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
"""Stockage des décisions de jury
|
||||||
|
"""
|
||||||
|
import pandas as pd
|
||||||
|
|
||||||
|
from app import db
|
||||||
|
from app.models import FormSemestre, ScolarFormSemestreValidation
|
||||||
|
from app.comp.res_cache import ResultatsCache
|
||||||
|
from app.scodoc import sco_cache
|
||||||
|
from app.scodoc import sco_codes_parcours
|
||||||
|
|
||||||
|
|
||||||
|
class ValidationsSemestre(ResultatsCache):
|
||||||
|
""" """
|
||||||
|
|
||||||
|
_cached_attrs = (
|
||||||
|
"decisions_jury",
|
||||||
|
"decisions_jury_ues",
|
||||||
|
"ue_capitalisees",
|
||||||
|
)
|
||||||
|
|
||||||
|
def __init__(self, formsemestre: FormSemestre):
|
||||||
|
super().__init__(formsemestre, sco_cache.ValidationsSemestreCache)
|
||||||
|
|
||||||
|
self.decisions_jury = {}
|
||||||
|
"""Décisions prises dans ce semestre:
|
||||||
|
{ etudid : { 'code' : None|ATT|..., 'assidu' : 0|1 }}"""
|
||||||
|
self.decisions_jury_ues = {}
|
||||||
|
"""Décisions sur des UEs dans ce semestre:
|
||||||
|
{ etudid : { ue_id : { 'code' : Note|ADM|CMP, 'event_date' }}}
|
||||||
|
"""
|
||||||
|
|
||||||
|
if not self.load_cached():
|
||||||
|
self.compute()
|
||||||
|
self.store()
|
||||||
|
|
||||||
|
def compute(self):
|
||||||
|
"Charge les résultats de jury et UEs capitalisées"
|
||||||
|
self.ue_capitalisees = formsemestre_get_ue_capitalisees(self.formsemestre)
|
||||||
|
self.comp_decisions_jury()
|
||||||
|
|
||||||
|
def comp_decisions_jury(self):
|
||||||
|
"""Cherche les decisions du jury pour le semestre (pas les UE).
|
||||||
|
Calcule les attributs:
|
||||||
|
decisions_jury = { etudid : { 'code' : None|ATT|..., 'assidu' : 0|1 }}
|
||||||
|
decision_jury_ues={ etudid : { ue_id : { 'code' : Note|ADM|CMP, 'event_date' }}}
|
||||||
|
Si la décision n'a pas été prise, la clé etudid n'est pas présente.
|
||||||
|
Si l'étudiant est défaillant, pas de décisions d'UE.
|
||||||
|
"""
|
||||||
|
# repris de NotesTable.comp_decisions_jury pour la compatibilité
|
||||||
|
decisions_jury_q = ScolarFormSemestreValidation.query.filter_by(
|
||||||
|
formsemestre_id=self.formsemestre.id
|
||||||
|
)
|
||||||
|
decisions_jury = {}
|
||||||
|
for decision in decisions_jury_q.filter(
|
||||||
|
ScolarFormSemestreValidation.ue_id == None # slt dec. sem.
|
||||||
|
):
|
||||||
|
decisions_jury[decision.etudid] = {
|
||||||
|
"code": decision.code,
|
||||||
|
"assidu": decision.assidu,
|
||||||
|
"compense_formsemestre_id": decision.compense_formsemestre_id,
|
||||||
|
"event_date": decision.event_date.strftime("%d/%m/%Y"),
|
||||||
|
}
|
||||||
|
self.decisions_jury = decisions_jury
|
||||||
|
|
||||||
|
# UEs:
|
||||||
|
decisions_jury_ues = {}
|
||||||
|
for decision in decisions_jury_q.filter(
|
||||||
|
ScolarFormSemestreValidation.ue_id != None # slt dec. sem.
|
||||||
|
):
|
||||||
|
if decision.etudid not in decisions_jury_ues:
|
||||||
|
decisions_jury_ues[decision.etudid] = {}
|
||||||
|
# Calcul des ECTS associés à cette UE:
|
||||||
|
if sco_codes_parcours.code_ue_validant(decision.code):
|
||||||
|
ects = decision.ue.ects or 0.0 # 0 if None
|
||||||
|
else:
|
||||||
|
ects = 0.0
|
||||||
|
|
||||||
|
decisions_jury_ues[decision.etudid][decision.ue.id] = {
|
||||||
|
"code": decision.code,
|
||||||
|
"ects": ects, # 0. si UE non validée
|
||||||
|
"event_date": decision.event_date.strftime("%d/%m/%Y"),
|
||||||
|
}
|
||||||
|
|
||||||
|
self.decisions_jury_ues = decisions_jury_ues
|
||||||
|
|
||||||
|
|
||||||
|
def formsemestre_get_ue_capitalisees(formsemestre: FormSemestre) -> pd.DataFrame:
|
||||||
|
"""Liste des UE capitalisées (ADM) utilisables dans ce formsemestre
|
||||||
|
|
||||||
|
Recherche dans les semestres des formations de même code, avec le même semestre_id
|
||||||
|
et une date de début antérieure que celle du formsemestre.
|
||||||
|
Prend aussi les UE externes validées.
|
||||||
|
|
||||||
|
Attention: fonction très coûteuse, cacher le résultat.
|
||||||
|
|
||||||
|
Résultat: DataFrame avec
|
||||||
|
etudid (index)
|
||||||
|
formsemestre_id : origine de l'UE capitalisée
|
||||||
|
is_external : vrai si validation effectuée dans un semestre extérieur
|
||||||
|
ue_id : dans le semestre origine (pas toujours de la même formation)
|
||||||
|
ue_code : code de l'UE
|
||||||
|
moy_ue :
|
||||||
|
event_date :
|
||||||
|
} ]
|
||||||
|
"""
|
||||||
|
query = """
|
||||||
|
SELECT DISTINCT SFV.*, ue.ue_code
|
||||||
|
FROM
|
||||||
|
notes_ue ue,
|
||||||
|
notes_formations nf,
|
||||||
|
notes_formations nf2,
|
||||||
|
scolar_formsemestre_validation SFV,
|
||||||
|
notes_formsemestre sem,
|
||||||
|
notes_formsemestre_inscription ins
|
||||||
|
|
||||||
|
WHERE ue.formation_id = nf.id
|
||||||
|
and nf.formation_code = nf2.formation_code
|
||||||
|
and nf2.id=%(formation_id)s
|
||||||
|
and ins.etudid = SFV.etudid
|
||||||
|
and ins.formsemestre_id = %(formsemestre_id)s
|
||||||
|
|
||||||
|
and SFV.ue_id = ue.id
|
||||||
|
and SFV.code = 'ADM'
|
||||||
|
|
||||||
|
and ( (sem.id = SFV.formsemestre_id
|
||||||
|
and sem.date_debut < %(date_debut)s
|
||||||
|
and sem.semestre_id = %(semestre_id)s )
|
||||||
|
or (
|
||||||
|
((SFV.formsemestre_id is NULL) OR (SFV.is_external)) -- les UE externes ou "anterieures"
|
||||||
|
AND (SFV.semestre_id is NULL OR SFV.semestre_id=%(semestre_id)s)
|
||||||
|
) )
|
||||||
|
"""
|
||||||
|
params = {
|
||||||
|
"formation_id": formsemestre.formation.id,
|
||||||
|
"formsemestre_id": formsemestre.id,
|
||||||
|
"semestre_id": formsemestre.semestre_id,
|
||||||
|
"date_debut": formsemestre.date_debut,
|
||||||
|
}
|
||||||
|
|
||||||
|
df = pd.read_sql_query(query, db.engine, params=params, index_col="etudid")
|
||||||
|
return df
|
@ -77,6 +77,8 @@ class ModuleImplResults:
|
|||||||
"{ evaluation.id : bool } indique si à prendre en compte ou non."
|
"{ evaluation.id : bool } indique si à prendre en compte ou non."
|
||||||
self.evaluations_etat = {}
|
self.evaluations_etat = {}
|
||||||
"{ evaluation_id: EvaluationEtat }"
|
"{ evaluation_id: EvaluationEtat }"
|
||||||
|
self.en_attente = False
|
||||||
|
"Vrai si au moins une évaluation a une note en attente"
|
||||||
#
|
#
|
||||||
self.evals_notes = None
|
self.evals_notes = None
|
||||||
"""DataFrame, colonnes: EVALS, Lignes: etudid (inscrits au SEMESTRE)
|
"""DataFrame, colonnes: EVALS, Lignes: etudid (inscrits au SEMESTRE)
|
||||||
@ -133,7 +135,7 @@ class ModuleImplResults:
|
|||||||
evals_notes = pd.DataFrame(index=self.etudids, dtype=float)
|
evals_notes = pd.DataFrame(index=self.etudids, dtype=float)
|
||||||
self.evaluations_completes = []
|
self.evaluations_completes = []
|
||||||
self.evaluations_completes_dict = {}
|
self.evaluations_completes_dict = {}
|
||||||
|
self.en_attente = False
|
||||||
for evaluation in moduleimpl.evaluations:
|
for evaluation in moduleimpl.evaluations:
|
||||||
eval_df = self._load_evaluation_notes(evaluation)
|
eval_df = self._load_evaluation_notes(evaluation)
|
||||||
# is_complete ssi tous les inscrits (non dem) au semestre ont une note
|
# is_complete ssi tous les inscrits (non dem) au semestre ont une note
|
||||||
@ -160,6 +162,8 @@ class ModuleImplResults:
|
|||||||
self.evaluations_etat[evaluation.id] = EvaluationEtat(
|
self.evaluations_etat[evaluation.id] = EvaluationEtat(
|
||||||
evaluation_id=evaluation.id, nb_attente=nb_att, is_complete=is_complete
|
evaluation_id=evaluation.id, nb_attente=nb_att, is_complete=is_complete
|
||||||
)
|
)
|
||||||
|
if nb_att > 0:
|
||||||
|
self.en_attente = True
|
||||||
|
|
||||||
# Force columns names to integers (evaluation ids)
|
# Force columns names to integers (evaluation ids)
|
||||||
evals_notes.columns = pd.Int64Index(
|
evals_notes.columns = pd.Int64Index(
|
||||||
@ -209,6 +213,13 @@ class ModuleImplResults:
|
|||||||
* self.evaluations_completes
|
* self.evaluations_completes
|
||||||
).reshape(-1, 1)
|
).reshape(-1, 1)
|
||||||
|
|
||||||
|
# was _list_notes_evals_titles
|
||||||
|
def get_evaluations_completes(self, moduleimpl: ModuleImpl) -> list:
|
||||||
|
"Liste des évaluations complètes"
|
||||||
|
return [
|
||||||
|
e for e in moduleimpl.evaluations if self.evaluations_completes_dict[e.id]
|
||||||
|
]
|
||||||
|
|
||||||
def get_eval_notes_sur_20(self, moduleimpl: ModuleImpl) -> np.array:
|
def get_eval_notes_sur_20(self, moduleimpl: ModuleImpl) -> np.array:
|
||||||
"""Les notes des évaluations,
|
"""Les notes des évaluations,
|
||||||
remplace les ATT, EXC, ABS, NaN par zéro et mets les notes sur 20.
|
remplace les ATT, EXC, ABS, NaN par zéro et mets les notes sur 20.
|
||||||
|
34
app/comp/res_cache.py
Normal file
34
app/comp/res_cache.py
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
##############################################################################
|
||||||
|
# ScoDoc
|
||||||
|
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||||
|
# See LICENSE
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
"""Cache pour résultats (super classe)
|
||||||
|
"""
|
||||||
|
|
||||||
|
from app.models import FormSemestre
|
||||||
|
|
||||||
|
|
||||||
|
class ResultatsCache:
|
||||||
|
_cached_attrs = () # virtual
|
||||||
|
|
||||||
|
def __init__(self, formsemestre: FormSemestre, cache_class=None):
|
||||||
|
self.formsemestre: FormSemestre = formsemestre
|
||||||
|
self.cache_class = cache_class
|
||||||
|
|
||||||
|
def load_cached(self) -> bool:
|
||||||
|
"Load cached dataframes, returns False si pas en cache"
|
||||||
|
data = self.cache_class.get(self.formsemestre.id)
|
||||||
|
if not data:
|
||||||
|
return False
|
||||||
|
for attr in self._cached_attrs:
|
||||||
|
setattr(self, attr, data[attr])
|
||||||
|
return True
|
||||||
|
|
||||||
|
def store(self):
|
||||||
|
"Cache our data"
|
||||||
|
self.cache_class.set(
|
||||||
|
self.formsemestre.id,
|
||||||
|
{attr: getattr(self, attr) for attr in self._cached_attrs},
|
||||||
|
)
|
@ -9,10 +9,13 @@ 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_stats import StatsMoyenne
|
from app.comp.aux_stats import StatsMoyenne
|
||||||
|
from app.comp.res_cache import ResultatsCache
|
||||||
|
from app.comp import res_sem
|
||||||
from app.comp.moy_mod import ModuleImplResults
|
from app.comp.moy_mod import ModuleImplResults
|
||||||
from app.models import FormSemestre, Identite, 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 import sco_evaluations
|
||||||
from app.scodoc.sco_cache import ResultatsSemestreCache
|
from app.scodoc.sco_cache import ResultatsSemestreCache
|
||||||
from app.scodoc.sco_codes_parcours import UE_SPORT, ATT, DEF
|
from app.scodoc.sco_codes_parcours import UE_SPORT, ATT, DEF
|
||||||
|
|
||||||
@ -25,7 +28,7 @@ from app.scodoc.sco_codes_parcours import UE_SPORT, ATT, DEF
|
|||||||
# (durée de vie de l'instance de ResultatsSemestre)
|
# (durée de vie de l'instance de ResultatsSemestre)
|
||||||
# qui sont notamment les attributs décorés par `@cached_property``
|
# qui sont notamment les attributs décorés par `@cached_property``
|
||||||
#
|
#
|
||||||
class ResultatsSemestre:
|
class ResultatsSemestre(ResultatsCache):
|
||||||
_cached_attrs = (
|
_cached_attrs = (
|
||||||
"etud_moy_gen_ranks",
|
"etud_moy_gen_ranks",
|
||||||
"etud_moy_gen",
|
"etud_moy_gen",
|
||||||
@ -36,7 +39,7 @@ class ResultatsSemestre:
|
|||||||
)
|
)
|
||||||
|
|
||||||
def __init__(self, formsemestre: FormSemestre):
|
def __init__(self, formsemestre: FormSemestre):
|
||||||
self.formsemestre: FormSemestre = formsemestre
|
super().__init__(formsemestre, ResultatsSemestreCache)
|
||||||
# BUT ou standard ? (apc == "approche par compétences")
|
# BUT ou standard ? (apc == "approche par compétences")
|
||||||
self.is_apc = formsemestre.formation.is_apc()
|
self.is_apc = formsemestre.formation.is_apc()
|
||||||
# Attributs "virtuels", définis dans les sous-classes
|
# Attributs "virtuels", définis dans les sous-classes
|
||||||
@ -46,26 +49,9 @@ 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
|
||||||
|
"Résultats de chaque modimpl: dict { modimpl.id : ModuleImplResults(Classique ou BUT) }"
|
||||||
self.etud_coef_ue_df = None
|
self.etud_coef_ue_df = None
|
||||||
"""coefs d'UE effectifs pour chaque etudiant (pour form. classiques)"""
|
"""coefs d'UE effectifs pour chaque étudiant (pour form. classiques)"""
|
||||||
|
|
||||||
# TODO ?
|
|
||||||
|
|
||||||
def load_cached(self) -> bool:
|
|
||||||
"Load cached dataframes, returns False si pas en cache"
|
|
||||||
data = ResultatsSemestreCache.get(self.formsemestre.id)
|
|
||||||
if not data:
|
|
||||||
return False
|
|
||||||
for attr in self._cached_attrs:
|
|
||||||
setattr(self, attr, data[attr])
|
|
||||||
return True
|
|
||||||
|
|
||||||
def store(self):
|
|
||||||
"Cache our data"
|
|
||||||
ResultatsSemestreCache.set(
|
|
||||||
self.formsemestre.id,
|
|
||||||
{attr: getattr(self, attr) for attr in self._cached_attrs},
|
|
||||||
)
|
|
||||||
|
|
||||||
def compute(self):
|
def compute(self):
|
||||||
"Charge les notes et inscriptions et calcule toutes les moyennes"
|
"Charge les notes et inscriptions et calcule toutes les moyennes"
|
||||||
@ -101,7 +87,8 @@ class ResultatsSemestre:
|
|||||||
@cached_property
|
@cached_property
|
||||||
def ues(self) -> list[UniteEns]:
|
def ues(self) -> list[UniteEns]:
|
||||||
"""Liste des UEs du semestre (avec les UE bonus sport)
|
"""Liste des UEs du semestre (avec les UE bonus sport)
|
||||||
(indices des DataFrames)
|
(indices des DataFrames).
|
||||||
|
Note: un étudiant n'est pas nécessairement inscrit dans toutes ces UEs.
|
||||||
"""
|
"""
|
||||||
return self.formsemestre.query_ues(with_sport=True).all()
|
return self.formsemestre.query_ues(with_sport=True).all()
|
||||||
|
|
||||||
@ -123,15 +110,34 @@ class ResultatsSemestre:
|
|||||||
if m.module.module_type == scu.ModuleType.SAE
|
if m.module.module_type == scu.ModuleType.SAE
|
||||||
]
|
]
|
||||||
|
|
||||||
@cached_property
|
def get_etud_ue_validables(self, etudid: int) -> list[UniteEns]:
|
||||||
def ue_validables(self) -> list:
|
"""Liste des UEs du semestre qui doivent être validées
|
||||||
"""Liste des UE du semestre qui doivent être validées
|
|
||||||
(toutes sauf le sport)
|
|
||||||
"""
|
|
||||||
return self.formsemestre.query_ues().filter(UniteEns.type != UE_SPORT).all()
|
|
||||||
|
|
||||||
def modimpls_in_ue(self, ue_id, etudid):
|
Rappel: l'étudiant est inscrit à des modimpls et non à des UEs.
|
||||||
"""Liste des modimpl de cet ue auxquels l'étudiant est inscrit"""
|
|
||||||
|
- En BUT: on considère que l'étudiant va (ou non) valider toutes les UEs des modules
|
||||||
|
du parcours. XXX notion à implémenter, pour l'instant toutes les UE du semestre.
|
||||||
|
|
||||||
|
- En classique: toutes les UEs des modimpls auxquels l'étufdiant est inscrit sont
|
||||||
|
susceptibles d'être validées.
|
||||||
|
|
||||||
|
Les UE "bonus" (sport) ne sont jamais "validables".
|
||||||
|
"""
|
||||||
|
if self.is_apc:
|
||||||
|
# TODO: introduire la notion de parcours (#sco93)
|
||||||
|
return self.formsemestre.query_ues().filter(UniteEns.type != UE_SPORT).all()
|
||||||
|
else:
|
||||||
|
# restreint aux UE auxquelles l'étudiant est inscrit (dans l'un des modimpls)
|
||||||
|
ues = {
|
||||||
|
modimpl.module.ue
|
||||||
|
for modimpl in self.formsemestre.modimpls_sorted
|
||||||
|
if self.modimpl_inscr_df[modimpl.id][etudid]
|
||||||
|
}
|
||||||
|
ues = sorted(list(ues), key=lambda x: x.numero or 0)
|
||||||
|
return ues
|
||||||
|
|
||||||
|
def modimpls_in_ue(self, ue_id, etudid) -> list[ModuleImpl]:
|
||||||
|
"""Liste des modimpl de cette UE auxquels l'étudiant est inscrit"""
|
||||||
# sert pour l'affichage ou non de l'UE sur le bulletin
|
# sert pour l'affichage ou non de l'UE sur le bulletin
|
||||||
return [
|
return [
|
||||||
modimpl
|
modimpl
|
||||||
@ -180,6 +186,7 @@ class NotesTableCompat(ResultatsSemestre):
|
|||||||
self.moy_moy = "NA"
|
self.moy_moy = "NA"
|
||||||
self.expr_diagnostics = ""
|
self.expr_diagnostics = ""
|
||||||
self.parcours = self.formsemestre.formation.get_parcours()
|
self.parcours = self.formsemestre.formation.get_parcours()
|
||||||
|
self.validations = None
|
||||||
|
|
||||||
def get_etudids(self, sorted=False) -> list[int]:
|
def get_etudids(self, sorted=False) -> list[int]:
|
||||||
"""Liste des etudids inscrits, incluant les démissionnaires.
|
"""Liste des etudids inscrits, incluant les démissionnaires.
|
||||||
@ -243,6 +250,21 @@ class NotesTableCompat(ResultatsSemestre):
|
|||||||
modimpls_dict.append(d)
|
modimpls_dict.append(d)
|
||||||
return modimpls_dict
|
return modimpls_dict
|
||||||
|
|
||||||
|
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:
|
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.
|
"""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 }
|
{ 'code' : None|ATT|..., 'assidu' : 0|1, 'event_date' : , compense_formsemestre_id }
|
||||||
@ -256,12 +278,11 @@ class NotesTableCompat(ResultatsSemestre):
|
|||||||
"compense_formsemestre_id": None,
|
"compense_formsemestre_id": None,
|
||||||
}
|
}
|
||||||
else:
|
else:
|
||||||
return {
|
if not self.validations:
|
||||||
"code": ATT, # XXX TODO
|
self.validations = res_sem.load_formsemestre_validations(
|
||||||
"assidu": True, # XXX TODO
|
self.formsemestre
|
||||||
"event_date": "",
|
)
|
||||||
"compense_formsemestre_id": None,
|
return self.validations.decisions_jury.get(etudid, None)
|
||||||
}
|
|
||||||
|
|
||||||
def get_etud_etat(self, etudid: int) -> str:
|
def get_etud_etat(self, etudid: int) -> str:
|
||||||
"Etat de l'etudiant: 'I', 'D', DEF ou '' (si pas connu dans ce semestre)"
|
"Etat de l'etudiant: 'I', 'D', DEF ou '' (si pas connu dans ce semestre)"
|
||||||
@ -290,6 +311,31 @@ class NotesTableCompat(ResultatsSemestre):
|
|||||||
"""
|
"""
|
||||||
return self.etud_moy_gen[etudid]
|
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_fond": 0.0, # not implemented (anciennemment pour école ingé)
|
||||||
|
}
|
||||||
|
|
||||||
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]
|
coef_ue = self.etud_coef_ue_df[ue_id][etudid]
|
||||||
return {
|
return {
|
||||||
@ -333,8 +379,32 @@ class NotesTableCompat(ResultatsSemestre):
|
|||||||
evals_results.append(d)
|
evals_results.append(d)
|
||||||
return evals_results
|
return evals_results
|
||||||
|
|
||||||
|
def get_evaluations_etats(self):
|
||||||
|
"""[ {...evaluation et son etat...} ]"""
|
||||||
|
# TODO: à moderniser
|
||||||
|
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):
|
def get_moduleimpls_attente(self):
|
||||||
return [] # XXX TODO
|
"""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:
|
def get_mod_stats(self, moduleimpl_id: int) -> dict:
|
||||||
"""Stats sur les notes obtenues dans un modimpl
|
"""Stats sur les notes obtenues dans un modimpl
|
||||||
|
@ -8,31 +8,49 @@
|
|||||||
"""
|
"""
|
||||||
from flask import g
|
from flask import g
|
||||||
|
|
||||||
|
from app.comp.jury import ValidationsSemestre
|
||||||
from app.comp.res_common import ResultatsSemestre
|
from app.comp.res_common import ResultatsSemestre
|
||||||
from app.comp.res_classic import ResultatsSemestreClassic
|
from app.comp.res_classic import ResultatsSemestreClassic
|
||||||
from app.comp.res_but import ResultatsSemestreBUT
|
from app.comp.res_but import ResultatsSemestreBUT
|
||||||
from app.models.formsemestre import FormSemestre
|
from app.models.formsemestre import FormSemestre
|
||||||
|
|
||||||
|
|
||||||
def load_formsemestre_result(formsemestre: FormSemestre) -> ResultatsSemestre:
|
def load_formsemestre_results(formsemestre: FormSemestre) -> ResultatsSemestre:
|
||||||
"""Returns ResultatsSemestre for this formsemestre.
|
"""Returns ResultatsSemestre for this formsemestre.
|
||||||
Suivant le type de formation, retour une instance de
|
Suivant le type de formation, retour une instance de
|
||||||
ResultatsSemestreClassic ou de ResultatsSemestreBUT.
|
ResultatsSemestreClassic ou de ResultatsSemestreBUT.
|
||||||
|
|
||||||
Search in local cache (g.formsemestre_result_cache)
|
Search in local cache (g.formsemestre_result_cache)
|
||||||
then global app cache (eg REDIS)
|
|
||||||
If not in cache, build it and cache it.
|
If not in cache, build it and cache it.
|
||||||
"""
|
"""
|
||||||
# --- Try local cache (within the same request context)
|
# --- Try local cache (within the same request context)
|
||||||
if not hasattr(g, "formsemestre_result_cache"):
|
if not hasattr(g, "formsemestre_results_cache"):
|
||||||
g.formsemestre_result_cache = {} # pylint: disable=C0237
|
g.formsemestre_results_cache = {} # pylint: disable=C0237
|
||||||
else:
|
else:
|
||||||
if formsemestre.id in g.formsemestre_result_cache:
|
if formsemestre.id in g.formsemestre_results_cache:
|
||||||
return g.formsemestre_result_cache[formsemestre.id]
|
return g.formsemestre_results_cache[formsemestre.id]
|
||||||
|
|
||||||
klass = (
|
klass = (
|
||||||
ResultatsSemestreBUT
|
ResultatsSemestreBUT
|
||||||
if formsemestre.formation.is_apc()
|
if formsemestre.formation.is_apc()
|
||||||
else ResultatsSemestreClassic
|
else ResultatsSemestreClassic
|
||||||
)
|
)
|
||||||
return klass(formsemestre)
|
g.formsemestre_results_cache[formsemestre.id] = klass(formsemestre)
|
||||||
|
return g.formsemestre_results_cache[formsemestre.id]
|
||||||
|
|
||||||
|
|
||||||
|
def load_formsemestre_validations(formsemestre: FormSemestre) -> ValidationsSemestre:
|
||||||
|
"""Charge les résultats de jury de ce semestre.
|
||||||
|
Search in local cache (g.formsemestre_result_cache)
|
||||||
|
If not in cache, build it and cache it.
|
||||||
|
"""
|
||||||
|
if not hasattr(g, "formsemestre_validation_cache"):
|
||||||
|
g.formsemestre_validations_cache = {} # pylint: disable=C0237
|
||||||
|
else:
|
||||||
|
if formsemestre.id in g.formsemestre_validations_cache:
|
||||||
|
return g.formsemestre_validations_cache[formsemestre.id]
|
||||||
|
|
||||||
|
g.formsemestre_validations_cache[formsemestre.id] = ValidationsSemestre(
|
||||||
|
formsemestre
|
||||||
|
)
|
||||||
|
return g.formsemestre_validations_cache[formsemestre.id]
|
||||||
|
@ -49,13 +49,15 @@ from app.models.evaluations import (
|
|||||||
)
|
)
|
||||||
from app.models.groups import Partition, GroupDescr, group_membership
|
from app.models.groups import Partition, GroupDescr, group_membership
|
||||||
from app.models.notes import (
|
from app.models.notes import (
|
||||||
ScolarEvent,
|
|
||||||
ScolarFormSemestreValidation,
|
|
||||||
ScolarAutorisationInscription,
|
|
||||||
BulAppreciations,
|
BulAppreciations,
|
||||||
NotesNotes,
|
NotesNotes,
|
||||||
NotesNotesLog,
|
NotesNotesLog,
|
||||||
)
|
)
|
||||||
|
from app.models.validations import (
|
||||||
|
ScolarEvent,
|
||||||
|
ScolarFormSemestreValidation,
|
||||||
|
ScolarAutorisationInscription,
|
||||||
|
)
|
||||||
from app.models.preferences import ScoPreference
|
from app.models.preferences import ScoPreference
|
||||||
|
|
||||||
from app.models.but_refcomp import (
|
from app.models.but_refcomp import (
|
||||||
|
@ -158,7 +158,7 @@ class FormSemestre(db.Model):
|
|||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def modimpls_sorted(self) -> list[ModuleImpl]:
|
def modimpls_sorted(self) -> list[ModuleImpl]:
|
||||||
"""Liste des modimpls du semestre
|
"""Liste des modimpls du semestre (y compris bonus)
|
||||||
- triée par type/numéro/code en APC
|
- triée par type/numéro/code en APC
|
||||||
- triée par numéros d'UE/matières/modules pour les formations standard.
|
- triée par numéros d'UE/matières/modules pour les formations standard.
|
||||||
"""
|
"""
|
||||||
|
@ -8,100 +8,6 @@ from app.models import SHORT_STR_LEN
|
|||||||
from app.models import CODE_STR_LEN
|
from app.models import CODE_STR_LEN
|
||||||
|
|
||||||
|
|
||||||
class ScolarEvent(db.Model):
|
|
||||||
"""Evenement dans le parcours scolaire d'un étudiant"""
|
|
||||||
|
|
||||||
__tablename__ = "scolar_events"
|
|
||||||
id = db.Column(db.Integer, primary_key=True)
|
|
||||||
event_id = db.synonym("id")
|
|
||||||
etudid = db.Column(
|
|
||||||
db.Integer,
|
|
||||||
db.ForeignKey("identite.id"),
|
|
||||||
)
|
|
||||||
event_date = db.Column(db.DateTime(timezone=True), server_default=db.func.now())
|
|
||||||
formsemestre_id = db.Column(
|
|
||||||
db.Integer,
|
|
||||||
db.ForeignKey("notes_formsemestre.id"),
|
|
||||||
)
|
|
||||||
ue_id = db.Column(
|
|
||||||
db.Integer,
|
|
||||||
db.ForeignKey("notes_ue.id"),
|
|
||||||
)
|
|
||||||
# 'CREATION', 'INSCRIPTION', 'DEMISSION',
|
|
||||||
# 'AUT_RED', 'EXCLUS', 'VALID_UE', 'VALID_SEM'
|
|
||||||
# 'ECHEC_SEM'
|
|
||||||
# 'UTIL_COMPENSATION'
|
|
||||||
event_type = db.Column(db.String(SHORT_STR_LEN))
|
|
||||||
# Semestre compensé par formsemestre_id:
|
|
||||||
comp_formsemestre_id = db.Column(
|
|
||||||
db.Integer,
|
|
||||||
db.ForeignKey("notes_formsemestre.id"),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class ScolarFormSemestreValidation(db.Model):
|
|
||||||
"""Décisions de jury"""
|
|
||||||
|
|
||||||
__tablename__ = "scolar_formsemestre_validation"
|
|
||||||
# Assure unicité de la décision:
|
|
||||||
__table_args__ = (db.UniqueConstraint("etudid", "formsemestre_id", "ue_id"),)
|
|
||||||
|
|
||||||
id = db.Column(db.Integer, primary_key=True)
|
|
||||||
formsemestre_validation_id = db.synonym("id")
|
|
||||||
etudid = db.Column(
|
|
||||||
db.Integer,
|
|
||||||
db.ForeignKey("identite.id"),
|
|
||||||
index=True,
|
|
||||||
)
|
|
||||||
formsemestre_id = db.Column(
|
|
||||||
db.Integer,
|
|
||||||
db.ForeignKey("notes_formsemestre.id"),
|
|
||||||
index=True,
|
|
||||||
)
|
|
||||||
ue_id = db.Column(
|
|
||||||
db.Integer,
|
|
||||||
db.ForeignKey("notes_ue.id"),
|
|
||||||
index=True,
|
|
||||||
)
|
|
||||||
code = db.Column(db.String(CODE_STR_LEN), nullable=False, index=True)
|
|
||||||
# NULL pour les UE, True|False pour les semestres:
|
|
||||||
assidu = db.Column(db.Boolean)
|
|
||||||
event_date = db.Column(db.DateTime(timezone=True), server_default=db.func.now())
|
|
||||||
# NULL sauf si compense un semestre:
|
|
||||||
compense_formsemestre_id = db.Column(
|
|
||||||
db.Integer,
|
|
||||||
db.ForeignKey("notes_formsemestre.id"),
|
|
||||||
)
|
|
||||||
moy_ue = db.Column(db.Float)
|
|
||||||
# (normalement NULL) indice du semestre, utile seulement pour
|
|
||||||
# UE "antérieures" et si la formation définit des UE utilisées
|
|
||||||
# dans plusieurs semestres (cas R&T IUTV v2)
|
|
||||||
semestre_id = db.Column(db.Integer)
|
|
||||||
# Si UE validée dans le cursus d'un autre etablissement
|
|
||||||
is_external = db.Column(db.Boolean, default=False, server_default="false")
|
|
||||||
|
|
||||||
|
|
||||||
class ScolarAutorisationInscription(db.Model):
|
|
||||||
"""Autorisation d'inscription dans un semestre"""
|
|
||||||
|
|
||||||
__tablename__ = "scolar_autorisation_inscription"
|
|
||||||
id = db.Column(db.Integer, primary_key=True)
|
|
||||||
autorisation_inscription_id = db.synonym("id")
|
|
||||||
|
|
||||||
etudid = db.Column(
|
|
||||||
db.Integer,
|
|
||||||
db.ForeignKey("identite.id"),
|
|
||||||
)
|
|
||||||
formation_code = db.Column(db.String(SHORT_STR_LEN), nullable=False)
|
|
||||||
# semestre ou on peut s'inscrire:
|
|
||||||
semestre_id = db.Column(db.Integer)
|
|
||||||
date = db.Column(db.DateTime(timezone=True), server_default=db.func.now())
|
|
||||||
origin_formsemestre_id = db.Column(
|
|
||||||
db.Integer,
|
|
||||||
db.ForeignKey("notes_formsemestre.id"),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class BulAppreciations(db.Model):
|
class BulAppreciations(db.Model):
|
||||||
"""Appréciations sur bulletins"""
|
"""Appréciations sur bulletins"""
|
||||||
|
|
||||||
|
109
app/models/validations.py
Normal file
109
app/models/validations.py
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
# -*- coding: UTF-8 -*
|
||||||
|
|
||||||
|
"""Notes, décisions de jury, évènements scolaires
|
||||||
|
"""
|
||||||
|
|
||||||
|
from app import db
|
||||||
|
from app.models import SHORT_STR_LEN
|
||||||
|
from app.models import CODE_STR_LEN
|
||||||
|
|
||||||
|
|
||||||
|
class ScolarFormSemestreValidation(db.Model):
|
||||||
|
"""Décisions de jury"""
|
||||||
|
|
||||||
|
__tablename__ = "scolar_formsemestre_validation"
|
||||||
|
# Assure unicité de la décision:
|
||||||
|
__table_args__ = (db.UniqueConstraint("etudid", "formsemestre_id", "ue_id"),)
|
||||||
|
|
||||||
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
formsemestre_validation_id = db.synonym("id")
|
||||||
|
etudid = db.Column(
|
||||||
|
db.Integer,
|
||||||
|
db.ForeignKey("identite.id"),
|
||||||
|
index=True,
|
||||||
|
)
|
||||||
|
formsemestre_id = db.Column(
|
||||||
|
db.Integer,
|
||||||
|
db.ForeignKey("notes_formsemestre.id"),
|
||||||
|
index=True,
|
||||||
|
)
|
||||||
|
ue_id = db.Column(
|
||||||
|
db.Integer,
|
||||||
|
db.ForeignKey("notes_ue.id"),
|
||||||
|
index=True,
|
||||||
|
)
|
||||||
|
code = db.Column(db.String(CODE_STR_LEN), nullable=False, index=True)
|
||||||
|
# NULL pour les UE, True|False pour les semestres:
|
||||||
|
assidu = db.Column(db.Boolean)
|
||||||
|
event_date = db.Column(db.DateTime(timezone=True), server_default=db.func.now())
|
||||||
|
# NULL sauf si compense un semestre:
|
||||||
|
compense_formsemestre_id = db.Column(
|
||||||
|
db.Integer,
|
||||||
|
db.ForeignKey("notes_formsemestre.id"),
|
||||||
|
)
|
||||||
|
moy_ue = db.Column(db.Float)
|
||||||
|
# (normalement NULL) indice du semestre, utile seulement pour
|
||||||
|
# UE "antérieures" et si la formation définit des UE utilisées
|
||||||
|
# dans plusieurs semestres (cas R&T IUTV v2)
|
||||||
|
semestre_id = db.Column(db.Integer)
|
||||||
|
# Si UE validée dans le cursus d'un autre etablissement
|
||||||
|
is_external = db.Column(
|
||||||
|
db.Boolean, default=False, server_default="false", index=True
|
||||||
|
)
|
||||||
|
|
||||||
|
ue = db.relationship("UniteEns", lazy="select", uselist=False)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"{self.__class__.__name__}({self.formsemestre_id}, {self.etudid}, code={self.code}, ue={self.ue_id}, moy_ue={self.moy_ue})"
|
||||||
|
|
||||||
|
|
||||||
|
class ScolarAutorisationInscription(db.Model):
|
||||||
|
"""Autorisation d'inscription dans un semestre"""
|
||||||
|
|
||||||
|
__tablename__ = "scolar_autorisation_inscription"
|
||||||
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
autorisation_inscription_id = db.synonym("id")
|
||||||
|
|
||||||
|
etudid = db.Column(
|
||||||
|
db.Integer,
|
||||||
|
db.ForeignKey("identite.id"),
|
||||||
|
)
|
||||||
|
formation_code = db.Column(db.String(SHORT_STR_LEN), nullable=False)
|
||||||
|
# semestre ou on peut s'inscrire:
|
||||||
|
semestre_id = db.Column(db.Integer)
|
||||||
|
date = db.Column(db.DateTime(timezone=True), server_default=db.func.now())
|
||||||
|
origin_formsemestre_id = db.Column(
|
||||||
|
db.Integer,
|
||||||
|
db.ForeignKey("notes_formsemestre.id"),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ScolarEvent(db.Model):
|
||||||
|
"""Evenement dans le parcours scolaire d'un étudiant"""
|
||||||
|
|
||||||
|
__tablename__ = "scolar_events"
|
||||||
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
event_id = db.synonym("id")
|
||||||
|
etudid = db.Column(
|
||||||
|
db.Integer,
|
||||||
|
db.ForeignKey("identite.id"),
|
||||||
|
)
|
||||||
|
event_date = db.Column(db.DateTime(timezone=True), server_default=db.func.now())
|
||||||
|
formsemestre_id = db.Column(
|
||||||
|
db.Integer,
|
||||||
|
db.ForeignKey("notes_formsemestre.id"),
|
||||||
|
)
|
||||||
|
ue_id = db.Column(
|
||||||
|
db.Integer,
|
||||||
|
db.ForeignKey("notes_ue.id"),
|
||||||
|
)
|
||||||
|
# 'CREATION', 'INSCRIPTION', 'DEMISSION',
|
||||||
|
# 'AUT_RED', 'EXCLUS', 'VALID_UE', 'VALID_SEM'
|
||||||
|
# 'ECHEC_SEM'
|
||||||
|
# 'UTIL_COMPENSATION'
|
||||||
|
event_type = db.Column(db.String(SHORT_STR_LEN))
|
||||||
|
# Semestre compensé par formsemestre_id:
|
||||||
|
comp_formsemestre_id = db.Column(
|
||||||
|
db.Integer,
|
||||||
|
db.ForeignKey("notes_formsemestre.id"),
|
||||||
|
)
|
@ -935,7 +935,7 @@ class NotesTable:
|
|||||||
"""
|
"""
|
||||||
return self.moy_gen[etudid]
|
return self.moy_gen[etudid]
|
||||||
|
|
||||||
def get_etud_moy_infos(self, etudid):
|
def get_etud_moy_infos(self, etudid): # XXX OBSOLETE
|
||||||
"""Infos sur moyennes"""
|
"""Infos sur moyennes"""
|
||||||
return self.etud_moy_infos[etudid]
|
return self.etud_moy_infos[etudid]
|
||||||
|
|
||||||
@ -1011,7 +1011,10 @@ class NotesTable:
|
|||||||
cnx = ndb.GetDBConnexion()
|
cnx = ndb.GetDBConnexion()
|
||||||
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
|
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
|
||||||
cursor.execute(
|
cursor.execute(
|
||||||
"select etudid, code, assidu, compense_formsemestre_id, event_date from scolar_formsemestre_validation where formsemestre_id=%(formsemestre_id)s and ue_id is NULL;",
|
"""SELECT etudid, code, assidu, compense_formsemestre_id, event_date
|
||||||
|
FROM scolar_formsemestre_validation
|
||||||
|
WHERE formsemestre_id=%(formsemestre_id)s AND ue_id is NULL;
|
||||||
|
""",
|
||||||
{"formsemestre_id": self.formsemestre_id},
|
{"formsemestre_id": self.formsemestre_id},
|
||||||
)
|
)
|
||||||
decisions_jury = {}
|
decisions_jury = {}
|
||||||
@ -1137,8 +1140,14 @@ class NotesTable:
|
|||||||
"""
|
"""
|
||||||
self.ue_capitalisees = scu.DictDefault(defaultvalue=[])
|
self.ue_capitalisees = scu.DictDefault(defaultvalue=[])
|
||||||
cnx = None
|
cnx = None
|
||||||
|
semestre_id = self.sem["semestre_id"]
|
||||||
for etudid in self.get_etudids():
|
for etudid in self.get_etudids():
|
||||||
capital = formsemestre_get_etud_capitalisation(self.sem, etudid)
|
capital = formsemestre_get_etud_capitalisation(
|
||||||
|
self.formation["id"],
|
||||||
|
semestre_id,
|
||||||
|
ndb.DateDMYtoISO(self.sem["date_debut"]),
|
||||||
|
etudid,
|
||||||
|
)
|
||||||
for ue_cap in capital:
|
for ue_cap in capital:
|
||||||
# Si la moyenne d'UE n'avait pas été stockée (anciennes versions de ScoDoc)
|
# Si la moyenne d'UE n'avait pas été stockée (anciennes versions de ScoDoc)
|
||||||
# il faut la calculer ici et l'enregistrer
|
# il faut la calculer ici et l'enregistrer
|
||||||
@ -1308,7 +1317,7 @@ class NotesTable:
|
|||||||
"""Liste des evaluations de ce semestre, avec leur etat"""
|
"""Liste des evaluations de ce semestre, avec leur etat"""
|
||||||
return self.get_evaluations_etats()
|
return self.get_evaluations_etats()
|
||||||
|
|
||||||
def get_mod_evaluation_etat_list(self, moduleimpl_id):
|
def get_mod_evaluation_etat_list(self, moduleimpl_id) -> list[dict]:
|
||||||
"""Liste des évaluations de ce module"""
|
"""Liste des évaluations de ce module"""
|
||||||
return [
|
return [
|
||||||
e
|
e
|
||||||
|
@ -142,7 +142,7 @@ def formsemestre_bulletinetud_dict(formsemestre_id, etudid, version="long"):
|
|||||||
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)
|
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||||
nt: NotesTableCompat = res_sem.load_formsemestre_result(formsemestre)
|
nt: NotesTableCompat = res_sem.load_formsemestre_results(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="")
|
||||||
|
@ -59,9 +59,9 @@ import traceback
|
|||||||
|
|
||||||
from flask import g
|
from flask import g
|
||||||
|
|
||||||
|
from app import log
|
||||||
from app.scodoc import notesdb as ndb
|
from app.scodoc import notesdb as ndb
|
||||||
from app.scodoc import sco_utils as scu
|
from app.scodoc import sco_utils as scu
|
||||||
from app import log
|
|
||||||
|
|
||||||
CACHE = None # set in app.__init__.py
|
CACHE = None # set in app.__init__.py
|
||||||
|
|
||||||
@ -293,6 +293,7 @@ def invalidate_formsemestre( # was inval_cache(formsemestre_id=None, pdfonly=Fa
|
|||||||
|
|
||||||
SemBulletinsPDFCache.invalidate_sems(formsemestre_ids)
|
SemBulletinsPDFCache.invalidate_sems(formsemestre_ids)
|
||||||
ResultatsSemestreCache.delete_many(formsemestre_ids)
|
ResultatsSemestreCache.delete_many(formsemestre_ids)
|
||||||
|
ValidationsSemestreCache.delete_many(formsemestre_ids)
|
||||||
|
|
||||||
|
|
||||||
class DefferedSemCacheManager:
|
class DefferedSemCacheManager:
|
||||||
@ -319,10 +320,20 @@ class DefferedSemCacheManager:
|
|||||||
|
|
||||||
# ---- Nouvelles classes ScoDoc 9.2
|
# ---- Nouvelles classes ScoDoc 9.2
|
||||||
class ResultatsSemestreCache(ScoDocCache):
|
class ResultatsSemestreCache(ScoDocCache):
|
||||||
"""Cache pour les résultats ResultatsSemestre.
|
"""Cache pour les résultats ResultatsSemestre (notes et moyennes)
|
||||||
Clé: formsemestre_id
|
Clé: formsemestre_id
|
||||||
Valeur: { un paquet de dataframes }
|
Valeur: { un paquet de dataframes }
|
||||||
"""
|
"""
|
||||||
|
|
||||||
prefix = "RSEM"
|
prefix = "RSEM"
|
||||||
timeout = 60 * 60 # ttl 1 heure (en phase de mise au point)
|
timeout = 60 * 60 # ttl 1 heure (en phase de mise au point)
|
||||||
|
|
||||||
|
|
||||||
|
class ValidationsSemestreCache(ScoDocCache):
|
||||||
|
"""Cache pour les résultats de jury d'un semestre
|
||||||
|
Clé: formsemestre_id
|
||||||
|
Valeur: un paquet de DataFrames
|
||||||
|
"""
|
||||||
|
|
||||||
|
prefix = "VSC"
|
||||||
|
timeout = 60 * 60 # ttl 1 heure (en phase de mise au point)
|
||||||
|
@ -170,18 +170,18 @@ CODES_SEM_REO = {NAR: 1} # reorientation
|
|||||||
CODES_UE_VALIDES = {ADM: True, CMP: True} # UE validée
|
CODES_UE_VALIDES = {ADM: True, CMP: True} # UE validée
|
||||||
|
|
||||||
|
|
||||||
def code_semestre_validant(code):
|
def code_semestre_validant(code: str) -> bool:
|
||||||
"Vrai si ce CODE entraine la validation du semestre"
|
"Vrai si ce CODE entraine la validation du semestre"
|
||||||
return CODES_SEM_VALIDES.get(code, False)
|
return CODES_SEM_VALIDES.get(code, False)
|
||||||
|
|
||||||
|
|
||||||
def code_semestre_attente(code):
|
def code_semestre_attente(code: str) -> bool:
|
||||||
"Vrai si ce CODE est un code d'attente (semestre validable plus tard par jury ou compensation)"
|
"Vrai si ce CODE est un code d'attente (semestre validable plus tard par jury ou compensation)"
|
||||||
return CODES_SEM_ATTENTES.get(code, False)
|
return CODES_SEM_ATTENTES.get(code, False)
|
||||||
|
|
||||||
|
|
||||||
def code_ue_validant(code):
|
def code_ue_validant(code: str) -> bool:
|
||||||
"Vrai si ce code entraine la validation de l'UE"
|
"Vrai si ce code entraine la validation des UEs du semestre."
|
||||||
return CODES_UE_VALIDES.get(code, False)
|
return CODES_UE_VALIDES.get(code, False)
|
||||||
|
|
||||||
|
|
||||||
@ -259,6 +259,7 @@ class TypeParcours(object):
|
|||||||
) # par defaut, autorise tous les types d'UE
|
) # par defaut, autorise tous les types d'UE
|
||||||
APC_SAE = False # Approche par compétences avec ressources et SAÉs
|
APC_SAE = False # Approche par compétences avec ressources et SAÉs
|
||||||
USE_REFERENTIEL_COMPETENCES = False # Lien avec ref. comp.
|
USE_REFERENTIEL_COMPETENCES = False # Lien avec ref. comp.
|
||||||
|
ECTS_FONDAMENTAUX_PER_YEAR = 0.0 # pour ISCID, deprecated
|
||||||
|
|
||||||
def check(self, formation=None):
|
def check(self, formation=None):
|
||||||
return True, "" # status, diagnostic_message
|
return True, "" # status, diagnostic_message
|
||||||
|
@ -32,7 +32,7 @@ from flask_login import current_user
|
|||||||
|
|
||||||
from app import db
|
from app import db
|
||||||
from app.models import Formation, UniteEns, Matiere, Module, FormSemestre, ModuleImpl
|
from app.models import Formation, UniteEns, Matiere, Module, FormSemestre, ModuleImpl
|
||||||
from app.models.notes import ScolarFormSemestreValidation
|
from app.models.validations import ScolarFormSemestreValidation
|
||||||
from app.scodoc.sco_codes_parcours import UE_SPORT
|
from app.scodoc.sco_codes_parcours import UE_SPORT
|
||||||
import app.scodoc.sco_utils as scu
|
import app.scodoc.sco_utils as scu
|
||||||
from app.scodoc import sco_groups
|
from app.scodoc import sco_groups
|
||||||
|
@ -393,9 +393,8 @@ def do_evaluation_etat_in_mod(nt, moduleimpl_id):
|
|||||||
""""""
|
""""""
|
||||||
evals = nt.get_mod_evaluation_etat_list(moduleimpl_id)
|
evals = nt.get_mod_evaluation_etat_list(moduleimpl_id)
|
||||||
etat = _eval_etat(evals)
|
etat = _eval_etat(evals)
|
||||||
etat["attente"] = moduleimpl_id in [
|
# Il y a-t-il des notes en attente dans ce module ?
|
||||||
m["moduleimpl_id"] for m in nt.get_moduleimpls_attente()
|
etat["attente"] = nt.modimpls_results[moduleimpl_id].en_attente
|
||||||
] # > liste moduleimpl en attente
|
|
||||||
return etat
|
return etat
|
||||||
|
|
||||||
|
|
||||||
|
@ -991,9 +991,9 @@ def formsemestre_status(formsemestre_id=None):
|
|||||||
modimpls = sco_moduleimpl.moduleimpl_withmodule_list(
|
modimpls = sco_moduleimpl.moduleimpl_withmodule_list(
|
||||||
formsemestre_id=formsemestre_id
|
formsemestre_id=formsemestre_id
|
||||||
)
|
)
|
||||||
nt = sco_cache.NotesTableCache.get(formsemestre_id)
|
# nt = sco_cache.NotesTableCache.get(formsemestre_id)
|
||||||
# WIP formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||||
# WIP nt = res_sem.load_formsemestre_result(formsemestre)
|
nt = res_sem.load_formsemestre_results(formsemestre)
|
||||||
|
|
||||||
# Construit la liste de tous les enseignants de ce semestre:
|
# Construit la liste de tous les enseignants de ce semestre:
|
||||||
mails_enseignants = set(
|
mails_enseignants = set(
|
||||||
|
@ -549,7 +549,7 @@ def formsemestre_recap_parcours_table(
|
|||||||
ass = ""
|
ass = ""
|
||||||
|
|
||||||
formsemestre = FormSemestre.query.get(sem["formsemestre_id"])
|
formsemestre = FormSemestre.query.get(sem["formsemestre_id"])
|
||||||
nt: NotesTableCompat = res_sem.load_formsemestre_result(formsemestre)
|
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
||||||
# nt = sco_cache.NotesTableCache.get(sem["formsemestre_id"])
|
# nt = sco_cache.NotesTableCache.get(sem["formsemestre_id"])
|
||||||
if is_cur:
|
if is_cur:
|
||||||
type_sem = "*" # now unused
|
type_sem = "*" # now unused
|
||||||
@ -692,7 +692,7 @@ def formsemestre_recap_parcours_table(
|
|||||||
sco_preferences.get_preference("bul_show_ects", sem["formsemestre_id"])
|
sco_preferences.get_preference("bul_show_ects", sem["formsemestre_id"])
|
||||||
or nt.parcours.ECTS_ONLY
|
or nt.parcours.ECTS_ONLY
|
||||||
):
|
):
|
||||||
etud_moy_infos = nt.get_etud_moy_infos(etudid)
|
etud_ects_infos = nt.get_etud_ects_pot(etudid)
|
||||||
H.append(
|
H.append(
|
||||||
'<tr class="%s rcp_l2 sem_%s">' % (class_sem, sem["formsemestre_id"])
|
'<tr class="%s rcp_l2 sem_%s">' % (class_sem, sem["formsemestre_id"])
|
||||||
)
|
)
|
||||||
@ -703,7 +703,7 @@ def formsemestre_recap_parcours_table(
|
|||||||
# total ECTS (affiché sous la moyenne générale)
|
# total ECTS (affiché sous la moyenne générale)
|
||||||
H.append(
|
H.append(
|
||||||
'<td class="sem_ects_tit"><a title="crédit potentiels (dont nb de fondamentaux)">ECTS:</a></td><td class="sem_ects">%g <span class="ects_fond">%g</span></td>'
|
'<td class="sem_ects_tit"><a title="crédit potentiels (dont nb de fondamentaux)">ECTS:</a></td><td class="sem_ects">%g <span class="ects_fond">%g</span></td>'
|
||||||
% (etud_moy_infos["ects_pot"], etud_moy_infos["ects_pot_fond"])
|
% (etud_ects_infos["ects_pot"], etud_ects_infos["ects_pot_fond"])
|
||||||
)
|
)
|
||||||
H.append('<td class="rcp_abs"></td>')
|
H.append('<td class="rcp_abs"></td>')
|
||||||
# ECTS validables dans chaque UE
|
# ECTS validables dans chaque UE
|
||||||
@ -1062,7 +1062,7 @@ def formsemestre_validate_previous_ue(formsemestre_id, etudid):
|
|||||||
"title": "Indice du semestre",
|
"title": "Indice du semestre",
|
||||||
"explanation": "Facultatif: indice du semestre dans la formation",
|
"explanation": "Facultatif: indice du semestre dans la formation",
|
||||||
"allow_null": True,
|
"allow_null": True,
|
||||||
"allowed_values": [""] + [str(x) for x in range(11)],
|
"allowed_values": [""] + [x for x in range(11)],
|
||||||
"labels": ["-"] + list(range(11)),
|
"labels": ["-"] + list(range(11)),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
@ -837,7 +837,7 @@ def _add_apc_columns(
|
|||||||
# => On recharge tout dans les nouveaux modèles
|
# => On recharge tout dans les nouveaux modèles
|
||||||
# rows est une liste de dict avec une clé "etudid"
|
# rows est une liste de dict avec une clé "etudid"
|
||||||
# on va y ajouter une clé par UE du semestre
|
# on va y ajouter une clé par UE du semestre
|
||||||
nt: NotesTableCompat = res_sem.load_formsemestre_result(modimpl.formsemestre)
|
nt: NotesTableCompat = res_sem.load_formsemestre_results(modimpl.formsemestre)
|
||||||
modimpl_results: ModuleImplResults = nt.modimpls_results[modimpl.id]
|
modimpl_results: ModuleImplResults = nt.modimpls_results[modimpl.id]
|
||||||
|
|
||||||
# XXX A ENLEVER TODO
|
# XXX A ENLEVER TODO
|
||||||
|
@ -28,6 +28,7 @@
|
|||||||
"""Semestres: gestion parcours DUT (Arreté du 13 août 2005)
|
"""Semestres: gestion parcours DUT (Arreté du 13 août 2005)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from app.models.ues import UniteEns
|
||||||
import app.scodoc.sco_utils as scu
|
import app.scodoc.sco_utils as scu
|
||||||
import app.scodoc.notesdb as ndb
|
import app.scodoc.notesdb as ndb
|
||||||
from app import log
|
from app import log
|
||||||
@ -678,10 +679,10 @@ class SituationEtudParcoursECTS(SituationEtudParcoursGeneric):
|
|||||||
|
|
||||||
Dans ce type de parcours, on n'utilise que ADM, AJ, et ADJ (?).
|
Dans ce type de parcours, on n'utilise que ADM, AJ, et ADJ (?).
|
||||||
"""
|
"""
|
||||||
etud_moy_infos = self.nt.get_etud_moy_infos(self.etudid)
|
etud_ects_infos = self.nt.get_etud_ects_pot(self.etudid)
|
||||||
if (
|
if (
|
||||||
etud_moy_infos["ects_pot"] >= self.parcours.ECTS_BARRE_VALID_YEAR
|
etud_ects_infos["ects_pot"] >= self.parcours.ECTS_BARRE_VALID_YEAR
|
||||||
and etud_moy_infos["ects_pot"] >= self.parcours.ECTS_FONDAMENTAUX_PER_YEAR
|
and etud_ects_infos["ects_pot"] >= self.parcours.ECTS_FONDAMENTAUX_PER_YEAR
|
||||||
):
|
):
|
||||||
choices = [
|
choices = [
|
||||||
DecisionSem(
|
DecisionSem(
|
||||||
@ -954,6 +955,9 @@ def do_formsemestre_validate_ue(
|
|||||||
is_external=False,
|
is_external=False,
|
||||||
):
|
):
|
||||||
"""Ajoute ou change validation UE"""
|
"""Ajoute ou change validation UE"""
|
||||||
|
if semestre_id is None:
|
||||||
|
ue = UniteEns.query.get_or_404(ue_id)
|
||||||
|
semestre_id = ue.semestre_idx
|
||||||
args = {
|
args = {
|
||||||
"formsemestre_id": formsemestre_id,
|
"formsemestre_id": formsemestre_id,
|
||||||
"etudid": etudid,
|
"etudid": etudid,
|
||||||
@ -971,7 +975,8 @@ def do_formsemestre_validate_ue(
|
|||||||
if formsemestre_id:
|
if formsemestre_id:
|
||||||
cond += " and formsemestre_id=%(formsemestre_id)s"
|
cond += " and formsemestre_id=%(formsemestre_id)s"
|
||||||
if semestre_id:
|
if semestre_id:
|
||||||
cond += " and semestre_id=%(semestre_id)s"
|
cond += " and (semestre_id=%(semestre_id)s or semestre_id is NULL)"
|
||||||
|
log(f"formsemestre_validate_ue: deleting where {cond}, args={args})")
|
||||||
cursor.execute("delete from scolar_formsemestre_validation where " + cond, args)
|
cursor.execute("delete from scolar_formsemestre_validation where " + cond, args)
|
||||||
# insert
|
# insert
|
||||||
args["code"] = code
|
args["code"] = code
|
||||||
@ -980,7 +985,7 @@ def do_formsemestre_validate_ue(
|
|||||||
# stocke la moyenne d'UE capitalisée:
|
# stocke la moyenne d'UE capitalisée:
|
||||||
moy_ue = nt.get_etud_ue_status(etudid, ue_id)["moy"]
|
moy_ue = nt.get_etud_ue_status(etudid, ue_id)["moy"]
|
||||||
args["moy_ue"] = moy_ue
|
args["moy_ue"] = moy_ue
|
||||||
log("formsemestre_validate_ue: %s" % args)
|
log("formsemestre_validate_ue: create %s" % args)
|
||||||
if code != None:
|
if code != None:
|
||||||
scolar_formsemestre_validation_create(cnx, args)
|
scolar_formsemestre_validation_create(cnx, args)
|
||||||
else:
|
else:
|
||||||
@ -1039,7 +1044,9 @@ def formsemestre_get_autorisation_inscription(etudid, origin_formsemestre_id):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def formsemestre_get_etud_capitalisation(sem, etudid):
|
def formsemestre_get_etud_capitalisation(
|
||||||
|
formation_id: int, semestre_idx: int, date_debut, etudid: int
|
||||||
|
) -> list[dict]:
|
||||||
"""Liste des UE capitalisées (ADM) correspondant au semestre sem et à l'étudiant.
|
"""Liste des UE capitalisées (ADM) correspondant au semestre sem et à l'étudiant.
|
||||||
|
|
||||||
Recherche dans les semestres de la même formation (code) avec le même
|
Recherche dans les semestres de la même formation (code) avec le même
|
||||||
@ -1057,30 +1064,32 @@ def formsemestre_get_etud_capitalisation(sem, etudid):
|
|||||||
cnx = ndb.GetDBConnexion()
|
cnx = ndb.GetDBConnexion()
|
||||||
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
|
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
|
||||||
cursor.execute(
|
cursor.execute(
|
||||||
"""select distinct SFV.*, ue.ue_code from notes_ue ue, notes_formations nf,
|
"""
|
||||||
notes_formations nf2, scolar_formsemestre_validation SFV, notes_formsemestre sem
|
SELECT DISTINCT SFV.*, ue.ue_code
|
||||||
|
FROM notes_ue ue, notes_formations nf,
|
||||||
|
notes_formations nf2, scolar_formsemestre_validation SFV, notes_formsemestre sem
|
||||||
|
|
||||||
WHERE ue.formation_id = nf.id
|
WHERE ue.formation_id = nf.id
|
||||||
and nf.formation_code = nf2.formation_code
|
and nf.formation_code = nf2.formation_code
|
||||||
and nf2.id=%(formation_id)s
|
and nf2.id=%(formation_id)s
|
||||||
|
|
||||||
and SFV.ue_id = ue.id
|
and SFV.ue_id = ue.id
|
||||||
and SFV.code = 'ADM'
|
and SFV.code = 'ADM'
|
||||||
and SFV.etudid = %(etudid)s
|
and SFV.etudid = %(etudid)s
|
||||||
|
|
||||||
and ( (sem.id = SFV.formsemestre_id
|
and ( (sem.id = SFV.formsemestre_id
|
||||||
and sem.date_debut < %(date_debut)s
|
and sem.date_debut < %(date_debut)s
|
||||||
and sem.semestre_id = %(semestre_id)s )
|
and sem.semestre_id = %(semestre_id)s )
|
||||||
or (
|
or (
|
||||||
((SFV.formsemestre_id is NULL) OR (SFV.is_external)) -- les UE externes ou "anterieures"
|
((SFV.formsemestre_id is NULL) OR (SFV.is_external)) -- les UE externes ou "anterieures"
|
||||||
AND (SFV.semestre_id is NULL OR SFV.semestre_id=%(semestre_id)s)
|
AND (SFV.semestre_id is NULL OR SFV.semestre_id=%(semestre_id)s)
|
||||||
) )
|
) )
|
||||||
""",
|
""",
|
||||||
{
|
{
|
||||||
"etudid": etudid,
|
"etudid": etudid,
|
||||||
"formation_id": sem["formation_id"],
|
"formation_id": formation_id,
|
||||||
"semestre_id": sem["semestre_id"],
|
"semestre_id": semestre_idx,
|
||||||
"date_debut": ndb.DateDMYtoISO(sem["date_debut"]),
|
"date_debut": date_debut,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -140,23 +140,23 @@ def descr_autorisations(autorisations):
|
|||||||
return ", ".join(alist)
|
return ", ".join(alist)
|
||||||
|
|
||||||
|
|
||||||
def _comp_ects_by_ue_code_and_type(nt, decision_ues):
|
def _comp_ects_by_ue_code(nt, decision_ues):
|
||||||
"""Calcul somme des ECTS validés dans ce semestre (sans les UE capitalisées)
|
"""Calcul somme des ECTS validés dans ce semestre (sans les UE capitalisées)
|
||||||
decision_ues est le resultat de nt.get_etud_decision_ues
|
decision_ues est le resultat de nt.get_etud_decision_ues
|
||||||
Chaque resultat est un dict: { ue_code : ects }
|
Chaque resultat est un dict: { ue_code : ects }
|
||||||
"""
|
"""
|
||||||
|
raise NotImplementedError() # XXX #sco92
|
||||||
|
# ré-écrire en utilisant
|
||||||
if not decision_ues:
|
if not decision_ues:
|
||||||
return {}, {}
|
return {}
|
||||||
|
|
||||||
ects_by_ue_code = {}
|
ects_by_ue_code = {}
|
||||||
ects_by_ue_type = scu.DictDefault(defaultvalue=0) # { ue_type : ects validés }
|
|
||||||
for ue_id in decision_ues:
|
for ue_id in decision_ues:
|
||||||
d = decision_ues[ue_id]
|
d = decision_ues[ue_id]
|
||||||
ue = nt.uedict[ue_id]
|
ue = nt.uedict[ue_id]
|
||||||
ects_by_ue_code[ue["ue_code"]] = d["ects"]
|
ects_by_ue_code[ue["ue_code"]] = d["ects"]
|
||||||
ects_by_ue_type[ue["type"]] += d["ects"]
|
|
||||||
|
|
||||||
return ects_by_ue_code, ects_by_ue_type
|
return ects_by_ue_code
|
||||||
|
|
||||||
|
|
||||||
def _comp_ects_capitalises_by_ue_code(nt, etudid):
|
def _comp_ects_capitalises_by_ue_code(nt, etudid):
|
||||||
@ -249,11 +249,8 @@ def dict_pvjury(
|
|||||||
|
|
||||||
ects_capitalises_by_ue_code = _comp_ects_capitalises_by_ue_code(nt, etudid)
|
ects_capitalises_by_ue_code = _comp_ects_capitalises_by_ue_code(nt, etudid)
|
||||||
d["sum_ects_capitalises"] = sum(ects_capitalises_by_ue_code.values())
|
d["sum_ects_capitalises"] = sum(ects_capitalises_by_ue_code.values())
|
||||||
ects_by_ue_code, ects_by_ue_type = _comp_ects_by_ue_code_and_type(
|
ects_by_ue_code = _comp_ects_by_ue_code(nt, d["decisions_ue"])
|
||||||
nt, d["decisions_ue"]
|
|
||||||
)
|
|
||||||
d["sum_ects"] = _sum_ects_dicts(ects_capitalises_by_ue_code, ects_by_ue_code)
|
d["sum_ects"] = _sum_ects_dicts(ects_capitalises_by_ue_code, ects_by_ue_code)
|
||||||
d["sum_ects_by_type"] = ects_by_ue_type
|
|
||||||
|
|
||||||
if d["decision_sem"] and sco_codes_parcours.code_semestre_validant(
|
if d["decision_sem"] and sco_codes_parcours.code_semestre_validant(
|
||||||
d["decision_sem"]["code"]
|
d["decision_sem"]["code"]
|
||||||
|
@ -25,7 +25,7 @@
|
|||||||
#
|
#
|
||||||
##############################################################################
|
##############################################################################
|
||||||
|
|
||||||
"""Tableau recapitulatif des notes d'un semestre
|
"""Tableau récapitulatif des notes d'un semestre
|
||||||
"""
|
"""
|
||||||
import datetime
|
import datetime
|
||||||
import json
|
import json
|
||||||
@ -41,6 +41,7 @@ from app.comp import res_sem
|
|||||||
from app.comp.res_common import NotesTableCompat
|
from app.comp.res_common import NotesTableCompat
|
||||||
from app.models import FormSemestre
|
from app.models import FormSemestre
|
||||||
from app.models.etudiants import Identite
|
from app.models.etudiants import Identite
|
||||||
|
from app.models.evaluations import Evaluation
|
||||||
|
|
||||||
import app.scodoc.sco_utils as scu
|
import app.scodoc.sco_utils as scu
|
||||||
from app.scodoc import html_sco_header
|
from app.scodoc import html_sco_header
|
||||||
@ -308,8 +309,8 @@ def make_formsemestre_recapcomplet(
|
|||||||
|
|
||||||
# nt = sco_cache.NotesTableCache.get(formsemestre_id) # sco91
|
# nt = sco_cache.NotesTableCache.get(formsemestre_id) # sco91
|
||||||
# sco92 :
|
# sco92 :
|
||||||
nt: NotesTableCompat = res_sem.load_formsemestre_result(formsemestre)
|
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
||||||
modimpls = nt.get_modimpls_dict()
|
modimpls = formsemestre.modimpls_sorted
|
||||||
ues = nt.get_ues_stat_dict() # incluant le(s) UE de sport
|
ues = nt.get_ues_stat_dict() # incluant le(s) UE de sport
|
||||||
#
|
#
|
||||||
# if formsemestre.formation.is_apc():
|
# if formsemestre.formation.is_apc():
|
||||||
@ -367,15 +368,16 @@ def make_formsemestre_recapcomplet(
|
|||||||
pass
|
pass
|
||||||
if not hidemodules and not ue["is_external"]:
|
if not hidemodules and not ue["is_external"]:
|
||||||
for modimpl in modimpls:
|
for modimpl in modimpls:
|
||||||
if modimpl["module"]["ue_id"] == ue["ue_id"]:
|
if modimpl.module.ue_id == ue["ue_id"]:
|
||||||
code = modimpl["module"]["code"]
|
code = modimpl.module.code
|
||||||
h.append(code)
|
h.append(code)
|
||||||
cod2mod[code] = modimpl # pour fabriquer le lien
|
cod2mod[code] = modimpl # pour fabriquer le lien
|
||||||
if format == "xlsall":
|
if format == "xlsall":
|
||||||
evals = nt.get_mod_evaluation_etat_list(
|
evals = nt.modimpls_results[
|
||||||
modimpl["moduleimpl_id"]
|
modimpl.id
|
||||||
)
|
].get_evaluations_completes(modimpl)
|
||||||
mod_evals[modimpl["moduleimpl_id"]] = evals
|
# evals = nt.get_mod_evaluation_etat_list(...
|
||||||
|
mod_evals[modimpl.id] = evals
|
||||||
h += _list_notes_evals_titles(code, evals)
|
h += _list_notes_evals_titles(code, evals)
|
||||||
|
|
||||||
h += admission_extra_cols
|
h += admission_extra_cols
|
||||||
@ -483,7 +485,7 @@ def make_formsemestre_recapcomplet(
|
|||||||
if not hidemodules and not ue["is_external"]:
|
if not hidemodules and not ue["is_external"]:
|
||||||
j = 0
|
j = 0
|
||||||
for modimpl in modimpls:
|
for modimpl in modimpls:
|
||||||
if modimpl["module"]["ue_id"] == ue["ue_id"]:
|
if modimpl.module.ue_id == ue["ue_id"]:
|
||||||
l.append(
|
l.append(
|
||||||
fmtnum(
|
fmtnum(
|
||||||
scu.fmt_note(
|
scu.fmt_note(
|
||||||
@ -492,9 +494,7 @@ def make_formsemestre_recapcomplet(
|
|||||||
)
|
)
|
||||||
) # moyenne etud dans module
|
) # moyenne etud dans module
|
||||||
if format == "xlsall":
|
if format == "xlsall":
|
||||||
l += _list_notes_evals(
|
l += _list_notes_evals(mod_evals[modimpl.id], etudid)
|
||||||
mod_evals[modimpl["moduleimpl_id"]], etudid
|
|
||||||
)
|
|
||||||
j += 1
|
j += 1
|
||||||
if not hidebac:
|
if not hidebac:
|
||||||
for k in admission_extra_cols:
|
for k in admission_extra_cols:
|
||||||
@ -509,9 +509,7 @@ def make_formsemestre_recapcomplet(
|
|||||||
if not hidemodules: # moy/min/max dans chaque module
|
if not hidemodules: # moy/min/max dans chaque module
|
||||||
mods_stats = {} # moduleimpl_id : stats
|
mods_stats = {} # moduleimpl_id : stats
|
||||||
for modimpl in modimpls:
|
for modimpl in modimpls:
|
||||||
mods_stats[modimpl["moduleimpl_id"]] = nt.get_mod_stats(
|
mods_stats[modimpl.id] = nt.get_mod_stats(modimpl.id)
|
||||||
modimpl["moduleimpl_id"]
|
|
||||||
)
|
|
||||||
|
|
||||||
def add_bottom_stat(key, title, corner_value=""):
|
def add_bottom_stat(key, title, corner_value=""):
|
||||||
l = ["", title]
|
l = ["", title]
|
||||||
@ -551,16 +549,16 @@ def make_formsemestre_recapcomplet(
|
|||||||
# ue_index.append(len(l) - 1)
|
# ue_index.append(len(l) - 1)
|
||||||
if not hidemodules and not ue["is_external"]:
|
if not hidemodules and not ue["is_external"]:
|
||||||
for modimpl in modimpls:
|
for modimpl in modimpls:
|
||||||
if modimpl["module"]["ue_id"] == ue["ue_id"]:
|
if modimpl.module.ue_id == ue["ue_id"]:
|
||||||
if key == "coef":
|
if key == "coef":
|
||||||
coef = modimpl["module"]["coefficient"]
|
coef = modimpl.module.coefficient
|
||||||
if format[:3] != "xls":
|
if format[:3] != "xls":
|
||||||
coef = str(coef)
|
coef = str(coef)
|
||||||
l.append(coef)
|
l.append(coef)
|
||||||
elif key == "ects":
|
elif key == "ects":
|
||||||
l.append("") # ECTS module ?
|
l.append("") # ECTS module ?
|
||||||
else:
|
else:
|
||||||
val = mods_stats[modimpl["moduleimpl_id"]][key]
|
val = mods_stats[modimpl.id][key]
|
||||||
if key == "nb_valid_evals":
|
if key == "nb_valid_evals":
|
||||||
if (
|
if (
|
||||||
format[:3] != "xls"
|
format[:3] != "xls"
|
||||||
@ -571,9 +569,7 @@ def make_formsemestre_recapcomplet(
|
|||||||
l.append(val)
|
l.append(val)
|
||||||
|
|
||||||
if format == "xlsall":
|
if format == "xlsall":
|
||||||
l += _list_notes_evals_stats(
|
l += _list_notes_evals_stats(mod_evals[modimpl.id], key)
|
||||||
mod_evals[modimpl["moduleimpl_id"]], key
|
|
||||||
)
|
|
||||||
if modejury:
|
if modejury:
|
||||||
l.append("") # case vide sur ligne "Moyennes"
|
l.append("") # case vide sur ligne "Moyennes"
|
||||||
|
|
||||||
@ -595,7 +591,7 @@ def make_formsemestre_recapcomplet(
|
|||||||
add_bottom_stat("nb_valid_evals", "Nb évals")
|
add_bottom_stat("nb_valid_evals", "Nb évals")
|
||||||
add_bottom_stat("ects", "ECTS")
|
add_bottom_stat("ects", "ECTS")
|
||||||
|
|
||||||
# Generation table au format demandé
|
# Génération de la table au format demandé
|
||||||
if format == "html":
|
if format == "html":
|
||||||
# Table format HTML
|
# Table format HTML
|
||||||
H = [
|
H = [
|
||||||
@ -838,63 +834,50 @@ def make_formsemestre_recapcomplet(
|
|||||||
raise ValueError("unknown format %s" % format)
|
raise ValueError("unknown format %s" % format)
|
||||||
|
|
||||||
|
|
||||||
def _list_notes_evals(evals, etudid):
|
def _list_notes_evals(evals: list[Evaluation], etudid: int) -> list[str]:
|
||||||
"""Liste des notes des evaluations completes de ce module
|
"""Liste des notes des evaluations completes de ce module
|
||||||
(pour table xls avec evals)
|
(pour table xls avec evals)
|
||||||
"""
|
"""
|
||||||
L = []
|
L = []
|
||||||
for e in evals:
|
for e in evals:
|
||||||
if (
|
notes_db = sco_evaluation_db.do_evaluation_get_all_notes(e.evaluation_id)
|
||||||
e["etat"]["evalcomplete"]
|
if etudid in notes_db:
|
||||||
or e["etat"]["evalattente"]
|
val = notes_db[etudid]["value"]
|
||||||
or e["publish_incomplete"]
|
else:
|
||||||
):
|
# Note manquante mais prise en compte immédiate: affiche ATT
|
||||||
notes_db = sco_evaluation_db.do_evaluation_get_all_notes(e["evaluation_id"])
|
val = scu.NOTES_ATTENTE
|
||||||
if etudid in notes_db:
|
val_fmt = scu.fmt_note(val, keep_numeric=True)
|
||||||
val = notes_db[etudid]["value"]
|
L.append(val_fmt)
|
||||||
else:
|
|
||||||
# Note manquante mais prise en compte immédiate: affiche ATT
|
|
||||||
val = scu.NOTES_ATTENTE
|
|
||||||
val_fmt = scu.fmt_note(val, keep_numeric=True)
|
|
||||||
L.append(val_fmt)
|
|
||||||
return L
|
return L
|
||||||
|
|
||||||
|
|
||||||
def _list_notes_evals_titles(codemodule, evals):
|
def _list_notes_evals_titles(codemodule: str, evals: list[Evaluation]) -> list[str]:
|
||||||
"""Liste des titres des evals completes"""
|
"""Liste des titres des evals completes"""
|
||||||
L = []
|
L = []
|
||||||
eval_index = len(evals) - 1
|
eval_index = len(evals) - 1
|
||||||
for e in evals:
|
for e in evals:
|
||||||
if (
|
L.append(codemodule + "-" + str(eval_index) + "-" + e.jour.isoformat())
|
||||||
e["etat"]["evalcomplete"]
|
|
||||||
or e["etat"]["evalattente"]
|
|
||||||
or e["publish_incomplete"]
|
|
||||||
):
|
|
||||||
L.append(codemodule + "-" + str(eval_index) + "-" + e["jour"].isoformat())
|
|
||||||
eval_index -= 1
|
eval_index -= 1
|
||||||
return L
|
return L
|
||||||
|
|
||||||
|
|
||||||
def _list_notes_evals_stats(evals, key):
|
def _list_notes_evals_stats(evals: list[Evaluation], key: str) -> list[str]:
|
||||||
"""Liste des stats (moy, ou rien!) des evals completes"""
|
"""Liste des stats (moy, ou rien!) des evals completes"""
|
||||||
L = []
|
L = []
|
||||||
for e in evals:
|
for e in evals:
|
||||||
if (
|
if key == "moy":
|
||||||
e["etat"]["evalcomplete"]
|
# TODO #sco92
|
||||||
or e["etat"]["evalattente"]
|
# val = e["etat"]["moy_num"]
|
||||||
or e["publish_incomplete"]
|
# L.append(scu.fmt_note(val, keep_numeric=True))
|
||||||
):
|
L.append("")
|
||||||
if key == "moy":
|
elif key == "max":
|
||||||
val = e["etat"]["moy_num"]
|
L.append(e.note_max)
|
||||||
L.append(scu.fmt_note(val, keep_numeric=True))
|
elif key == "min":
|
||||||
elif key == "max":
|
L.append(0.0)
|
||||||
L.append(e["note_max"])
|
elif key == "coef":
|
||||||
elif key == "min":
|
L.append(e.coefficient)
|
||||||
L.append(0.0)
|
else:
|
||||||
elif key == "coef":
|
L.append("") # on n'a pas sous la main min/max
|
||||||
L.append(e["coefficient"])
|
|
||||||
else:
|
|
||||||
L.append("") # on n'a pas sous la main min/max
|
|
||||||
return L
|
return L
|
||||||
|
|
||||||
|
|
||||||
|
@ -2140,7 +2140,7 @@ def formsemestre_validation_etud_manu(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/formsemestre_validate_previous_ue")
|
@bp.route("/formsemestre_validate_previous_ue", methods=["GET", "POST"])
|
||||||
@scodoc
|
@scodoc
|
||||||
@permission_required(Permission.ScoView)
|
@permission_required(Permission.ScoView)
|
||||||
@scodoc7func
|
@scodoc7func
|
||||||
|
Loading…
Reference in New Issue
Block a user