forked from ScoDoc/ScoDoc
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."
|
||||
self.evaluations_etat = {}
|
||||
"{ evaluation_id: EvaluationEtat }"
|
||||
self.en_attente = False
|
||||
"Vrai si au moins une évaluation a une note en attente"
|
||||
#
|
||||
self.evals_notes = None
|
||||
"""DataFrame, colonnes: EVALS, Lignes: etudid (inscrits au SEMESTRE)
|
||||
@ -133,7 +135,7 @@ class ModuleImplResults:
|
||||
evals_notes = pd.DataFrame(index=self.etudids, dtype=float)
|
||||
self.evaluations_completes = []
|
||||
self.evaluations_completes_dict = {}
|
||||
|
||||
self.en_attente = False
|
||||
for evaluation in moduleimpl.evaluations:
|
||||
eval_df = self._load_evaluation_notes(evaluation)
|
||||
# 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(
|
||||
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)
|
||||
evals_notes.columns = pd.Int64Index(
|
||||
@ -209,6 +213,13 @@ class ModuleImplResults:
|
||||
* self.evaluations_completes
|
||||
).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:
|
||||
"""Les notes des évaluations,
|
||||
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 pandas as pd
|
||||
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.models import FormSemestre, Identite, ModuleImpl
|
||||
from app.models.ues import UniteEns
|
||||
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_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)
|
||||
# qui sont notamment les attributs décorés par `@cached_property``
|
||||
#
|
||||
class ResultatsSemestre:
|
||||
class ResultatsSemestre(ResultatsCache):
|
||||
_cached_attrs = (
|
||||
"etud_moy_gen_ranks",
|
||||
"etud_moy_gen",
|
||||
@ -36,7 +39,7 @@ class ResultatsSemestre:
|
||||
)
|
||||
|
||||
def __init__(self, formsemestre: FormSemestre):
|
||||
self.formsemestre: FormSemestre = formsemestre
|
||||
super().__init__(formsemestre, ResultatsSemestreCache)
|
||||
# BUT ou standard ? (apc == "approche par compétences")
|
||||
self.is_apc = formsemestre.formation.is_apc()
|
||||
# Attributs "virtuels", définis dans les sous-classes
|
||||
@ -46,26 +49,9 @@ class ResultatsSemestre:
|
||||
self.etud_moy_gen = {}
|
||||
self.etud_moy_gen_ranks = {}
|
||||
self.modimpls_results: ModuleImplResults = None
|
||||
"Résultats de chaque modimpl: dict { modimpl.id : ModuleImplResults(Classique ou BUT) }"
|
||||
self.etud_coef_ue_df = None
|
||||
"""coefs d'UE effectifs pour chaque etudiant (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},
|
||||
)
|
||||
"""coefs d'UE effectifs pour chaque étudiant (pour form. classiques)"""
|
||||
|
||||
def compute(self):
|
||||
"Charge les notes et inscriptions et calcule toutes les moyennes"
|
||||
@ -101,7 +87,8 @@ class ResultatsSemestre:
|
||||
@cached_property
|
||||
def ues(self) -> list[UniteEns]:
|
||||
"""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()
|
||||
|
||||
@ -123,15 +110,34 @@ class ResultatsSemestre:
|
||||
if m.module.module_type == scu.ModuleType.SAE
|
||||
]
|
||||
|
||||
@cached_property
|
||||
def ue_validables(self) -> list:
|
||||
"""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 get_etud_ue_validables(self, etudid: int) -> list[UniteEns]:
|
||||
"""Liste des UEs du semestre qui doivent être validées
|
||||
|
||||
def modimpls_in_ue(self, ue_id, etudid):
|
||||
"""Liste des modimpl de cet ue auxquels l'étudiant est inscrit"""
|
||||
Rappel: l'étudiant est inscrit à des modimpls et non à des UEs.
|
||||
|
||||
- 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
|
||||
return [
|
||||
modimpl
|
||||
@ -180,6 +186,7 @@ class NotesTableCompat(ResultatsSemestre):
|
||||
self.moy_moy = "NA"
|
||||
self.expr_diagnostics = ""
|
||||
self.parcours = self.formsemestre.formation.get_parcours()
|
||||
self.validations = None
|
||||
|
||||
def get_etudids(self, sorted=False) -> list[int]:
|
||||
"""Liste des etudids inscrits, incluant les démissionnaires.
|
||||
@ -243,6 +250,21 @@ class NotesTableCompat(ResultatsSemestre):
|
||||
modimpls_dict.append(d)
|
||||
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:
|
||||
"""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 }
|
||||
@ -256,12 +278,11 @@ class NotesTableCompat(ResultatsSemestre):
|
||||
"compense_formsemestre_id": None,
|
||||
}
|
||||
else:
|
||||
return {
|
||||
"code": ATT, # XXX TODO
|
||||
"assidu": True, # XXX TODO
|
||||
"event_date": "",
|
||||
"compense_formsemestre_id": None,
|
||||
}
|
||||
if not self.validations:
|
||||
self.validations = res_sem.load_formsemestre_validations(
|
||||
self.formsemestre
|
||||
)
|
||||
return self.validations.decisions_jury.get(etudid, None)
|
||||
|
||||
def get_etud_etat(self, etudid: int) -> str:
|
||||
"Etat de l'etudiant: 'I', 'D', DEF ou '' (si pas connu dans ce semestre)"
|
||||
@ -290,6 +311,31 @@ class NotesTableCompat(ResultatsSemestre):
|
||||
"""
|
||||
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):
|
||||
coef_ue = self.etud_coef_ue_df[ue_id][etudid]
|
||||
return {
|
||||
@ -333,8 +379,32 @@ class NotesTableCompat(ResultatsSemestre):
|
||||
evals_results.append(d)
|
||||
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):
|
||||
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:
|
||||
"""Stats sur les notes obtenues dans un modimpl
|
||||
|
@ -8,31 +8,49 @@
|
||||
"""
|
||||
from flask import g
|
||||
|
||||
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
|
||||
|
||||
|
||||
def load_formsemestre_result(formsemestre: FormSemestre) -> ResultatsSemestre:
|
||||
def load_formsemestre_results(formsemestre: FormSemestre) -> ResultatsSemestre:
|
||||
"""Returns ResultatsSemestre for this formsemestre.
|
||||
Suivant le type de formation, retour une instance de
|
||||
ResultatsSemestreClassic ou de ResultatsSemestreBUT.
|
||||
|
||||
Search in local cache (g.formsemestre_result_cache)
|
||||
then global app cache (eg REDIS)
|
||||
If not in cache, build it and cache it.
|
||||
"""
|
||||
# --- Try local cache (within the same request context)
|
||||
if not hasattr(g, "formsemestre_result_cache"):
|
||||
g.formsemestre_result_cache = {} # pylint: disable=C0237
|
||||
if not hasattr(g, "formsemestre_results_cache"):
|
||||
g.formsemestre_results_cache = {} # pylint: disable=C0237
|
||||
else:
|
||||
if formsemestre.id in g.formsemestre_result_cache:
|
||||
return g.formsemestre_result_cache[formsemestre.id]
|
||||
if formsemestre.id in g.formsemestre_results_cache:
|
||||
return g.formsemestre_results_cache[formsemestre.id]
|
||||
|
||||
klass = (
|
||||
ResultatsSemestreBUT
|
||||
if formsemestre.formation.is_apc()
|
||||
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.notes import (
|
||||
ScolarEvent,
|
||||
ScolarFormSemestreValidation,
|
||||
ScolarAutorisationInscription,
|
||||
BulAppreciations,
|
||||
NotesNotes,
|
||||
NotesNotesLog,
|
||||
)
|
||||
from app.models.validations import (
|
||||
ScolarEvent,
|
||||
ScolarFormSemestreValidation,
|
||||
ScolarAutorisationInscription,
|
||||
)
|
||||
from app.models.preferences import ScoPreference
|
||||
|
||||
from app.models.but_refcomp import (
|
||||
|
@ -158,7 +158,7 @@ class FormSemestre(db.Model):
|
||||
|
||||
@cached_property
|
||||
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 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
|
||||
|
||||
|
||||
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):
|
||||
"""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]
|
||||
|
||||
def get_etud_moy_infos(self, etudid):
|
||||
def get_etud_moy_infos(self, etudid): # XXX OBSOLETE
|
||||
"""Infos sur moyennes"""
|
||||
return self.etud_moy_infos[etudid]
|
||||
|
||||
@ -1011,7 +1011,10 @@ class NotesTable:
|
||||
cnx = ndb.GetDBConnexion()
|
||||
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
|
||||
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},
|
||||
)
|
||||
decisions_jury = {}
|
||||
@ -1137,8 +1140,14 @@ class NotesTable:
|
||||
"""
|
||||
self.ue_capitalisees = scu.DictDefault(defaultvalue=[])
|
||||
cnx = None
|
||||
semestre_id = self.sem["semestre_id"]
|
||||
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:
|
||||
# Si la moyenne d'UE n'avait pas été stockée (anciennes versions de ScoDoc)
|
||||
# il faut la calculer ici et l'enregistrer
|
||||
@ -1308,7 +1317,7 @@ class NotesTable:
|
||||
"""Liste des evaluations de ce semestre, avec leur etat"""
|
||||
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"""
|
||||
return [
|
||||
e
|
||||
|
@ -142,7 +142,7 @@ def formsemestre_bulletinetud_dict(formsemestre_id, etudid, version="long"):
|
||||
prefs = sco_preferences.SemPreferences(formsemestre_id)
|
||||
# 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)
|
||||
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
||||
if not nt.get_etud_etat(etudid):
|
||||
raise ScoValueError("Etudiant non inscrit à ce semestre")
|
||||
I = scu.DictDefault(defaultvalue="")
|
||||
|
@ -59,9 +59,9 @@ import traceback
|
||||
|
||||
from flask import g
|
||||
|
||||
from app import log
|
||||
from app.scodoc import notesdb as ndb
|
||||
from app.scodoc import sco_utils as scu
|
||||
from app import log
|
||||
|
||||
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)
|
||||
ResultatsSemestreCache.delete_many(formsemestre_ids)
|
||||
ValidationsSemestreCache.delete_many(formsemestre_ids)
|
||||
|
||||
|
||||
class DefferedSemCacheManager:
|
||||
@ -319,10 +320,20 @@ class DefferedSemCacheManager:
|
||||
|
||||
# ---- Nouvelles classes ScoDoc 9.2
|
||||
class ResultatsSemestreCache(ScoDocCache):
|
||||
"""Cache pour les résultats ResultatsSemestre.
|
||||
"""Cache pour les résultats ResultatsSemestre (notes et moyennes)
|
||||
Clé: formsemestre_id
|
||||
Valeur: { un paquet de dataframes }
|
||||
"""
|
||||
|
||||
prefix = "RSEM"
|
||||
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
|
||||
|
||||
|
||||
def code_semestre_validant(code):
|
||||
def code_semestre_validant(code: str) -> bool:
|
||||
"Vrai si ce CODE entraine la validation du semestre"
|
||||
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)"
|
||||
return CODES_SEM_ATTENTES.get(code, False)
|
||||
|
||||
|
||||
def code_ue_validant(code):
|
||||
"Vrai si ce code entraine la validation de l'UE"
|
||||
def code_ue_validant(code: str) -> bool:
|
||||
"Vrai si ce code entraine la validation des UEs du semestre."
|
||||
return CODES_UE_VALIDES.get(code, False)
|
||||
|
||||
|
||||
@ -259,6 +259,7 @@ class TypeParcours(object):
|
||||
) # par defaut, autorise tous les types d'UE
|
||||
APC_SAE = False # Approche par compétences avec ressources et SAÉs
|
||||
USE_REFERENTIEL_COMPETENCES = False # Lien avec ref. comp.
|
||||
ECTS_FONDAMENTAUX_PER_YEAR = 0.0 # pour ISCID, deprecated
|
||||
|
||||
def check(self, formation=None):
|
||||
return True, "" # status, diagnostic_message
|
||||
|
@ -32,7 +32,7 @@ from flask_login import current_user
|
||||
|
||||
from app import db
|
||||
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
|
||||
import app.scodoc.sco_utils as scu
|
||||
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)
|
||||
etat = _eval_etat(evals)
|
||||
etat["attente"] = moduleimpl_id in [
|
||||
m["moduleimpl_id"] for m in nt.get_moduleimpls_attente()
|
||||
] # > liste moduleimpl en attente
|
||||
# Il y a-t-il des notes en attente dans ce module ?
|
||||
etat["attente"] = nt.modimpls_results[moduleimpl_id].en_attente
|
||||
return etat
|
||||
|
||||
|
||||
|
@ -991,9 +991,9 @@ def formsemestre_status(formsemestre_id=None):
|
||||
modimpls = sco_moduleimpl.moduleimpl_withmodule_list(
|
||||
formsemestre_id=formsemestre_id
|
||||
)
|
||||
nt = sco_cache.NotesTableCache.get(formsemestre_id)
|
||||
# WIP formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||
# WIP nt = res_sem.load_formsemestre_result(formsemestre)
|
||||
# nt = sco_cache.NotesTableCache.get(formsemestre_id)
|
||||
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||
nt = res_sem.load_formsemestre_results(formsemestre)
|
||||
|
||||
# Construit la liste de tous les enseignants de ce semestre:
|
||||
mails_enseignants = set(
|
||||
|
@ -549,7 +549,7 @@ def formsemestre_recap_parcours_table(
|
||||
ass = ""
|
||||
|
||||
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"])
|
||||
if is_cur:
|
||||
type_sem = "*" # now unused
|
||||
@ -692,7 +692,7 @@ def formsemestre_recap_parcours_table(
|
||||
sco_preferences.get_preference("bul_show_ects", sem["formsemestre_id"])
|
||||
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(
|
||||
'<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)
|
||||
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>'
|
||||
% (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>')
|
||||
# ECTS validables dans chaque UE
|
||||
@ -1062,7 +1062,7 @@ def formsemestre_validate_previous_ue(formsemestre_id, etudid):
|
||||
"title": "Indice du semestre",
|
||||
"explanation": "Facultatif: indice du semestre dans la formation",
|
||||
"allow_null": True,
|
||||
"allowed_values": [""] + [str(x) for x in range(11)],
|
||||
"allowed_values": [""] + [x for x in range(11)],
|
||||
"labels": ["-"] + list(range(11)),
|
||||
},
|
||||
),
|
||||
|
@ -837,7 +837,7 @@ def _add_apc_columns(
|
||||
# => On recharge tout dans les nouveaux modèles
|
||||
# rows est une liste de dict avec une clé "etudid"
|
||||
# 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]
|
||||
|
||||
# XXX A ENLEVER TODO
|
||||
|
@ -28,6 +28,7 @@
|
||||
"""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.notesdb as ndb
|
||||
from app import log
|
||||
@ -678,10 +679,10 @@ class SituationEtudParcoursECTS(SituationEtudParcoursGeneric):
|
||||
|
||||
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 (
|
||||
etud_moy_infos["ects_pot"] >= self.parcours.ECTS_BARRE_VALID_YEAR
|
||||
and etud_moy_infos["ects_pot"] >= self.parcours.ECTS_FONDAMENTAUX_PER_YEAR
|
||||
etud_ects_infos["ects_pot"] >= self.parcours.ECTS_BARRE_VALID_YEAR
|
||||
and etud_ects_infos["ects_pot"] >= self.parcours.ECTS_FONDAMENTAUX_PER_YEAR
|
||||
):
|
||||
choices = [
|
||||
DecisionSem(
|
||||
@ -954,6 +955,9 @@ def do_formsemestre_validate_ue(
|
||||
is_external=False,
|
||||
):
|
||||
"""Ajoute ou change validation UE"""
|
||||
if semestre_id is None:
|
||||
ue = UniteEns.query.get_or_404(ue_id)
|
||||
semestre_id = ue.semestre_idx
|
||||
args = {
|
||||
"formsemestre_id": formsemestre_id,
|
||||
"etudid": etudid,
|
||||
@ -971,7 +975,8 @@ def do_formsemestre_validate_ue(
|
||||
if formsemestre_id:
|
||||
cond += " and formsemestre_id=%(formsemestre_id)s"
|
||||
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)
|
||||
# insert
|
||||
args["code"] = code
|
||||
@ -980,7 +985,7 @@ def do_formsemestre_validate_ue(
|
||||
# stocke la moyenne d'UE capitalisée:
|
||||
moy_ue = nt.get_etud_ue_status(etudid, ue_id)["moy"]
|
||||
args["moy_ue"] = moy_ue
|
||||
log("formsemestre_validate_ue: %s" % args)
|
||||
log("formsemestre_validate_ue: create %s" % args)
|
||||
if code != None:
|
||||
scolar_formsemestre_validation_create(cnx, args)
|
||||
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.
|
||||
|
||||
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()
|
||||
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
|
||||
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 nf2.id=%(formation_id)s
|
||||
|
||||
and SFV.ue_id = ue.id
|
||||
and SFV.code = 'ADM'
|
||||
and SFV.etudid = %(etudid)s
|
||||
|
||||
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)
|
||||
|
||||
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)
|
||||
) )
|
||||
""",
|
||||
{
|
||||
"etudid": etudid,
|
||||
"formation_id": sem["formation_id"],
|
||||
"semestre_id": sem["semestre_id"],
|
||||
"date_debut": ndb.DateDMYtoISO(sem["date_debut"]),
|
||||
"formation_id": formation_id,
|
||||
"semestre_id": semestre_idx,
|
||||
"date_debut": date_debut,
|
||||
},
|
||||
)
|
||||
|
||||
|
@ -140,23 +140,23 @@ def descr_autorisations(autorisations):
|
||||
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)
|
||||
decision_ues est le resultat de nt.get_etud_decision_ues
|
||||
Chaque resultat est un dict: { ue_code : ects }
|
||||
"""
|
||||
raise NotImplementedError() # XXX #sco92
|
||||
# ré-écrire en utilisant
|
||||
if not decision_ues:
|
||||
return {}, {}
|
||||
return {}
|
||||
|
||||
ects_by_ue_code = {}
|
||||
ects_by_ue_type = scu.DictDefault(defaultvalue=0) # { ue_type : ects validés }
|
||||
for ue_id in decision_ues:
|
||||
d = decision_ues[ue_id]
|
||||
ue = nt.uedict[ue_id]
|
||||
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):
|
||||
@ -249,11 +249,8 @@ def dict_pvjury(
|
||||
|
||||
ects_capitalises_by_ue_code = _comp_ects_capitalises_by_ue_code(nt, etudid)
|
||||
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(
|
||||
nt, d["decisions_ue"]
|
||||
)
|
||||
ects_by_ue_code = _comp_ects_by_ue_code(nt, d["decisions_ue"])
|
||||
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(
|
||||
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 json
|
||||
@ -41,6 +41,7 @@ from app.comp import res_sem
|
||||
from app.comp.res_common import NotesTableCompat
|
||||
from app.models import FormSemestre
|
||||
from app.models.etudiants import Identite
|
||||
from app.models.evaluations import Evaluation
|
||||
|
||||
import app.scodoc.sco_utils as scu
|
||||
from app.scodoc import html_sco_header
|
||||
@ -308,8 +309,8 @@ def make_formsemestre_recapcomplet(
|
||||
|
||||
# nt = sco_cache.NotesTableCache.get(formsemestre_id) # sco91
|
||||
# sco92 :
|
||||
nt: NotesTableCompat = res_sem.load_formsemestre_result(formsemestre)
|
||||
modimpls = nt.get_modimpls_dict()
|
||||
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
||||
modimpls = formsemestre.modimpls_sorted
|
||||
ues = nt.get_ues_stat_dict() # incluant le(s) UE de sport
|
||||
#
|
||||
# if formsemestre.formation.is_apc():
|
||||
@ -367,15 +368,16 @@ def make_formsemestre_recapcomplet(
|
||||
pass
|
||||
if not hidemodules and not ue["is_external"]:
|
||||
for modimpl in modimpls:
|
||||
if modimpl["module"]["ue_id"] == ue["ue_id"]:
|
||||
code = modimpl["module"]["code"]
|
||||
if modimpl.module.ue_id == ue["ue_id"]:
|
||||
code = modimpl.module.code
|
||||
h.append(code)
|
||||
cod2mod[code] = modimpl # pour fabriquer le lien
|
||||
if format == "xlsall":
|
||||
evals = nt.get_mod_evaluation_etat_list(
|
||||
modimpl["moduleimpl_id"]
|
||||
)
|
||||
mod_evals[modimpl["moduleimpl_id"]] = evals
|
||||
evals = nt.modimpls_results[
|
||||
modimpl.id
|
||||
].get_evaluations_completes(modimpl)
|
||||
# evals = nt.get_mod_evaluation_etat_list(...
|
||||
mod_evals[modimpl.id] = evals
|
||||
h += _list_notes_evals_titles(code, evals)
|
||||
|
||||
h += admission_extra_cols
|
||||
@ -483,7 +485,7 @@ def make_formsemestre_recapcomplet(
|
||||
if not hidemodules and not ue["is_external"]:
|
||||
j = 0
|
||||
for modimpl in modimpls:
|
||||
if modimpl["module"]["ue_id"] == ue["ue_id"]:
|
||||
if modimpl.module.ue_id == ue["ue_id"]:
|
||||
l.append(
|
||||
fmtnum(
|
||||
scu.fmt_note(
|
||||
@ -492,9 +494,7 @@ def make_formsemestre_recapcomplet(
|
||||
)
|
||||
) # moyenne etud dans module
|
||||
if format == "xlsall":
|
||||
l += _list_notes_evals(
|
||||
mod_evals[modimpl["moduleimpl_id"]], etudid
|
||||
)
|
||||
l += _list_notes_evals(mod_evals[modimpl.id], etudid)
|
||||
j += 1
|
||||
if not hidebac:
|
||||
for k in admission_extra_cols:
|
||||
@ -509,9 +509,7 @@ def make_formsemestre_recapcomplet(
|
||||
if not hidemodules: # moy/min/max dans chaque module
|
||||
mods_stats = {} # moduleimpl_id : stats
|
||||
for modimpl in modimpls:
|
||||
mods_stats[modimpl["moduleimpl_id"]] = nt.get_mod_stats(
|
||||
modimpl["moduleimpl_id"]
|
||||
)
|
||||
mods_stats[modimpl.id] = nt.get_mod_stats(modimpl.id)
|
||||
|
||||
def add_bottom_stat(key, title, corner_value=""):
|
||||
l = ["", title]
|
||||
@ -551,16 +549,16 @@ def make_formsemestre_recapcomplet(
|
||||
# ue_index.append(len(l) - 1)
|
||||
if not hidemodules and not ue["is_external"]:
|
||||
for modimpl in modimpls:
|
||||
if modimpl["module"]["ue_id"] == ue["ue_id"]:
|
||||
if modimpl.module.ue_id == ue["ue_id"]:
|
||||
if key == "coef":
|
||||
coef = modimpl["module"]["coefficient"]
|
||||
coef = modimpl.module.coefficient
|
||||
if format[:3] != "xls":
|
||||
coef = str(coef)
|
||||
l.append(coef)
|
||||
elif key == "ects":
|
||||
l.append("") # ECTS module ?
|
||||
else:
|
||||
val = mods_stats[modimpl["moduleimpl_id"]][key]
|
||||
val = mods_stats[modimpl.id][key]
|
||||
if key == "nb_valid_evals":
|
||||
if (
|
||||
format[:3] != "xls"
|
||||
@ -571,9 +569,7 @@ def make_formsemestre_recapcomplet(
|
||||
l.append(val)
|
||||
|
||||
if format == "xlsall":
|
||||
l += _list_notes_evals_stats(
|
||||
mod_evals[modimpl["moduleimpl_id"]], key
|
||||
)
|
||||
l += _list_notes_evals_stats(mod_evals[modimpl.id], key)
|
||||
if modejury:
|
||||
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("ects", "ECTS")
|
||||
|
||||
# Generation table au format demandé
|
||||
# Génération de la table au format demandé
|
||||
if format == "html":
|
||||
# Table format HTML
|
||||
H = [
|
||||
@ -838,63 +834,50 @@ def make_formsemestre_recapcomplet(
|
||||
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
|
||||
(pour table xls avec evals)
|
||||
"""
|
||||
L = []
|
||||
for e in evals:
|
||||
if (
|
||||
e["etat"]["evalcomplete"]
|
||||
or e["etat"]["evalattente"]
|
||||
or e["publish_incomplete"]
|
||||
):
|
||||
notes_db = sco_evaluation_db.do_evaluation_get_all_notes(e["evaluation_id"])
|
||||
if etudid in notes_db:
|
||||
val = notes_db[etudid]["value"]
|
||||
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)
|
||||
notes_db = sco_evaluation_db.do_evaluation_get_all_notes(e.evaluation_id)
|
||||
if etudid in notes_db:
|
||||
val = notes_db[etudid]["value"]
|
||||
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
|
||||
|
||||
|
||||
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"""
|
||||
L = []
|
||||
eval_index = len(evals) - 1
|
||||
for e in evals:
|
||||
if (
|
||||
e["etat"]["evalcomplete"]
|
||||
or e["etat"]["evalattente"]
|
||||
or e["publish_incomplete"]
|
||||
):
|
||||
L.append(codemodule + "-" + str(eval_index) + "-" + e["jour"].isoformat())
|
||||
L.append(codemodule + "-" + str(eval_index) + "-" + e.jour.isoformat())
|
||||
eval_index -= 1
|
||||
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"""
|
||||
L = []
|
||||
for e in evals:
|
||||
if (
|
||||
e["etat"]["evalcomplete"]
|
||||
or e["etat"]["evalattente"]
|
||||
or e["publish_incomplete"]
|
||||
):
|
||||
if key == "moy":
|
||||
val = e["etat"]["moy_num"]
|
||||
L.append(scu.fmt_note(val, keep_numeric=True))
|
||||
elif key == "max":
|
||||
L.append(e["note_max"])
|
||||
elif key == "min":
|
||||
L.append(0.0)
|
||||
elif key == "coef":
|
||||
L.append(e["coefficient"])
|
||||
else:
|
||||
L.append("") # on n'a pas sous la main min/max
|
||||
if key == "moy":
|
||||
# TODO #sco92
|
||||
# val = e["etat"]["moy_num"]
|
||||
# L.append(scu.fmt_note(val, keep_numeric=True))
|
||||
L.append("")
|
||||
elif key == "max":
|
||||
L.append(e.note_max)
|
||||
elif key == "min":
|
||||
L.append(0.0)
|
||||
elif key == "coef":
|
||||
L.append(e.coefficient)
|
||||
else:
|
||||
L.append("") # on n'a pas sous la main min/max
|
||||
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
|
||||
@permission_required(Permission.ScoView)
|
||||
@scodoc7func
|
||||
|
Loading…
Reference in New Issue
Block a user