forked from ScoDoc/ScoDoc
Merge branch 'refactor_nt' of https://scodoc.org/git/ScoDoc/ScoDoc into entreprises
This commit is contained in:
commit
e67cbf87eb
@ -77,11 +77,20 @@ class BulletinBUT(ResultatsSemestreBUT):
|
|||||||
"saes": self.etud_ue_mod_results(etud, ue, self.saes),
|
"saes": self.etud_ue_mod_results(etud, ue, self.saes),
|
||||||
}
|
}
|
||||||
if ue.type != UE_SPORT:
|
if ue.type != UE_SPORT:
|
||||||
|
if sco_preferences.get_preference(
|
||||||
|
"bul_show_ue_rangs", self.formsemestre.id
|
||||||
|
):
|
||||||
|
rangs, effectif = self.ue_rangs[ue.id]
|
||||||
|
rang = rangs[etud.id]
|
||||||
|
else:
|
||||||
|
rang, effectif = "", 0
|
||||||
d["moyenne"] = {
|
d["moyenne"] = {
|
||||||
"value": fmt_note(self.etud_moy_ue[ue.id][etud.id]),
|
"value": fmt_note(self.etud_moy_ue[ue.id][etud.id]),
|
||||||
"min": fmt_note(self.etud_moy_ue[ue.id].min()),
|
"min": fmt_note(self.etud_moy_ue[ue.id].min()),
|
||||||
"max": fmt_note(self.etud_moy_ue[ue.id].max()),
|
"max": fmt_note(self.etud_moy_ue[ue.id].max()),
|
||||||
"moy": fmt_note(self.etud_moy_ue[ue.id].mean()),
|
"moy": fmt_note(self.etud_moy_ue[ue.id].mean()),
|
||||||
|
"rang": rang,
|
||||||
|
"total": effectif, # nb etud avec note dans cette UE
|
||||||
}
|
}
|
||||||
else:
|
else:
|
||||||
# ceci suppose que l'on a une seule UE bonus,
|
# ceci suppose que l'on a une seule UE bonus,
|
||||||
@ -227,8 +236,8 @@ class BulletinBUT(ResultatsSemestreBUT):
|
|||||||
"date_debut": formsemestre.date_debut.isoformat(),
|
"date_debut": formsemestre.date_debut.isoformat(),
|
||||||
"date_fin": formsemestre.date_fin.isoformat(),
|
"date_fin": formsemestre.date_fin.isoformat(),
|
||||||
"annee_universitaire": self.formsemestre.annee_scolaire_str(),
|
"annee_universitaire": self.formsemestre.annee_scolaire_str(),
|
||||||
"inscription": "TODO-MM-JJ", # XXX TODO
|
|
||||||
"numero": formsemestre.semestre_id,
|
"numero": formsemestre.semestre_id,
|
||||||
|
"inscription": "", # inutilisé mais nécessaire pour le js de Seb.
|
||||||
"groupes": [], # XXX TODO
|
"groupes": [], # XXX TODO
|
||||||
"absences": {
|
"absences": {
|
||||||
"injustifie": nbabsjust,
|
"injustifie": nbabsjust,
|
||||||
|
@ -21,7 +21,7 @@ class StatsMoyenne:
|
|||||||
Les valeurs NAN ou non numériques sont toujours enlevées.
|
Les valeurs NAN ou non numériques sont toujours enlevées.
|
||||||
Si vals is None, renvoie des zéros (utilisé pour UE bonus)
|
Si vals is None, renvoie des zéros (utilisé pour UE bonus)
|
||||||
"""
|
"""
|
||||||
if vals is None:
|
if vals is None or len(vals) == 0:
|
||||||
self.moy = self.min = self.max = self.size = self.nb_vals = 0
|
self.moy = self.min = self.max = self.size = self.nb_vals = 0
|
||||||
else:
|
else:
|
||||||
self.moy = np.nanmean(vals)
|
self.moy = np.nanmean(vals)
|
||||||
|
154
app/comp/jury.py
Normal file
154
app/comp/jury.py
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
##############################################################################
|
||||||
|
# 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' }}}
|
||||||
|
"""
|
||||||
|
self.ue_capitalisees: pd.DataFrame = None
|
||||||
|
"""DataFrame, index etudid
|
||||||
|
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 : note enregistrée,
|
||||||
|
event_date : date de la validation (jury)."""
|
||||||
|
|
||||||
|
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.
|
||||||
|
@ -12,6 +12,7 @@ from app.comp import moy_ue, moy_sem, inscr_mod
|
|||||||
from app.comp.res_common import NotesTableCompat
|
from app.comp.res_common import NotesTableCompat
|
||||||
from app.comp.bonus_spo import BonusSport
|
from app.comp.bonus_spo import BonusSport
|
||||||
from app.models import ScoDocSiteConfig
|
from app.models import ScoDocSiteConfig
|
||||||
|
from app.models.ues import UniteEns
|
||||||
from app.scodoc.sco_codes_parcours import UE_SPORT
|
from app.scodoc.sco_codes_parcours import UE_SPORT
|
||||||
|
|
||||||
|
|
||||||
@ -93,12 +94,16 @@ class ResultatsSemestreBUT(NotesTableCompat):
|
|||||||
self.etud_moy_ue.clip(lower=0.0, upper=20.0, inplace=True)
|
self.etud_moy_ue.clip(lower=0.0, upper=20.0, inplace=True)
|
||||||
|
|
||||||
# Moyenne générale indicative:
|
# Moyenne générale indicative:
|
||||||
# (note: le bonus sport a déjà été appliqué aux moyenens d'UE, et impacte
|
# (note: le bonus sport a déjà été appliqué aux moyennes d'UE, et impacte
|
||||||
# donc la moyenne indicative)
|
# donc la moyenne indicative)
|
||||||
self.etud_moy_gen = moy_sem.compute_sem_moys_apc(
|
self.etud_moy_gen = moy_sem.compute_sem_moys_apc(
|
||||||
self.etud_moy_ue, self.modimpl_coefs_df
|
self.etud_moy_ue, self.modimpl_coefs_df
|
||||||
)
|
)
|
||||||
self.etud_moy_gen_ranks = moy_sem.comp_ranks_series(self.etud_moy_gen)
|
# --- UE capitalisées
|
||||||
|
self.apply_capitalisation()
|
||||||
|
|
||||||
|
# --- Classements:
|
||||||
|
self.compute_rangs()
|
||||||
|
|
||||||
def get_etud_mod_moy(self, moduleimpl_id: int, etudid: int) -> float:
|
def get_etud_mod_moy(self, moduleimpl_id: int, etudid: int) -> float:
|
||||||
"""La moyenne de l'étudiant dans le moduleimpl
|
"""La moyenne de l'étudiant dans le moduleimpl
|
||||||
@ -109,3 +114,12 @@ class ResultatsSemestreBUT(NotesTableCompat):
|
|||||||
etud_idx = self.etud_index[etudid]
|
etud_idx = self.etud_index[etudid]
|
||||||
# moyenne sur les UE:
|
# moyenne sur les UE:
|
||||||
return self.sem_cube[etud_idx, mod_idx].mean()
|
return self.sem_cube[etud_idx, mod_idx].mean()
|
||||||
|
|
||||||
|
def compute_etud_ue_coef(self, etudid: int, ue: UniteEns) -> float:
|
||||||
|
"""Détermine le coefficient de l'UE pour cet étudiant.
|
||||||
|
N'est utilisé que pour l'injection des UE capitalisées dans la
|
||||||
|
moyenne générale.
|
||||||
|
En BUT, c'est simple: Coef = somme des coefs des modules vers cette UE.
|
||||||
|
(ne dépend pas des modules auxquels est inscrit l'étudiant, ).
|
||||||
|
"""
|
||||||
|
return self.modimpl_coefs_df.loc[ue.id].sum()
|
||||||
|
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},
|
||||||
|
)
|
@ -6,15 +6,24 @@
|
|||||||
|
|
||||||
"""Résultats semestres classiques (non APC)
|
"""Résultats semestres classiques (non APC)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
|
from sqlalchemy.sql import text
|
||||||
|
|
||||||
from app.comp import moy_mod, moy_ue, moy_sem, inscr_mod
|
from flask import g, url_for
|
||||||
|
|
||||||
|
from app import db
|
||||||
|
from app import log
|
||||||
|
from app.comp import moy_mod, moy_ue, inscr_mod
|
||||||
from app.comp.res_common import NotesTableCompat
|
from app.comp.res_common import NotesTableCompat
|
||||||
from app.comp.bonus_spo import BonusSport
|
from app.comp.bonus_spo import BonusSport
|
||||||
from app.models import ScoDocSiteConfig
|
from app.models import ScoDocSiteConfig
|
||||||
|
from app.models.etudiants import Identite
|
||||||
from app.models.formsemestre import FormSemestre
|
from app.models.formsemestre import FormSemestre
|
||||||
|
from app.models.ues import UniteEns
|
||||||
from app.scodoc.sco_codes_parcours import UE_SPORT
|
from app.scodoc.sco_codes_parcours import UE_SPORT
|
||||||
|
from app.scodoc.sco_exceptions import ScoValueError
|
||||||
from app.scodoc.sco_utils import ModuleType
|
from app.scodoc.sco_utils import ModuleType
|
||||||
|
|
||||||
|
|
||||||
@ -104,8 +113,11 @@ class ResultatsSemestreClassic(NotesTableCompat):
|
|||||||
self.bonus = (
|
self.bonus = (
|
||||||
bonus_mg # compat nt, utilisé pour l'afficher sur les bulletins
|
bonus_mg # compat nt, utilisé pour l'afficher sur les bulletins
|
||||||
)
|
)
|
||||||
|
# --- UE capitalisées
|
||||||
|
self.apply_capitalisation()
|
||||||
|
|
||||||
# --- Classements:
|
# --- Classements:
|
||||||
self.etud_moy_gen_ranks = moy_sem.comp_ranks_series(self.etud_moy_gen)
|
self.compute_rangs()
|
||||||
|
|
||||||
def get_etud_mod_moy(self, moduleimpl_id: int, etudid: int) -> float:
|
def get_etud_mod_moy(self, moduleimpl_id: int, etudid: int) -> float:
|
||||||
"""La moyenne de l'étudiant dans le moduleimpl
|
"""La moyenne de l'étudiant dans le moduleimpl
|
||||||
@ -132,6 +144,42 @@ class ResultatsSemestreClassic(NotesTableCompat):
|
|||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def compute_etud_ue_coef(self, etudid: int, ue: UniteEns) -> float:
|
||||||
|
"""Détermine le coefficient de l'UE pour cet étudiant.
|
||||||
|
N'est utilisé que pour l'injection des UE capitalisées dans la
|
||||||
|
moyenne générale.
|
||||||
|
Coef = somme des coefs des modules de l'UE auxquels il est inscrit
|
||||||
|
"""
|
||||||
|
c = comp_etud_sum_coef_modules_ue(self.formsemestre.id, etudid, ue["ue_id"])
|
||||||
|
if c is not None: # inscrit à au moins un module de cette UE
|
||||||
|
return c
|
||||||
|
# arfff: aucun moyen de déterminer le coefficient de façon sûre
|
||||||
|
log(
|
||||||
|
"* oups: calcul coef UE impossible\nformsemestre_id='%s'\netudid='%s'\nue=%s"
|
||||||
|
% (self.formsemestre.id, etudid, ue)
|
||||||
|
)
|
||||||
|
etud: Identite = Identite.query.get(etudid)
|
||||||
|
raise ScoValueError(
|
||||||
|
"""<div class="scovalueerror"><p>Coefficient de l'UE capitalisée %s impossible à déterminer
|
||||||
|
pour l'étudiant <a href="%s" class="discretelink">%s</a></p>
|
||||||
|
<p>Il faut <a href="%s">saisir le coefficient de cette UE avant de continuer</a></p>
|
||||||
|
</div>
|
||||||
|
"""
|
||||||
|
% (
|
||||||
|
ue.acronyme,
|
||||||
|
url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid),
|
||||||
|
etud.nom_disp(),
|
||||||
|
url_for(
|
||||||
|
"notes.formsemestre_edit_uecoefs",
|
||||||
|
scodoc_dept=g.scodoc_dept,
|
||||||
|
formsemestre_id=self.formsemestre.id,
|
||||||
|
err_ue_id=ue["ue_id"],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return 0.0 # ?
|
||||||
|
|
||||||
|
|
||||||
def notes_sem_load_matrix(formsemestre: FormSemestre) -> tuple[np.ndarray, dict]:
|
def notes_sem_load_matrix(formsemestre: FormSemestre) -> tuple[np.ndarray, dict]:
|
||||||
"""Calcule la matrice des notes du semestre
|
"""Calcule la matrice des notes du semestre
|
||||||
@ -165,3 +213,29 @@ def notes_sem_assemble_matrix(modimpls_notes: list[pd.Series]) -> np.ndarray:
|
|||||||
modimpls_notes = np.stack(modimpls_notes_arr)
|
modimpls_notes = np.stack(modimpls_notes_arr)
|
||||||
# passe de (mod x etud) à (etud x mod)
|
# passe de (mod x etud) à (etud x mod)
|
||||||
return modimpls_notes.T
|
return modimpls_notes.T
|
||||||
|
|
||||||
|
|
||||||
|
def comp_etud_sum_coef_modules_ue(formsemestre_id, etudid, ue_id):
|
||||||
|
"""Somme des coefficients des modules de l'UE dans lesquels cet étudiant est inscrit
|
||||||
|
ou None s'il n'y a aucun module.
|
||||||
|
"""
|
||||||
|
# comme l'ancien notes_table.comp_etud_sum_coef_modules_ue
|
||||||
|
# mais en raw sqlalchemy et la somme en SQL
|
||||||
|
sql = text(
|
||||||
|
"""
|
||||||
|
SELECT sum(mod.coefficient)
|
||||||
|
FROM notes_modules mod, notes_moduleimpl mi, notes_moduleimpl_inscription ins
|
||||||
|
WHERE mod.id = mi.module_id
|
||||||
|
and ins.etudid = :etudid
|
||||||
|
and ins.moduleimpl_id = mi.id
|
||||||
|
and mi.formsemestre_id = :formsemestre_id
|
||||||
|
and mod.ue_id = :ue_id
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
cursor = db.session.execute(
|
||||||
|
sql, {"etudid": etudid, "formsemestre_id": formsemestre_id, "ue_id": ue_id}
|
||||||
|
)
|
||||||
|
r = cursor.fetchone()
|
||||||
|
if r is None:
|
||||||
|
return None
|
||||||
|
return r[0]
|
||||||
|
@ -8,13 +8,22 @@ from collections import defaultdict, Counter
|
|||||||
from functools import cached_property
|
from functools import cached_property
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
|
|
||||||
|
from flask import g
|
||||||
|
|
||||||
from app.comp.aux_stats import StatsMoyenne
|
from app.comp.aux_stats import StatsMoyenne
|
||||||
|
from app.comp import moy_sem
|
||||||
|
from app.comp.res_cache import ResultatsCache
|
||||||
|
from app.comp import res_sem
|
||||||
from app.comp.moy_mod import ModuleImplResults
|
from app.comp.moy_mod import ModuleImplResults
|
||||||
from app.models import FormSemestre, Identite, ModuleImpl
|
from app.models import FormSemestre, Identite, ModuleImpl
|
||||||
|
from app.models import FormSemestreUECoef
|
||||||
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
|
||||||
|
from app.scodoc.sco_exceptions import ScoValueError
|
||||||
|
|
||||||
# Il faut bien distinguer
|
# Il faut bien distinguer
|
||||||
# - ce qui est caché de façon persistente (via redis):
|
# - ce qui est caché de façon persistente (via redis):
|
||||||
@ -25,7 +34,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 +45,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 +55,10 @@ class ResultatsSemestre:
|
|||||||
self.etud_moy_gen = {}
|
self.etud_moy_gen = {}
|
||||||
self.etud_moy_gen_ranks = {}
|
self.etud_moy_gen_ranks = {}
|
||||||
self.modimpls_results: ModuleImplResults = None
|
self.modimpls_results: ModuleImplResults = None
|
||||||
|
"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)"""
|
||||||
|
self.validations = None
|
||||||
# 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 +94,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 +117,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
|
||||||
@ -148,6 +161,115 @@ class ResultatsSemestre:
|
|||||||
"""
|
"""
|
||||||
return self.etud_moy_ue > (seuil - scu.NOTES_TOLERANCE)
|
return self.etud_moy_ue > (seuil - scu.NOTES_TOLERANCE)
|
||||||
|
|
||||||
|
def apply_capitalisation(self):
|
||||||
|
"""Recalcule la moyenne générale pour prendre en compte d'éventuelles
|
||||||
|
UE capitalisées.
|
||||||
|
"""
|
||||||
|
# Supposant qu'il y a peu d'UE capitalisées,
|
||||||
|
# on va soustraire la moyenne d'UE et ajouter celle de l'UE capitalisée.
|
||||||
|
# return # XXX XXX XXX
|
||||||
|
if not self.validations:
|
||||||
|
self.validations = res_sem.load_formsemestre_validations(self.formsemestre)
|
||||||
|
ue_capitalisees = self.validations.ue_capitalisees
|
||||||
|
ue_by_code = {}
|
||||||
|
for etudid in ue_capitalisees.index:
|
||||||
|
recompute_mg = False
|
||||||
|
# ue_codes = set(ue_capitalisees.loc[etudid]["ue_code"])
|
||||||
|
# for ue_code in ue_codes:
|
||||||
|
# ue = ue_by_code.get(ue_code)
|
||||||
|
# if ue is None:
|
||||||
|
# ue = self.formsemestre.query_ues.filter_by(ue_code=ue_code)
|
||||||
|
# ue_by_code[ue_code] = ue
|
||||||
|
|
||||||
|
# Quand il y a une capitalisation, vérifie toutes les UEs
|
||||||
|
sum_notes_ue = 0.0
|
||||||
|
sum_coefs_ue = 0.0
|
||||||
|
for ue in self.formsemestre.query_ues():
|
||||||
|
ue_cap = self.get_etud_ue_status(etudid, ue.id)
|
||||||
|
if ue_cap["is_capitalized"]:
|
||||||
|
recompute_mg = True
|
||||||
|
coef = ue_cap["coef_ue"]
|
||||||
|
if not np.isnan(ue_cap["moy"]):
|
||||||
|
sum_notes_ue += ue_cap["moy"] * coef
|
||||||
|
sum_coefs_ue += coef
|
||||||
|
|
||||||
|
if recompute_mg and sum_coefs_ue > 0.0:
|
||||||
|
# On doit prendre en compte une ou plusieurs UE capitalisées
|
||||||
|
# et donc recalculer la moyenne générale
|
||||||
|
self.etud_moy_gen[etudid] = sum_notes_ue / sum_coefs_ue
|
||||||
|
|
||||||
|
def _get_etud_ue_cap(self, etudid, ue):
|
||||||
|
""""""
|
||||||
|
capitalisations = self.validations.ue_capitalisees.loc[etudid]
|
||||||
|
if isinstance(capitalisations, pd.DataFrame):
|
||||||
|
ue_cap = capitalisations[capitalisations["ue_code"] == ue.ue_code]
|
||||||
|
if isinstance(ue_cap, pd.DataFrame) and not ue_cap.empty:
|
||||||
|
# si plusieurs fois capitalisée, prend le max
|
||||||
|
cap_idx = ue_cap["moy_ue"].values.argmax()
|
||||||
|
ue_cap = ue_cap.iloc[cap_idx]
|
||||||
|
else:
|
||||||
|
if capitalisations["ue_code"] == ue.ue_code:
|
||||||
|
ue_cap = capitalisations
|
||||||
|
else:
|
||||||
|
ue_cap = None
|
||||||
|
return ue_cap
|
||||||
|
|
||||||
|
def get_etud_ue_status(self, etudid: int, ue_id: int) -> dict:
|
||||||
|
"""L'état de l'UE pour cet étudiant.
|
||||||
|
Result: dict
|
||||||
|
"""
|
||||||
|
if not self.validations:
|
||||||
|
self.validations = res_sem.load_formsemestre_validations(self.formsemestre)
|
||||||
|
ue = UniteEns.query.get(ue_id) # TODO cacher nos UEs ?
|
||||||
|
cur_moy_ue = self.etud_moy_ue[ue_id][etudid]
|
||||||
|
moy_ue = cur_moy_ue
|
||||||
|
is_capitalized = False
|
||||||
|
if etudid in self.validations.ue_capitalisees.index:
|
||||||
|
ue_cap = self._get_etud_ue_cap(etudid, ue)
|
||||||
|
if ue_cap is not None and not ue_cap.empty:
|
||||||
|
if ue_cap["moy_ue"] > cur_moy_ue:
|
||||||
|
moy_ue = ue_cap["moy_ue"]
|
||||||
|
is_capitalized = True
|
||||||
|
if is_capitalized:
|
||||||
|
coef_ue = 1.0
|
||||||
|
coef_ue = self.etud_coef_ue_df[ue_id][etudid]
|
||||||
|
|
||||||
|
return {
|
||||||
|
"is_capitalized": is_capitalized,
|
||||||
|
"is_external": ue_cap["is_external"] if is_capitalized else ue.is_external,
|
||||||
|
"coef_ue": coef_ue,
|
||||||
|
"cur_moy_ue": cur_moy_ue,
|
||||||
|
"moy": moy_ue,
|
||||||
|
"event_date": ue_cap["event_date"] if is_capitalized else None,
|
||||||
|
"ue": ue.to_dict(),
|
||||||
|
"formsemestre_id": ue_cap["formsemestre_id"] if is_capitalized else None,
|
||||||
|
"capitalized_ue_id": ue_cap["ue_id"] if is_capitalized else None,
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_etud_ue_cap_coef(self, etudid, ue, ue_cap):
|
||||||
|
"""Calcule le coefficient d'une UE capitalisée, pour cet étudiant,
|
||||||
|
injectée dans le semestre courant.
|
||||||
|
|
||||||
|
ue : ue du semestre courant
|
||||||
|
|
||||||
|
ue_cap = resultat de formsemestre_get_etud_capitalisation
|
||||||
|
{ 'ue_id' (dans le semestre source),
|
||||||
|
'ue_code', 'moy', 'event_date','formsemestre_id' }
|
||||||
|
"""
|
||||||
|
# 1- Coefficient explicitement déclaré dans le semestre courant pour cette UE ?
|
||||||
|
ue_coef_db = FormSemestreUECoef.query.filter_by(
|
||||||
|
formsemestre_id=self.formsemestre.id, ue_id=ue.id
|
||||||
|
).first()
|
||||||
|
if ue_coef_db is not None:
|
||||||
|
return ue_coef_db.coefficient
|
||||||
|
|
||||||
|
# En APC: somme des coefs des modules vers cette UE
|
||||||
|
# En classique: Capitalisation UE externe: quel coef appliquer ?
|
||||||
|
# En ScoDoc 7, calculait la somme des coefs dans l'UE du semestre d'origine
|
||||||
|
# ici si l'étudiant est inscrit dans le semestre courant,
|
||||||
|
# somme des coefs des modules de l'UE auxquels il est inscrit
|
||||||
|
return self.compute_etud_ue_coef(etudid, ue)
|
||||||
|
|
||||||
|
|
||||||
# Pour raccorder le code des anciens codes qui attendent une NoteTable
|
# Pour raccorder le code des anciens codes qui attendent une NoteTable
|
||||||
class NotesTableCompat(ResultatsSemestre):
|
class NotesTableCompat(ResultatsSemestre):
|
||||||
@ -163,6 +285,8 @@ class NotesTableCompat(ResultatsSemestre):
|
|||||||
"bonus",
|
"bonus",
|
||||||
"bonus_ues",
|
"bonus_ues",
|
||||||
"malus",
|
"malus",
|
||||||
|
"etud_moy_gen_ranks",
|
||||||
|
"ue_rangs",
|
||||||
)
|
)
|
||||||
|
|
||||||
def __init__(self, formsemestre: FormSemestre):
|
def __init__(self, formsemestre: FormSemestre):
|
||||||
@ -243,6 +367,35 @@ class NotesTableCompat(ResultatsSemestre):
|
|||||||
modimpls_dict.append(d)
|
modimpls_dict.append(d)
|
||||||
return modimpls_dict
|
return modimpls_dict
|
||||||
|
|
||||||
|
def compute_rangs(self):
|
||||||
|
"""Calcule les classements
|
||||||
|
Moyenne générale: etud_moy_gen_ranks
|
||||||
|
Par UE:
|
||||||
|
"""
|
||||||
|
self.etud_moy_gen_ranks = moy_sem.comp_ranks_series(self.etud_moy_gen)
|
||||||
|
for ue in self.formsemestre.query_ues():
|
||||||
|
moy_ue = self.etud_moy_ue[ue.id]
|
||||||
|
self.ue_rangs[ue.id] = (
|
||||||
|
moy_sem.comp_ranks_series(moy_ue),
|
||||||
|
int(moy_ue.count()),
|
||||||
|
)
|
||||||
|
# .count() -> nb of non NaN values
|
||||||
|
|
||||||
|
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 +409,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,13 +442,29 @@ class NotesTableCompat(ResultatsSemestre):
|
|||||||
"""
|
"""
|
||||||
return self.etud_moy_gen[etudid]
|
return self.etud_moy_gen[etudid]
|
||||||
|
|
||||||
def get_etud_ue_status(self, etudid: int, ue_id: int):
|
def get_etud_ects_pot(self, etudid: int) -> dict:
|
||||||
coef_ue = self.etud_coef_ue_df[ue_id][etudid]
|
"""
|
||||||
|
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 {
|
return {
|
||||||
"cur_moy_ue": self.etud_moy_ue[ue_id][etudid],
|
"ects_pot": ects_pot,
|
||||||
"moy": self.etud_moy_ue[ue_id][etudid],
|
"ects_fond": 0.0, # not implemented (anciennemment pour école ingé)
|
||||||
"is_capitalized": False, # XXX TODO
|
|
||||||
"coef_ue": coef_ue, # XXX TODO
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def get_etud_rang(self, etudid: int):
|
def get_etud_rang(self, etudid: int):
|
||||||
@ -332,8 +500,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.
|
||||||
"""
|
"""
|
||||||
@ -427,10 +427,12 @@ class FormSemestreUECoef(db.Model):
|
|||||||
formsemestre_id = db.Column(
|
formsemestre_id = db.Column(
|
||||||
db.Integer,
|
db.Integer,
|
||||||
db.ForeignKey("notes_formsemestre.id"),
|
db.ForeignKey("notes_formsemestre.id"),
|
||||||
|
index=True,
|
||||||
)
|
)
|
||||||
ue_id = db.Column(
|
ue_id = db.Column(
|
||||||
db.Integer,
|
db.Integer,
|
||||||
db.ForeignKey("notes_ue.id"),
|
db.ForeignKey("notes_ue.id"),
|
||||||
|
index=True,
|
||||||
)
|
)
|
||||||
coefficient = db.Column(db.Float, nullable=False)
|
coefficient = db.Column(db.Float, nullable=False)
|
||||||
|
|
||||||
|
@ -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"),
|
||||||
|
)
|
@ -703,11 +703,12 @@ class NotesTable:
|
|||||||
où ue_status = {
|
où ue_status = {
|
||||||
'est_inscrit' : True si étudiant inscrit à au moins un module de cette UE
|
'est_inscrit' : True si étudiant inscrit à au moins un module de cette UE
|
||||||
'moy' : moyenne, avec capitalisation eventuelle
|
'moy' : moyenne, avec capitalisation eventuelle
|
||||||
|
'capitalized_ue_id' : id de l'UE capitalisée
|
||||||
'coef_ue' : coef de l'UE utilisé pour le calcul de la moyenne générale
|
'coef_ue' : coef de l'UE utilisé pour le calcul de la moyenne générale
|
||||||
(la somme des coefs des modules, ou le coef d'UE capitalisée,
|
(la somme des coefs des modules, ou le coef d'UE capitalisée,
|
||||||
ou encore le coef d'UE si l'option use_ue_coefs est active)
|
ou encore le coef d'UE si l'option use_ue_coefs est active)
|
||||||
'cur_moy_ue' : moyenne de l'UE en cours (sans considérer de capitalisation)
|
'cur_moy_ue' : moyenne de l'UE en cours (sans considérer de capitalisation)
|
||||||
'cur_coef_ue': coefficient de l'UE courante
|
'cur_coef_ue': coefficient de l'UE courante (inutilisé ?)
|
||||||
'is_capitalized' : True|False,
|
'is_capitalized' : True|False,
|
||||||
'ects_pot' : (float) nb de crédits ECTS qui seraient validés (sous réserve de validation par le jury),
|
'ects_pot' : (float) nb de crédits ECTS qui seraient validés (sous réserve de validation par le jury),
|
||||||
'ects_pot_fond': 0. si UE non fondamentale, = ects_pot sinon,
|
'ects_pot_fond': 0. si UE non fondamentale, = ects_pot sinon,
|
||||||
@ -935,7 +936,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 +1012,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 +1141,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 +1318,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
|
||||||
|
@ -123,9 +123,6 @@ def module_create(
|
|||||||
Sinon, donne le choix de l'UE de rattachement et utilise la première
|
Sinon, donne le choix de l'UE de rattachement et utilise la première
|
||||||
matière de cette UE (si elle n'existe pas, la crée).
|
matière de cette UE (si elle n'existe pas, la crée).
|
||||||
"""
|
"""
|
||||||
from app.scodoc import sco_formations
|
|
||||||
from app.scodoc import sco_edit_ue
|
|
||||||
|
|
||||||
if matiere_id:
|
if matiere_id:
|
||||||
matiere = Matiere.query.get_or_404(matiere_id)
|
matiere = Matiere.query.get_or_404(matiere_id)
|
||||||
ue = matiere.ue
|
ue = matiere.ue
|
||||||
@ -160,7 +157,7 @@ def module_create(
|
|||||||
else:
|
else:
|
||||||
H += [
|
H += [
|
||||||
f"""<h2>Création {object_name} dans la matière {matiere.titre},
|
f"""<h2>Création {object_name} dans la matière {matiere.titre},
|
||||||
(UE {ue.acronyme})</h2>
|
(UE {ue.acronyme}), semestre {ue.semestre_idx}</h2>
|
||||||
"""
|
"""
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -534,19 +531,15 @@ def module_edit(module_id=None):
|
|||||||
formsemestres=FormSemestre.query.filter(
|
formsemestres=FormSemestre.query.filter(
|
||||||
ModuleImpl.formsemestre_id == FormSemestre.id,
|
ModuleImpl.formsemestre_id == FormSemestre.id,
|
||||||
ModuleImpl.module_id == module_id,
|
ModuleImpl.module_id == module_id,
|
||||||
).all(),
|
)
|
||||||
|
.order_by(FormSemestre.date_debut)
|
||||||
|
.all(),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
if not unlocked:
|
if not unlocked:
|
||||||
H.append(
|
H.append(
|
||||||
"""<div class="ue_warning"><span>Formation verrouillée, seuls certains éléments peuvent être modifiés</span></div>"""
|
"""<div class="ue_warning"><span>Formation verrouillée, seuls certains éléments peuvent être modifiés</span></div>"""
|
||||||
)
|
)
|
||||||
if in_use:
|
|
||||||
H.append(
|
|
||||||
"""<div class="ue_warning"><span>Module déjà utilisé dans des semestres,
|
|
||||||
soyez prudents !
|
|
||||||
</span></div>"""
|
|
||||||
)
|
|
||||||
if is_apc:
|
if is_apc:
|
||||||
module_types = scu.ModuleType # tous les types
|
module_types = scu.ModuleType # tous les types
|
||||||
else:
|
else:
|
||||||
@ -728,30 +721,9 @@ def module_edit(module_id=None):
|
|||||||
initvalues=module,
|
initvalues=module,
|
||||||
submitlabel="Modifier ce module",
|
submitlabel="Modifier ce module",
|
||||||
)
|
)
|
||||||
# Affiche liste des formseemstre utilisant ce module
|
|
||||||
if in_use:
|
|
||||||
formsemestre_ids = {modimpl.formsemestre_id for modimpl in a_module.modimpls}
|
|
||||||
formsemestres = [FormSemestre.query.get(fid) for fid in formsemestre_ids]
|
|
||||||
formsemestres.sort(key=lambda f: f.date_debut)
|
|
||||||
items = [
|
|
||||||
f"""<a class="stdlink" href="{
|
|
||||||
url_for("notes.formsemestre_status", scodoc_dept=g.scodoc_dept, formsemestre_id=f.id )
|
|
||||||
}">{f.titre}</a>"""
|
|
||||||
for f in formsemestres
|
|
||||||
]
|
|
||||||
sem_descr = f"""
|
|
||||||
<div class="ue_warning">
|
|
||||||
<div>Ce module est utilisé dans les formsemestres suivants:</div>
|
|
||||||
<ul><li>
|
|
||||||
{"</li><li>".join( items )}
|
|
||||||
</li></ul>
|
|
||||||
</div>
|
|
||||||
"""
|
|
||||||
else:
|
|
||||||
sem_descr = ""
|
|
||||||
#
|
#
|
||||||
if tf[0] == 0:
|
if tf[0] == 0:
|
||||||
return "\n".join(H) + tf[1] + sem_descr + html_sco_header.sco_footer()
|
return "\n".join(H) + tf[1] + html_sco_header.sco_footer()
|
||||||
elif tf[0] == -1:
|
elif tf[0] == -1:
|
||||||
return flask.redirect(
|
return flask.redirect(
|
||||||
url_for(
|
url_for(
|
||||||
|
@ -244,18 +244,22 @@ def ue_edit(ue_id=None, create=False, formation_id=None, default_semestre_idx=No
|
|||||||
"""Formulaire modification ou création d'une UE"""
|
"""Formulaire modification ou création d'une UE"""
|
||||||
create = int(create)
|
create = int(create)
|
||||||
if not create:
|
if not create:
|
||||||
U = ue_list(args={"ue_id": ue_id})
|
ue: UniteEns = UniteEns.query.get_or_404(ue_id)
|
||||||
if not U:
|
ue_dict = ue.to_dict()
|
||||||
raise ScoValueError("UE inexistante !")
|
formation_id = ue.formation_id
|
||||||
U = U[0]
|
title = f"Modification de l'UE {ue.acronyme} {ue.titre}"
|
||||||
formation_id = U["formation_id"]
|
initvalues = ue_dict
|
||||||
title = "Modification de l'UE %(titre)s" % U
|
|
||||||
initvalues = U
|
|
||||||
submitlabel = "Modifier les valeurs"
|
submitlabel = "Modifier les valeurs"
|
||||||
|
can_change_semestre_id = ue.modules.count() == 0
|
||||||
else:
|
else:
|
||||||
|
ue = None
|
||||||
title = "Création d'une UE"
|
title = "Création d'une UE"
|
||||||
initvalues = {"semestre_idx": default_semestre_idx}
|
initvalues = {
|
||||||
|
"semestre_idx": default_semestre_idx,
|
||||||
|
"color": ue_guess_color_default(formation_id, default_semestre_idx),
|
||||||
|
}
|
||||||
submitlabel = "Créer cette UE"
|
submitlabel = "Créer cette UE"
|
||||||
|
can_change_semestre_id = True
|
||||||
formation = Formation.query.get(formation_id)
|
formation = Formation.query.get(formation_id)
|
||||||
if not formation:
|
if not formation:
|
||||||
raise ScoValueError(f"Formation inexistante ! (id={formation_id})")
|
raise ScoValueError(f"Formation inexistante ! (id={formation_id})")
|
||||||
@ -282,7 +286,7 @@ def ue_edit(ue_id=None, create=False, formation_id=None, default_semestre_idx=No
|
|||||||
ue_types_names = [sco_codes_parcours.UE_TYPE_NAME[k] for k in ue_types]
|
ue_types_names = [sco_codes_parcours.UE_TYPE_NAME[k] for k in ue_types]
|
||||||
ue_types = [str(x) for x in ue_types]
|
ue_types = [str(x) for x in ue_types]
|
||||||
|
|
||||||
fw = [
|
form_descr = [
|
||||||
("ue_id", {"input_type": "hidden"}),
|
("ue_id", {"input_type": "hidden"}),
|
||||||
("create", {"input_type": "hidden", "default": create}),
|
("create", {"input_type": "hidden", "default": create}),
|
||||||
("formation_id", {"input_type": "hidden", "default": formation_id}),
|
("formation_id", {"input_type": "hidden", "default": formation_id}),
|
||||||
@ -296,18 +300,28 @@ def ue_edit(ue_id=None, create=False, formation_id=None, default_semestre_idx=No
|
|||||||
"type": "int",
|
"type": "int",
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
(
|
]
|
||||||
"semestre_idx",
|
if can_change_semestre_id:
|
||||||
{
|
form_descr += [
|
||||||
"input_type": "menu",
|
(
|
||||||
"type": "int",
|
"semestre_idx",
|
||||||
"allow_null": False,
|
{
|
||||||
"title": parcours.SESSION_NAME.capitalize(),
|
"input_type": "menu",
|
||||||
"explanation": "%s de l'UE dans la formation" % parcours.SESSION_NAME,
|
"type": "int",
|
||||||
"labels": ["non spécifié"] + [str(x) for x in semestres_indices],
|
"allow_null": False,
|
||||||
"allowed_values": [""] + semestres_indices,
|
"title": parcours.SESSION_NAME.capitalize(),
|
||||||
},
|
"explanation": "%s de l'UE dans la formation"
|
||||||
),
|
% parcours.SESSION_NAME,
|
||||||
|
"labels": ["non spécifié"] + [str(x) for x in semestres_indices],
|
||||||
|
"allowed_values": [""] + semestres_indices,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
|
else:
|
||||||
|
form_descr += [
|
||||||
|
("semestre_idx", {"default": ue.semestre_idx, "input_type": "hidden"}),
|
||||||
|
]
|
||||||
|
form_descr += [
|
||||||
(
|
(
|
||||||
"type",
|
"type",
|
||||||
{
|
{
|
||||||
@ -377,7 +391,7 @@ def ue_edit(ue_id=None, create=False, formation_id=None, default_semestre_idx=No
|
|||||||
),
|
),
|
||||||
]
|
]
|
||||||
if create and not parcours.UE_IS_MODULE and not is_apc:
|
if create and not parcours.UE_IS_MODULE and not is_apc:
|
||||||
fw.append(
|
form_descr.append(
|
||||||
(
|
(
|
||||||
"create_matiere",
|
"create_matiere",
|
||||||
{
|
{
|
||||||
@ -391,14 +405,33 @@ def ue_edit(ue_id=None, create=False, formation_id=None, default_semestre_idx=No
|
|||||||
tf = TrivialFormulator(
|
tf = TrivialFormulator(
|
||||||
request.base_url,
|
request.base_url,
|
||||||
scu.get_request_args(),
|
scu.get_request_args(),
|
||||||
fw,
|
form_descr,
|
||||||
initvalues=initvalues,
|
initvalues=initvalues,
|
||||||
submitlabel=submitlabel,
|
submitlabel=submitlabel,
|
||||||
)
|
)
|
||||||
if tf[0] == 0:
|
if tf[0] == 0:
|
||||||
ue_div = """<div id="ue_list_code"></div>"""
|
if ue and ue.modules.count():
|
||||||
|
modules_div = f"""<div id="ue_list_modules">
|
||||||
|
<div><b>{ue.modules.count()} modules sont rattachés
|
||||||
|
à cette UE</b> du semestre S{ue.semestre_idx},
|
||||||
|
elle ne peut donc pas être changée de semestre.</div>
|
||||||
|
<ul>"""
|
||||||
|
for m in ue.modules:
|
||||||
|
modules_div += f"""<li><a class="stdlink" href="{url_for(
|
||||||
|
"notes.module_edit",scodoc_dept=g.scodoc_dept, module_id=m.id)}">{m.code} {m.titre}</a></li>"""
|
||||||
|
modules_div += """</ul></div>"""
|
||||||
|
else:
|
||||||
|
modules_div = ""
|
||||||
bonus_div = """<div id="bonus_description"></div>"""
|
bonus_div = """<div id="bonus_description"></div>"""
|
||||||
return "\n".join(H) + tf[1] + bonus_div + ue_div + html_sco_header.sco_footer()
|
ue_div = """<div id="ue_list_code"></div>"""
|
||||||
|
return (
|
||||||
|
"\n".join(H)
|
||||||
|
+ tf[1]
|
||||||
|
+ modules_div
|
||||||
|
+ bonus_div
|
||||||
|
+ ue_div
|
||||||
|
+ html_sco_header.sco_footer()
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
if create:
|
if create:
|
||||||
if not tf[2]["ue_code"]:
|
if not tf[2]["ue_code"]:
|
||||||
@ -547,14 +580,15 @@ def ue_table(formation_id=None, semestre_idx=1, msg=""): # was ue_list
|
|||||||
formation_id=formation_id, is_external=True
|
formation_id=formation_id, is_external=True
|
||||||
)
|
)
|
||||||
if is_apc:
|
if is_apc:
|
||||||
# pour faciliter la transition des anciens programmes non APC
|
# Pour faciliter la transition des anciens programmes non APC
|
||||||
for ue in ues_obj:
|
for ue in ues_obj:
|
||||||
ue.guess_semestre_idx()
|
ue.guess_semestre_idx()
|
||||||
# vérifie qu'on a bien au moins une matière dans chaque UE
|
# vérifie qu'on a bien au moins une matière dans chaque UE
|
||||||
for ue in ues_obj:
|
|
||||||
if ue.matieres.count() < 1:
|
if ue.matieres.count() < 1:
|
||||||
mat = Matiere(ue_id=ue.id)
|
mat = Matiere(ue_id=ue.id)
|
||||||
db.session.add(mat)
|
db.session.add(mat)
|
||||||
|
# donne des couleurs aux UEs crées avant
|
||||||
|
colorie_anciennes_ues(ues_obj)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
ues = [ue.to_dict() for ue in ues_obj]
|
ues = [ue.to_dict() for ue in ues_obj]
|
||||||
ues_externes = [ue.to_dict() for ue in ues_externes_obj]
|
ues_externes = [ue.to_dict() for ue in ues_externes_obj]
|
||||||
@ -1370,7 +1404,7 @@ def formation_table_recap(formation_id, format="html"):
|
|||||||
return tab.make_page(format=format)
|
return tab.make_page(format=format)
|
||||||
|
|
||||||
|
|
||||||
def ue_list_semestre_ids(ue):
|
def ue_list_semestre_ids(ue: dict):
|
||||||
"""Liste triée des numeros de semestres des modules dans cette UE
|
"""Liste triée des numeros de semestres des modules dans cette UE
|
||||||
Il est recommandable que tous les modules d'une UE aient le même indice de semestre.
|
Il est recommandable que tous les modules d'une UE aient le même indice de semestre.
|
||||||
Mais cela n'a pas toujours été le cas dans les programmes pédagogiques officiels,
|
Mais cela n'a pas toujours été le cas dans les programmes pédagogiques officiels,
|
||||||
@ -1378,3 +1412,45 @@ def ue_list_semestre_ids(ue):
|
|||||||
"""
|
"""
|
||||||
modules = sco_edit_module.module_list(args={"ue_id": ue["ue_id"]})
|
modules = sco_edit_module.module_list(args={"ue_id": ue["ue_id"]})
|
||||||
return sorted(list(set([mod["semestre_id"] for mod in modules])))
|
return sorted(list(set([mod["semestre_id"] for mod in modules])))
|
||||||
|
|
||||||
|
|
||||||
|
UE_PALETTE = [
|
||||||
|
"#EFA00B",
|
||||||
|
"#99C24D",
|
||||||
|
"#EC9192",
|
||||||
|
"#0075C4",
|
||||||
|
"#D65108",
|
||||||
|
"#DEC0F1",
|
||||||
|
"#B02E0C",
|
||||||
|
"#151E3F",
|
||||||
|
"#FB3640",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def colorie_anciennes_ues(ues: list[UniteEns]) -> None:
|
||||||
|
"""Avant ScoDoc 9.2, les ue n'avaient pas de couleurs
|
||||||
|
Met des défauts raisonnables
|
||||||
|
"""
|
||||||
|
nb_colors = len(UE_PALETTE)
|
||||||
|
index = 0
|
||||||
|
last_sem_idx = 0
|
||||||
|
for ue in ues:
|
||||||
|
if ue.semestre_idx != last_sem_idx:
|
||||||
|
last_sem_idx = ue.semestre_idx
|
||||||
|
index = 0
|
||||||
|
if ue.color is None:
|
||||||
|
ue.color = UE_PALETTE[index % nb_colors]
|
||||||
|
index += 1
|
||||||
|
db.session.add(ue)
|
||||||
|
|
||||||
|
|
||||||
|
def ue_guess_color_default(formation_id: int, default_semestre_idx: int) -> str:
|
||||||
|
"""Un code couleur pour une nouvelle UE dans ce semestre"""
|
||||||
|
nb_colors = len(UE_PALETTE)
|
||||||
|
# UE existantes dans ce semestre:
|
||||||
|
nb_ues = UniteEns.query.filter(
|
||||||
|
UniteEns.formation_id == formation_id,
|
||||||
|
UniteEns.semestre_idx == default_semestre_idx,
|
||||||
|
).count()
|
||||||
|
index = nb_ues
|
||||||
|
return UE_PALETTE[index % nb_colors]
|
||||||
|
@ -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(
|
||||||
|
@ -31,15 +31,21 @@ import time
|
|||||||
|
|
||||||
import flask
|
import flask
|
||||||
from flask import url_for, g, request
|
from flask import url_for, g, request
|
||||||
|
from app.api.sco_api import formsemestre
|
||||||
|
|
||||||
import app.scodoc.notesdb as ndb
|
import app.scodoc.notesdb as ndb
|
||||||
import app.scodoc.sco_utils as scu
|
import app.scodoc.sco_utils as scu
|
||||||
from app import log
|
from app import log
|
||||||
from app.scodoc.scolog import logdb
|
|
||||||
from app.scodoc.TrivialFormulator import TrivialFormulator, tf_error_message
|
|
||||||
from app.scodoc.sco_exceptions import ScoValueError
|
|
||||||
|
|
||||||
|
from app.comp import res_sem
|
||||||
|
from app.comp.res_common import NotesTableCompat
|
||||||
|
from app.models import FormSemestre
|
||||||
|
|
||||||
|
from app.scodoc.sco_exceptions import ScoValueError
|
||||||
|
from app.scodoc.scolog import logdb
|
||||||
from app.scodoc.sco_codes_parcours import *
|
from app.scodoc.sco_codes_parcours import *
|
||||||
|
from app.scodoc.TrivialFormulator import TrivialFormulator, tf_error_message
|
||||||
|
|
||||||
from app.scodoc import html_sco_header
|
from app.scodoc import html_sco_header
|
||||||
from app.scodoc import sco_abs
|
from app.scodoc import sco_abs
|
||||||
from app.scodoc import sco_codes_parcours
|
from app.scodoc import sco_codes_parcours
|
||||||
@ -542,9 +548,9 @@ def formsemestre_recap_parcours_table(
|
|||||||
else:
|
else:
|
||||||
ass = ""
|
ass = ""
|
||||||
|
|
||||||
nt = sco_cache.NotesTableCache.get(
|
formsemestre = FormSemestre.query.get(sem["formsemestre_id"])
|
||||||
sem["formsemestre_id"]
|
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
||||||
) # > get_ues_stat_dict, get_etud_moy_gen, get_etud_ue_status
|
# nt = sco_cache.NotesTableCache.get(sem["formsemestre_id"])
|
||||||
if is_cur:
|
if is_cur:
|
||||||
type_sem = "*" # now unused
|
type_sem = "*" # now unused
|
||||||
class_sem = "sem_courant"
|
class_sem = "sem_courant"
|
||||||
@ -686,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"])
|
||||||
)
|
)
|
||||||
@ -697,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
|
||||||
@ -1056,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,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -53,6 +53,7 @@ from reportlab.lib import styles
|
|||||||
|
|
||||||
import flask
|
import flask
|
||||||
from flask import url_for, g, request
|
from flask import url_for, g, request
|
||||||
|
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
|
||||||
@ -140,23 +141,21 @@ 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 }
|
||||||
"""
|
"""
|
||||||
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 = UniteEns.query.get(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):
|
||||||
@ -166,10 +165,7 @@ def _comp_ects_capitalises_by_ue_code(nt, etudid):
|
|||||||
for ue in ues:
|
for ue in ues:
|
||||||
ue_status = nt.get_etud_ue_status(etudid, ue["ue_id"])
|
ue_status = nt.get_etud_ue_status(etudid, ue["ue_id"])
|
||||||
if ue_status["is_capitalized"]:
|
if ue_status["is_capitalized"]:
|
||||||
try:
|
ects_val = float(ue_status["ue"]["ects"] or 0.0)
|
||||||
ects_val = float(ue_status["ue"]["ects"])
|
|
||||||
except (ValueError, TypeError):
|
|
||||||
ects_val = 0.0
|
|
||||||
ects_by_ue_code[ue["ue_code"]] = ects_val
|
ects_by_ue_code[ue["ue_code"]] = ects_val
|
||||||
|
|
||||||
return ects_by_ue_code
|
return ects_by_ue_code
|
||||||
@ -249,11 +245,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 = [
|
||||||
@ -649,12 +645,12 @@ def make_formsemestre_recapcomplet(
|
|||||||
): # Rang: force tri numerique pour sortable
|
): # Rang: force tri numerique pour sortable
|
||||||
cls = cls + " sortnumeric"
|
cls = cls + " sortnumeric"
|
||||||
if F[0][i] in cod2mod: # lien vers etat module
|
if F[0][i] in cod2mod: # lien vers etat module
|
||||||
mod = cod2mod[F[0][i]]
|
modimpl = cod2mod[F[0][i]]
|
||||||
cells += '<td class="%s"><a href="moduleimpl_status?moduleimpl_id=%s" title="%s (%s)">%s</a></td>' % (
|
cells += '<td class="%s"><a href="moduleimpl_status?moduleimpl_id=%s" title="%s (%s)">%s</a></td>' % (
|
||||||
cls,
|
cls,
|
||||||
mod["moduleimpl_id"],
|
modimpl.id,
|
||||||
mod["module"]["titre"],
|
modimpl.module.titre,
|
||||||
sco_users.user_info(mod["responsable_id"])["nomcomplet"],
|
sco_users.user_info(modimpl.responsable_id)["nomcomplet"],
|
||||||
F[0][i],
|
F[0][i],
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
@ -1771,40 +1771,54 @@ ul.notes_module_list {
|
|||||||
}
|
}
|
||||||
|
|
||||||
div#ue_list_code {
|
div#ue_list_code {
|
||||||
background-color: rgb(220,220,220);
|
background-color: rgb(155, 218, 155);
|
||||||
font-size: small;
|
padding: 10px;
|
||||||
padding-left: 4px;
|
border: 1px solid blue;
|
||||||
padding-bottom: 1px;
|
border-radius: 10px;
|
||||||
|
padding: 10px;
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
margin: 3ex;
|
margin-right: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
ul.notes_module_list {
|
ul.notes_module_list {
|
||||||
list-style-type: none;
|
list-style-type: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
div#ue_list_modules {
|
||||||
|
background-color: rgb(191, 242, 255);
|
||||||
|
border: 1px solid blue;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 10px;
|
||||||
|
margin-top: 10px;
|
||||||
|
margin-right: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
div#ue_list_etud_validations {
|
div#ue_list_etud_validations {
|
||||||
background-color: rgb(220,250,220);
|
background-color: rgb(220,250,220);
|
||||||
padding-left: 4px;
|
padding-left: 4px;
|
||||||
padding-bottom: 1px;
|
padding-bottom: 1px;
|
||||||
margin: 3ex;
|
margin: 3ex;
|
||||||
}
|
}
|
||||||
div#ue_list_etud_validations span {
|
div#ue_list_etud_validations span {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
span.ue_share {
|
span.ue_share {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.ue_warning {
|
div.ue_warning {
|
||||||
border: 1px solid red;
|
border: 1px solid red;
|
||||||
background-color: rgb(250,220,220);
|
border-radius: 10px;
|
||||||
margin: 3ex;
|
background-color: rgb(250,220,220);
|
||||||
padding-left: 1ex;
|
margin-top: 10px;
|
||||||
padding-right: 1ex;
|
margin-right: 15px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
div.ue_warning:first-child {
|
||||||
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.ue_warning span:before {
|
div.ue_warning span:before {
|
||||||
content: url(/ScoDoc/static/icons/warning_img.png);
|
content: url(/ScoDoc/static/icons/warning_img.png);
|
||||||
vertical-align: -80%;
|
vertical-align: -80%;
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
{# -*- mode: jinja-html -*- #}
|
||||||
<div class="help">
|
<div class="help">
|
||||||
<p class="help">
|
<p class="help">
|
||||||
Les modules sont décrits dans le programme pédagogique. Un module est pour ce
|
Les modules sont décrits dans le programme pédagogique. Un module est pour ce
|
||||||
@ -24,24 +25,26 @@
|
|||||||
<a href="https://scodoc.org/BUT" target="_blank">la documentation</a>.
|
<a href="https://scodoc.org/BUT" target="_blank">la documentation</a>.
|
||||||
</p>
|
</p>
|
||||||
{%endif%}
|
{%endif%}
|
||||||
|
</div>
|
||||||
|
|
||||||
{% if formsemestres %}
|
{% if formsemestres %}
|
||||||
<p class="help">
|
<div class="ue_warning"> <span><b>Module déjà utilisé dans des semestres, soyez prudents !</b></span>
|
||||||
Ce module est utilisé dans des semestres déjà mis en place, il faut prêter attention
|
<p class="help">
|
||||||
aux conséquences des changements effectués ici: par exemple les coefficients vont modifier
|
Ce module est utilisé dans des semestres déjà mis en place, il faut prêter attention
|
||||||
les notes moyennes calculées. Les modules déjà utilisés ne peuvent pas être changés de semestre, ni détruits.
|
aux conséquences des changements effectués ici: par exemple les coefficients vont modifier
|
||||||
Si vous souhaitez faire cela, allez d'abord modifier les semestres concernés pour déselectionner le module.
|
les notes moyennes calculées. Les modules déjà utilisés ne peuvent pas être changés de semestre, ni détruits.
|
||||||
</p>
|
Si vous souhaitez faire cela, allez d'abord modifier les semestres concernés pour déselectionner le module.
|
||||||
<h4>Semestres utilisant ce module:</h4>
|
</p>
|
||||||
<ul>
|
<h4>Semestres utilisant ce module:</h4>
|
||||||
|
<ul>
|
||||||
{%for formsemestre in formsemestres %}
|
{%for formsemestre in formsemestres %}
|
||||||
<li><a class="stdlink" href="{{
|
<li><a class="stdlink" href="{{
|
||||||
url_for('notes.formsemestre_status',
|
url_for('notes.formsemestre_status',
|
||||||
scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre.id)
|
scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre.id)
|
||||||
}}">{{formsemestre.titre_mois()}}</a>
|
}}">{{formsemestre.titre_mois()}}</a>
|
||||||
</li>
|
</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
{%endif%}
|
</div>
|
||||||
|
{%endif%}
|
||||||
|
|
||||||
</div>
|
|
@ -496,6 +496,7 @@ def SignaleAbsenceGrSemestre(
|
|||||||
require_module = sco_preferences.get_preference(
|
require_module = sco_preferences.get_preference(
|
||||||
"abs_require_module", formsemestre_id
|
"abs_require_module", formsemestre_id
|
||||||
)
|
)
|
||||||
|
sem = sco_formsemestre.do_formsemestre_list({"formsemestre_id": formsemestre_id})[0]
|
||||||
etuds = [
|
etuds = [
|
||||||
sco_etud.get_etud_info(etudid=m["etudid"], filled=True)[0]
|
sco_etud.get_etud_info(etudid=m["etudid"], filled=True)[0]
|
||||||
for m in groups_infos.members
|
for m in groups_infos.members
|
||||||
@ -526,9 +527,7 @@ def SignaleAbsenceGrSemestre(
|
|||||||
|
|
||||||
if etuds:
|
if etuds:
|
||||||
nt = sco_cache.NotesTableCache.get(formsemestre_id)
|
nt = sco_cache.NotesTableCache.get(formsemestre_id)
|
||||||
sem = sco_formsemestre.do_formsemestre_list(
|
|
||||||
{"formsemestre_id": formsemestre_id}
|
|
||||||
)[0]
|
|
||||||
work_saturday = sco_abs.is_work_saturday()
|
work_saturday = sco_abs.is_work_saturday()
|
||||||
jourdebut = sco_abs.ddmmyyyy(datedebut, work_saturday=work_saturday)
|
jourdebut = sco_abs.ddmmyyyy(datedebut, work_saturday=work_saturday)
|
||||||
jourfin = sco_abs.ddmmyyyy(datefin, work_saturday=work_saturday)
|
jourfin = sco_abs.ddmmyyyy(datefin, work_saturday=work_saturday)
|
||||||
|
@ -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