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),
|
||||
}
|
||||
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"] = {
|
||||
"value": fmt_note(self.etud_moy_ue[ue.id][etud.id]),
|
||||
"min": fmt_note(self.etud_moy_ue[ue.id].min()),
|
||||
"max": fmt_note(self.etud_moy_ue[ue.id].max()),
|
||||
"moy": fmt_note(self.etud_moy_ue[ue.id].mean()),
|
||||
"rang": rang,
|
||||
"total": effectif, # nb etud avec note dans cette UE
|
||||
}
|
||||
else:
|
||||
# ceci suppose que l'on a une seule UE bonus,
|
||||
@ -227,8 +236,8 @@ class BulletinBUT(ResultatsSemestreBUT):
|
||||
"date_debut": formsemestre.date_debut.isoformat(),
|
||||
"date_fin": formsemestre.date_fin.isoformat(),
|
||||
"annee_universitaire": self.formsemestre.annee_scolaire_str(),
|
||||
"inscription": "TODO-MM-JJ", # XXX TODO
|
||||
"numero": formsemestre.semestre_id,
|
||||
"inscription": "", # inutilisé mais nécessaire pour le js de Seb.
|
||||
"groupes": [], # XXX TODO
|
||||
"absences": {
|
||||
"injustifie": nbabsjust,
|
||||
|
@ -21,7 +21,7 @@ class StatsMoyenne:
|
||||
Les valeurs NAN ou non numériques sont toujours enlevées.
|
||||
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
|
||||
else:
|
||||
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."
|
||||
self.evaluations_etat = {}
|
||||
"{ evaluation_id: EvaluationEtat }"
|
||||
self.en_attente = False
|
||||
"Vrai si au moins une évaluation a une note en attente"
|
||||
#
|
||||
self.evals_notes = None
|
||||
"""DataFrame, colonnes: EVALS, Lignes: etudid (inscrits au SEMESTRE)
|
||||
@ -133,7 +135,7 @@ class ModuleImplResults:
|
||||
evals_notes = pd.DataFrame(index=self.etudids, dtype=float)
|
||||
self.evaluations_completes = []
|
||||
self.evaluations_completes_dict = {}
|
||||
|
||||
self.en_attente = False
|
||||
for evaluation in moduleimpl.evaluations:
|
||||
eval_df = self._load_evaluation_notes(evaluation)
|
||||
# is_complete ssi tous les inscrits (non dem) au semestre ont une note
|
||||
@ -160,6 +162,8 @@ class ModuleImplResults:
|
||||
self.evaluations_etat[evaluation.id] = EvaluationEtat(
|
||||
evaluation_id=evaluation.id, nb_attente=nb_att, is_complete=is_complete
|
||||
)
|
||||
if nb_att > 0:
|
||||
self.en_attente = True
|
||||
|
||||
# Force columns names to integers (evaluation ids)
|
||||
evals_notes.columns = pd.Int64Index(
|
||||
@ -209,6 +213,13 @@ class ModuleImplResults:
|
||||
* self.evaluations_completes
|
||||
).reshape(-1, 1)
|
||||
|
||||
# was _list_notes_evals_titles
|
||||
def get_evaluations_completes(self, moduleimpl: ModuleImpl) -> list:
|
||||
"Liste des évaluations complètes"
|
||||
return [
|
||||
e for e in moduleimpl.evaluations if self.evaluations_completes_dict[e.id]
|
||||
]
|
||||
|
||||
def get_eval_notes_sur_20(self, moduleimpl: ModuleImpl) -> np.array:
|
||||
"""Les notes des évaluations,
|
||||
remplace les ATT, EXC, ABS, NaN par zéro et mets les notes sur 20.
|
||||
|
@ -12,6 +12,7 @@ from app.comp import moy_ue, moy_sem, inscr_mod
|
||||
from app.comp.res_common import NotesTableCompat
|
||||
from app.comp.bonus_spo import BonusSport
|
||||
from app.models import ScoDocSiteConfig
|
||||
from app.models.ues import UniteEns
|
||||
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)
|
||||
|
||||
# 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)
|
||||
self.etud_moy_gen = moy_sem.compute_sem_moys_apc(
|
||||
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:
|
||||
"""La moyenne de l'étudiant dans le moduleimpl
|
||||
@ -109,3 +114,12 @@ class ResultatsSemestreBUT(NotesTableCompat):
|
||||
etud_idx = self.etud_index[etudid]
|
||||
# moyenne sur les UE:
|
||||
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)
|
||||
"""
|
||||
|
||||
import numpy as np
|
||||
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.bonus_spo import BonusSport
|
||||
from app.models import ScoDocSiteConfig
|
||||
from app.models.etudiants import Identite
|
||||
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_exceptions import ScoValueError
|
||||
from app.scodoc.sco_utils import ModuleType
|
||||
|
||||
|
||||
@ -104,8 +113,11 @@ class ResultatsSemestreClassic(NotesTableCompat):
|
||||
self.bonus = (
|
||||
bonus_mg # compat nt, utilisé pour l'afficher sur les bulletins
|
||||
)
|
||||
# --- UE capitalisées
|
||||
self.apply_capitalisation()
|
||||
|
||||
# --- 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:
|
||||
"""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]:
|
||||
"""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)
|
||||
# passe de (mod x etud) à (etud x mod)
|
||||
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
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
|
||||
from flask import g
|
||||
|
||||
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.models import FormSemestre, Identite, ModuleImpl
|
||||
from app.models import FormSemestreUECoef
|
||||
from app.models.ues import UniteEns
|
||||
from app.scodoc import sco_utils as scu
|
||||
from app.scodoc import sco_evaluations
|
||||
from app.scodoc.sco_cache import ResultatsSemestreCache
|
||||
from app.scodoc.sco_codes_parcours import UE_SPORT, ATT, DEF
|
||||
from app.scodoc.sco_exceptions import ScoValueError
|
||||
|
||||
# Il faut bien distinguer
|
||||
# - 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)
|
||||
# qui sont notamment les attributs décorés par `@cached_property``
|
||||
#
|
||||
class ResultatsSemestre:
|
||||
class ResultatsSemestre(ResultatsCache):
|
||||
_cached_attrs = (
|
||||
"etud_moy_gen_ranks",
|
||||
"etud_moy_gen",
|
||||
@ -36,7 +45,7 @@ class ResultatsSemestre:
|
||||
)
|
||||
|
||||
def __init__(self, formsemestre: FormSemestre):
|
||||
self.formsemestre: FormSemestre = formsemestre
|
||||
super().__init__(formsemestre, ResultatsSemestreCache)
|
||||
# BUT ou standard ? (apc == "approche par compétences")
|
||||
self.is_apc = formsemestre.formation.is_apc()
|
||||
# Attributs "virtuels", définis dans les sous-classes
|
||||
@ -46,26 +55,10 @@ class ResultatsSemestre:
|
||||
self.etud_moy_gen = {}
|
||||
self.etud_moy_gen_ranks = {}
|
||||
self.modimpls_results: ModuleImplResults = None
|
||||
"Résultats de chaque modimpl: dict { modimpl.id : ModuleImplResults(Classique ou BUT) }"
|
||||
self.etud_coef_ue_df = None
|
||||
"""coefs d'UE effectifs pour chaque etudiant (pour form. classiques)"""
|
||||
|
||||
# TODO ?
|
||||
|
||||
def load_cached(self) -> bool:
|
||||
"Load cached dataframes, returns False si pas en cache"
|
||||
data = ResultatsSemestreCache.get(self.formsemestre.id)
|
||||
if not data:
|
||||
return False
|
||||
for attr in self._cached_attrs:
|
||||
setattr(self, attr, data[attr])
|
||||
return True
|
||||
|
||||
def store(self):
|
||||
"Cache our data"
|
||||
ResultatsSemestreCache.set(
|
||||
self.formsemestre.id,
|
||||
{attr: getattr(self, attr) for attr in self._cached_attrs},
|
||||
)
|
||||
"""coefs d'UE effectifs pour chaque étudiant (pour form. classiques)"""
|
||||
self.validations = None
|
||||
|
||||
def compute(self):
|
||||
"Charge les notes et inscriptions et calcule toutes les moyennes"
|
||||
@ -101,7 +94,8 @@ class ResultatsSemestre:
|
||||
@cached_property
|
||||
def ues(self) -> list[UniteEns]:
|
||||
"""Liste des UEs du semestre (avec les UE bonus sport)
|
||||
(indices des DataFrames)
|
||||
(indices des DataFrames).
|
||||
Note: un étudiant n'est pas nécessairement inscrit dans toutes ces UEs.
|
||||
"""
|
||||
return self.formsemestre.query_ues(with_sport=True).all()
|
||||
|
||||
@ -123,15 +117,34 @@ class ResultatsSemestre:
|
||||
if m.module.module_type == scu.ModuleType.SAE
|
||||
]
|
||||
|
||||
@cached_property
|
||||
def ue_validables(self) -> list:
|
||||
"""Liste des UE du semestre qui doivent être validées
|
||||
(toutes sauf le sport)
|
||||
"""
|
||||
return self.formsemestre.query_ues().filter(UniteEns.type != UE_SPORT).all()
|
||||
def get_etud_ue_validables(self, etudid: int) -> list[UniteEns]:
|
||||
"""Liste des UEs du semestre qui doivent être validées
|
||||
|
||||
def modimpls_in_ue(self, ue_id, etudid):
|
||||
"""Liste des modimpl de cet ue auxquels l'étudiant est inscrit"""
|
||||
Rappel: l'étudiant est inscrit à des modimpls et non à des UEs.
|
||||
|
||||
- En BUT: on considère que l'étudiant va (ou non) valider toutes les UEs des modules
|
||||
du parcours. XXX notion à implémenter, pour l'instant toutes les UE du semestre.
|
||||
|
||||
- En classique: toutes les UEs des modimpls auxquels l'étufdiant est inscrit sont
|
||||
susceptibles d'être validées.
|
||||
|
||||
Les UE "bonus" (sport) ne sont jamais "validables".
|
||||
"""
|
||||
if self.is_apc:
|
||||
# TODO: introduire la notion de parcours (#sco93)
|
||||
return self.formsemestre.query_ues().filter(UniteEns.type != UE_SPORT).all()
|
||||
else:
|
||||
# restreint aux UE auxquelles l'étudiant est inscrit (dans l'un des modimpls)
|
||||
ues = {
|
||||
modimpl.module.ue
|
||||
for modimpl in self.formsemestre.modimpls_sorted
|
||||
if self.modimpl_inscr_df[modimpl.id][etudid]
|
||||
}
|
||||
ues = sorted(list(ues), key=lambda x: x.numero or 0)
|
||||
return ues
|
||||
|
||||
def modimpls_in_ue(self, ue_id, etudid) -> list[ModuleImpl]:
|
||||
"""Liste des modimpl de cette UE auxquels l'étudiant est inscrit"""
|
||||
# sert pour l'affichage ou non de l'UE sur le bulletin
|
||||
return [
|
||||
modimpl
|
||||
@ -148,6 +161,115 @@ class ResultatsSemestre:
|
||||
"""
|
||||
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
|
||||
class NotesTableCompat(ResultatsSemestre):
|
||||
@ -163,6 +285,8 @@ class NotesTableCompat(ResultatsSemestre):
|
||||
"bonus",
|
||||
"bonus_ues",
|
||||
"malus",
|
||||
"etud_moy_gen_ranks",
|
||||
"ue_rangs",
|
||||
)
|
||||
|
||||
def __init__(self, formsemestre: FormSemestre):
|
||||
@ -243,6 +367,35 @@ class NotesTableCompat(ResultatsSemestre):
|
||||
modimpls_dict.append(d)
|
||||
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:
|
||||
"""Decision du jury prise pour cet etudiant, ou None s'il n'y en pas eu.
|
||||
{ 'code' : None|ATT|..., 'assidu' : 0|1, 'event_date' : , compense_formsemestre_id }
|
||||
@ -256,12 +409,11 @@ class NotesTableCompat(ResultatsSemestre):
|
||||
"compense_formsemestre_id": None,
|
||||
}
|
||||
else:
|
||||
return {
|
||||
"code": ATT, # XXX TODO
|
||||
"assidu": True, # XXX TODO
|
||||
"event_date": "",
|
||||
"compense_formsemestre_id": None,
|
||||
}
|
||||
if not self.validations:
|
||||
self.validations = res_sem.load_formsemestre_validations(
|
||||
self.formsemestre
|
||||
)
|
||||
return self.validations.decisions_jury.get(etudid, None)
|
||||
|
||||
def get_etud_etat(self, etudid: int) -> str:
|
||||
"Etat de l'etudiant: 'I', 'D', DEF ou '' (si pas connu dans ce semestre)"
|
||||
@ -290,13 +442,29 @@ class NotesTableCompat(ResultatsSemestre):
|
||||
"""
|
||||
return self.etud_moy_gen[etudid]
|
||||
|
||||
def get_etud_ue_status(self, etudid: int, ue_id: int):
|
||||
coef_ue = self.etud_coef_ue_df[ue_id][etudid]
|
||||
def get_etud_ects_pot(self, etudid: int) -> dict:
|
||||
"""
|
||||
Un dict avec les champs
|
||||
ects_pot : (float) nb de crédits ECTS qui seraient validés (sous réserve de validation par le jury),
|
||||
ects_pot_fond: (float) nb d'ECTS issus d'UE fondamentales (non électives)
|
||||
|
||||
Ce sont les ECTS des UE au dessus de la barre (10/20 en principe), avant le jury (donc non
|
||||
encore enregistrées).
|
||||
"""
|
||||
# was nt.get_etud_moy_infos
|
||||
# XXX pour compat nt, à remplacer ultérieurement
|
||||
ues = self.get_etud_ue_validables(etudid)
|
||||
ects_pot = 0.0
|
||||
for ue in ues:
|
||||
if (
|
||||
ue.id in self.etud_moy_ue
|
||||
and ue.ects is not None
|
||||
and self.etud_moy_ue[ue.id][etudid] > self.parcours.NOTES_BARRE_VALID_UE
|
||||
):
|
||||
ects_pot += ue.ects
|
||||
return {
|
||||
"cur_moy_ue": self.etud_moy_ue[ue_id][etudid],
|
||||
"moy": self.etud_moy_ue[ue_id][etudid],
|
||||
"is_capitalized": False, # XXX TODO
|
||||
"coef_ue": coef_ue, # XXX TODO
|
||||
"ects_pot": ects_pot,
|
||||
"ects_fond": 0.0, # not implemented (anciennemment pour école ingé)
|
||||
}
|
||||
|
||||
def get_etud_rang(self, etudid: int):
|
||||
@ -332,8 +500,32 @@ class NotesTableCompat(ResultatsSemestre):
|
||||
evals_results.append(d)
|
||||
return evals_results
|
||||
|
||||
def get_evaluations_etats(self):
|
||||
"""[ {...evaluation et son etat...} ]"""
|
||||
# TODO: à moderniser
|
||||
if not hasattr(self, "_evaluations_etats"):
|
||||
self._evaluations_etats = sco_evaluations.do_evaluation_list_in_sem(
|
||||
self.formsemestre.id
|
||||
)
|
||||
|
||||
return self._evaluations_etats
|
||||
|
||||
def get_mod_evaluation_etat_list(self, moduleimpl_id) -> list[dict]:
|
||||
"""Liste des états des évaluations de ce module"""
|
||||
# XXX TODO à moderniser: lent, recharge des donénes que l'on a déjà...
|
||||
return [
|
||||
e
|
||||
for e in self.get_evaluations_etats()
|
||||
if e["moduleimpl_id"] == moduleimpl_id
|
||||
]
|
||||
|
||||
def get_moduleimpls_attente(self):
|
||||
return [] # XXX TODO
|
||||
"""Liste des modimpls du semestre ayant des notes en attente"""
|
||||
return [
|
||||
modimpl
|
||||
for modimpl in self.formsemestre.modimpls_sorted
|
||||
if self.modimpls_results[modimpl.id].en_attente
|
||||
]
|
||||
|
||||
def get_mod_stats(self, moduleimpl_id: int) -> dict:
|
||||
"""Stats sur les notes obtenues dans un modimpl
|
||||
|
@ -8,31 +8,49 @@
|
||||
"""
|
||||
from flask import g
|
||||
|
||||
from app.comp.jury import ValidationsSemestre
|
||||
from app.comp.res_common import ResultatsSemestre
|
||||
from app.comp.res_classic import ResultatsSemestreClassic
|
||||
from app.comp.res_but import ResultatsSemestreBUT
|
||||
from app.models.formsemestre import FormSemestre
|
||||
|
||||
|
||||
def load_formsemestre_result(formsemestre: FormSemestre) -> ResultatsSemestre:
|
||||
def load_formsemestre_results(formsemestre: FormSemestre) -> ResultatsSemestre:
|
||||
"""Returns ResultatsSemestre for this formsemestre.
|
||||
Suivant le type de formation, retour une instance de
|
||||
ResultatsSemestreClassic ou de ResultatsSemestreBUT.
|
||||
|
||||
Search in local cache (g.formsemestre_result_cache)
|
||||
then global app cache (eg REDIS)
|
||||
If not in cache, build it and cache it.
|
||||
"""
|
||||
# --- Try local cache (within the same request context)
|
||||
if not hasattr(g, "formsemestre_result_cache"):
|
||||
g.formsemestre_result_cache = {} # pylint: disable=C0237
|
||||
if not hasattr(g, "formsemestre_results_cache"):
|
||||
g.formsemestre_results_cache = {} # pylint: disable=C0237
|
||||
else:
|
||||
if formsemestre.id in g.formsemestre_result_cache:
|
||||
return g.formsemestre_result_cache[formsemestre.id]
|
||||
if formsemestre.id in g.formsemestre_results_cache:
|
||||
return g.formsemestre_results_cache[formsemestre.id]
|
||||
|
||||
klass = (
|
||||
ResultatsSemestreBUT
|
||||
if formsemestre.formation.is_apc()
|
||||
else ResultatsSemestreClassic
|
||||
)
|
||||
return klass(formsemestre)
|
||||
g.formsemestre_results_cache[formsemestre.id] = klass(formsemestre)
|
||||
return g.formsemestre_results_cache[formsemestre.id]
|
||||
|
||||
|
||||
def load_formsemestre_validations(formsemestre: FormSemestre) -> ValidationsSemestre:
|
||||
"""Charge les résultats de jury de ce semestre.
|
||||
Search in local cache (g.formsemestre_result_cache)
|
||||
If not in cache, build it and cache it.
|
||||
"""
|
||||
if not hasattr(g, "formsemestre_validation_cache"):
|
||||
g.formsemestre_validations_cache = {} # pylint: disable=C0237
|
||||
else:
|
||||
if formsemestre.id in g.formsemestre_validations_cache:
|
||||
return g.formsemestre_validations_cache[formsemestre.id]
|
||||
|
||||
g.formsemestre_validations_cache[formsemestre.id] = ValidationsSemestre(
|
||||
formsemestre
|
||||
)
|
||||
return g.formsemestre_validations_cache[formsemestre.id]
|
||||
|
@ -49,13 +49,15 @@ from app.models.evaluations import (
|
||||
)
|
||||
from app.models.groups import Partition, GroupDescr, group_membership
|
||||
from app.models.notes import (
|
||||
ScolarEvent,
|
||||
ScolarFormSemestreValidation,
|
||||
ScolarAutorisationInscription,
|
||||
BulAppreciations,
|
||||
NotesNotes,
|
||||
NotesNotesLog,
|
||||
)
|
||||
from app.models.validations import (
|
||||
ScolarEvent,
|
||||
ScolarFormSemestreValidation,
|
||||
ScolarAutorisationInscription,
|
||||
)
|
||||
from app.models.preferences import ScoPreference
|
||||
|
||||
from app.models.but_refcomp import (
|
||||
|
@ -158,7 +158,7 @@ class FormSemestre(db.Model):
|
||||
|
||||
@cached_property
|
||||
def modimpls_sorted(self) -> list[ModuleImpl]:
|
||||
"""Liste des modimpls du semestre
|
||||
"""Liste des modimpls du semestre (y compris bonus)
|
||||
- triée par type/numéro/code en APC
|
||||
- triée par numéros d'UE/matières/modules pour les formations standard.
|
||||
"""
|
||||
@ -427,10 +427,12 @@ class FormSemestreUECoef(db.Model):
|
||||
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,
|
||||
)
|
||||
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
|
||||
|
||||
|
||||
class ScolarEvent(db.Model):
|
||||
"""Evenement dans le parcours scolaire d'un étudiant"""
|
||||
|
||||
__tablename__ = "scolar_events"
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
event_id = db.synonym("id")
|
||||
etudid = db.Column(
|
||||
db.Integer,
|
||||
db.ForeignKey("identite.id"),
|
||||
)
|
||||
event_date = db.Column(db.DateTime(timezone=True), server_default=db.func.now())
|
||||
formsemestre_id = db.Column(
|
||||
db.Integer,
|
||||
db.ForeignKey("notes_formsemestre.id"),
|
||||
)
|
||||
ue_id = db.Column(
|
||||
db.Integer,
|
||||
db.ForeignKey("notes_ue.id"),
|
||||
)
|
||||
# 'CREATION', 'INSCRIPTION', 'DEMISSION',
|
||||
# 'AUT_RED', 'EXCLUS', 'VALID_UE', 'VALID_SEM'
|
||||
# 'ECHEC_SEM'
|
||||
# 'UTIL_COMPENSATION'
|
||||
event_type = db.Column(db.String(SHORT_STR_LEN))
|
||||
# Semestre compensé par formsemestre_id:
|
||||
comp_formsemestre_id = db.Column(
|
||||
db.Integer,
|
||||
db.ForeignKey("notes_formsemestre.id"),
|
||||
)
|
||||
|
||||
|
||||
class ScolarFormSemestreValidation(db.Model):
|
||||
"""Décisions de jury"""
|
||||
|
||||
__tablename__ = "scolar_formsemestre_validation"
|
||||
# Assure unicité de la décision:
|
||||
__table_args__ = (db.UniqueConstraint("etudid", "formsemestre_id", "ue_id"),)
|
||||
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
formsemestre_validation_id = db.synonym("id")
|
||||
etudid = db.Column(
|
||||
db.Integer,
|
||||
db.ForeignKey("identite.id"),
|
||||
index=True,
|
||||
)
|
||||
formsemestre_id = db.Column(
|
||||
db.Integer,
|
||||
db.ForeignKey("notes_formsemestre.id"),
|
||||
index=True,
|
||||
)
|
||||
ue_id = db.Column(
|
||||
db.Integer,
|
||||
db.ForeignKey("notes_ue.id"),
|
||||
index=True,
|
||||
)
|
||||
code = db.Column(db.String(CODE_STR_LEN), nullable=False, index=True)
|
||||
# NULL pour les UE, True|False pour les semestres:
|
||||
assidu = db.Column(db.Boolean)
|
||||
event_date = db.Column(db.DateTime(timezone=True), server_default=db.func.now())
|
||||
# NULL sauf si compense un semestre:
|
||||
compense_formsemestre_id = db.Column(
|
||||
db.Integer,
|
||||
db.ForeignKey("notes_formsemestre.id"),
|
||||
)
|
||||
moy_ue = db.Column(db.Float)
|
||||
# (normalement NULL) indice du semestre, utile seulement pour
|
||||
# UE "antérieures" et si la formation définit des UE utilisées
|
||||
# dans plusieurs semestres (cas R&T IUTV v2)
|
||||
semestre_id = db.Column(db.Integer)
|
||||
# Si UE validée dans le cursus d'un autre etablissement
|
||||
is_external = db.Column(db.Boolean, default=False, server_default="false")
|
||||
|
||||
|
||||
class ScolarAutorisationInscription(db.Model):
|
||||
"""Autorisation d'inscription dans un semestre"""
|
||||
|
||||
__tablename__ = "scolar_autorisation_inscription"
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
autorisation_inscription_id = db.synonym("id")
|
||||
|
||||
etudid = db.Column(
|
||||
db.Integer,
|
||||
db.ForeignKey("identite.id"),
|
||||
)
|
||||
formation_code = db.Column(db.String(SHORT_STR_LEN), nullable=False)
|
||||
# semestre ou on peut s'inscrire:
|
||||
semestre_id = db.Column(db.Integer)
|
||||
date = db.Column(db.DateTime(timezone=True), server_default=db.func.now())
|
||||
origin_formsemestre_id = db.Column(
|
||||
db.Integer,
|
||||
db.ForeignKey("notes_formsemestre.id"),
|
||||
)
|
||||
|
||||
|
||||
class BulAppreciations(db.Model):
|
||||
"""Appréciations sur bulletins"""
|
||||
|
||||
|
109
app/models/validations.py
Normal file
109
app/models/validations.py
Normal file
@ -0,0 +1,109 @@
|
||||
# -*- coding: UTF-8 -*
|
||||
|
||||
"""Notes, décisions de jury, évènements scolaires
|
||||
"""
|
||||
|
||||
from app import db
|
||||
from app.models import SHORT_STR_LEN
|
||||
from app.models import CODE_STR_LEN
|
||||
|
||||
|
||||
class ScolarFormSemestreValidation(db.Model):
|
||||
"""Décisions de jury"""
|
||||
|
||||
__tablename__ = "scolar_formsemestre_validation"
|
||||
# Assure unicité de la décision:
|
||||
__table_args__ = (db.UniqueConstraint("etudid", "formsemestre_id", "ue_id"),)
|
||||
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
formsemestre_validation_id = db.synonym("id")
|
||||
etudid = db.Column(
|
||||
db.Integer,
|
||||
db.ForeignKey("identite.id"),
|
||||
index=True,
|
||||
)
|
||||
formsemestre_id = db.Column(
|
||||
db.Integer,
|
||||
db.ForeignKey("notes_formsemestre.id"),
|
||||
index=True,
|
||||
)
|
||||
ue_id = db.Column(
|
||||
db.Integer,
|
||||
db.ForeignKey("notes_ue.id"),
|
||||
index=True,
|
||||
)
|
||||
code = db.Column(db.String(CODE_STR_LEN), nullable=False, index=True)
|
||||
# NULL pour les UE, True|False pour les semestres:
|
||||
assidu = db.Column(db.Boolean)
|
||||
event_date = db.Column(db.DateTime(timezone=True), server_default=db.func.now())
|
||||
# NULL sauf si compense un semestre:
|
||||
compense_formsemestre_id = db.Column(
|
||||
db.Integer,
|
||||
db.ForeignKey("notes_formsemestre.id"),
|
||||
)
|
||||
moy_ue = db.Column(db.Float)
|
||||
# (normalement NULL) indice du semestre, utile seulement pour
|
||||
# UE "antérieures" et si la formation définit des UE utilisées
|
||||
# dans plusieurs semestres (cas R&T IUTV v2)
|
||||
semestre_id = db.Column(db.Integer)
|
||||
# Si UE validée dans le cursus d'un autre etablissement
|
||||
is_external = db.Column(
|
||||
db.Boolean, default=False, server_default="false", index=True
|
||||
)
|
||||
|
||||
ue = db.relationship("UniteEns", lazy="select", uselist=False)
|
||||
|
||||
def __repr__(self):
|
||||
return f"{self.__class__.__name__}({self.formsemestre_id}, {self.etudid}, code={self.code}, ue={self.ue_id}, moy_ue={self.moy_ue})"
|
||||
|
||||
|
||||
class ScolarAutorisationInscription(db.Model):
|
||||
"""Autorisation d'inscription dans un semestre"""
|
||||
|
||||
__tablename__ = "scolar_autorisation_inscription"
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
autorisation_inscription_id = db.synonym("id")
|
||||
|
||||
etudid = db.Column(
|
||||
db.Integer,
|
||||
db.ForeignKey("identite.id"),
|
||||
)
|
||||
formation_code = db.Column(db.String(SHORT_STR_LEN), nullable=False)
|
||||
# semestre ou on peut s'inscrire:
|
||||
semestre_id = db.Column(db.Integer)
|
||||
date = db.Column(db.DateTime(timezone=True), server_default=db.func.now())
|
||||
origin_formsemestre_id = db.Column(
|
||||
db.Integer,
|
||||
db.ForeignKey("notes_formsemestre.id"),
|
||||
)
|
||||
|
||||
|
||||
class ScolarEvent(db.Model):
|
||||
"""Evenement dans le parcours scolaire d'un étudiant"""
|
||||
|
||||
__tablename__ = "scolar_events"
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
event_id = db.synonym("id")
|
||||
etudid = db.Column(
|
||||
db.Integer,
|
||||
db.ForeignKey("identite.id"),
|
||||
)
|
||||
event_date = db.Column(db.DateTime(timezone=True), server_default=db.func.now())
|
||||
formsemestre_id = db.Column(
|
||||
db.Integer,
|
||||
db.ForeignKey("notes_formsemestre.id"),
|
||||
)
|
||||
ue_id = db.Column(
|
||||
db.Integer,
|
||||
db.ForeignKey("notes_ue.id"),
|
||||
)
|
||||
# 'CREATION', 'INSCRIPTION', 'DEMISSION',
|
||||
# 'AUT_RED', 'EXCLUS', 'VALID_UE', 'VALID_SEM'
|
||||
# 'ECHEC_SEM'
|
||||
# 'UTIL_COMPENSATION'
|
||||
event_type = db.Column(db.String(SHORT_STR_LEN))
|
||||
# Semestre compensé par formsemestre_id:
|
||||
comp_formsemestre_id = db.Column(
|
||||
db.Integer,
|
||||
db.ForeignKey("notes_formsemestre.id"),
|
||||
)
|
@ -703,11 +703,12 @@ class NotesTable:
|
||||
où ue_status = {
|
||||
'est_inscrit' : True si étudiant inscrit à au moins un module de cette UE
|
||||
'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
|
||||
(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)
|
||||
'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,
|
||||
'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,
|
||||
@ -935,7 +936,7 @@ class NotesTable:
|
||||
"""
|
||||
return self.moy_gen[etudid]
|
||||
|
||||
def get_etud_moy_infos(self, etudid):
|
||||
def get_etud_moy_infos(self, etudid): # XXX OBSOLETE
|
||||
"""Infos sur moyennes"""
|
||||
return self.etud_moy_infos[etudid]
|
||||
|
||||
@ -1011,7 +1012,10 @@ class NotesTable:
|
||||
cnx = ndb.GetDBConnexion()
|
||||
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
|
||||
cursor.execute(
|
||||
"select etudid, code, assidu, compense_formsemestre_id, event_date from scolar_formsemestre_validation where formsemestre_id=%(formsemestre_id)s and ue_id is NULL;",
|
||||
"""SELECT etudid, code, assidu, compense_formsemestre_id, event_date
|
||||
FROM scolar_formsemestre_validation
|
||||
WHERE formsemestre_id=%(formsemestre_id)s AND ue_id is NULL;
|
||||
""",
|
||||
{"formsemestre_id": self.formsemestre_id},
|
||||
)
|
||||
decisions_jury = {}
|
||||
@ -1137,8 +1141,14 @@ class NotesTable:
|
||||
"""
|
||||
self.ue_capitalisees = scu.DictDefault(defaultvalue=[])
|
||||
cnx = None
|
||||
semestre_id = self.sem["semestre_id"]
|
||||
for etudid in self.get_etudids():
|
||||
capital = formsemestre_get_etud_capitalisation(self.sem, etudid)
|
||||
capital = formsemestre_get_etud_capitalisation(
|
||||
self.formation["id"],
|
||||
semestre_id,
|
||||
ndb.DateDMYtoISO(self.sem["date_debut"]),
|
||||
etudid,
|
||||
)
|
||||
for ue_cap in capital:
|
||||
# Si la moyenne d'UE n'avait pas été stockée (anciennes versions de ScoDoc)
|
||||
# il faut la calculer ici et l'enregistrer
|
||||
@ -1308,7 +1318,7 @@ class NotesTable:
|
||||
"""Liste des evaluations de ce semestre, avec leur etat"""
|
||||
return self.get_evaluations_etats()
|
||||
|
||||
def get_mod_evaluation_etat_list(self, moduleimpl_id):
|
||||
def get_mod_evaluation_etat_list(self, moduleimpl_id) -> list[dict]:
|
||||
"""Liste des évaluations de ce module"""
|
||||
return [
|
||||
e
|
||||
|
@ -142,7 +142,7 @@ def formsemestre_bulletinetud_dict(formsemestre_id, etudid, version="long"):
|
||||
prefs = sco_preferences.SemPreferences(formsemestre_id)
|
||||
# nt = sco_cache.NotesTableCache.get(formsemestre_id) # > toutes notes
|
||||
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||
nt: NotesTableCompat = res_sem.load_formsemestre_result(formsemestre)
|
||||
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
||||
if not nt.get_etud_etat(etudid):
|
||||
raise ScoValueError("Etudiant non inscrit à ce semestre")
|
||||
I = scu.DictDefault(defaultvalue="")
|
||||
|
@ -59,9 +59,9 @@ import traceback
|
||||
|
||||
from flask import g
|
||||
|
||||
from app import log
|
||||
from app.scodoc import notesdb as ndb
|
||||
from app.scodoc import sco_utils as scu
|
||||
from app import log
|
||||
|
||||
CACHE = None # set in app.__init__.py
|
||||
|
||||
@ -293,6 +293,7 @@ def invalidate_formsemestre( # was inval_cache(formsemestre_id=None, pdfonly=Fa
|
||||
|
||||
SemBulletinsPDFCache.invalidate_sems(formsemestre_ids)
|
||||
ResultatsSemestreCache.delete_many(formsemestre_ids)
|
||||
ValidationsSemestreCache.delete_many(formsemestre_ids)
|
||||
|
||||
|
||||
class DefferedSemCacheManager:
|
||||
@ -319,10 +320,20 @@ class DefferedSemCacheManager:
|
||||
|
||||
# ---- Nouvelles classes ScoDoc 9.2
|
||||
class ResultatsSemestreCache(ScoDocCache):
|
||||
"""Cache pour les résultats ResultatsSemestre.
|
||||
"""Cache pour les résultats ResultatsSemestre (notes et moyennes)
|
||||
Clé: formsemestre_id
|
||||
Valeur: { un paquet de dataframes }
|
||||
"""
|
||||
|
||||
prefix = "RSEM"
|
||||
timeout = 60 * 60 # ttl 1 heure (en phase de mise au point)
|
||||
|
||||
|
||||
class ValidationsSemestreCache(ScoDocCache):
|
||||
"""Cache pour les résultats de jury d'un semestre
|
||||
Clé: formsemestre_id
|
||||
Valeur: un paquet de DataFrames
|
||||
"""
|
||||
|
||||
prefix = "VSC"
|
||||
timeout = 60 * 60 # ttl 1 heure (en phase de mise au point)
|
||||
|
@ -170,18 +170,18 @@ CODES_SEM_REO = {NAR: 1} # reorientation
|
||||
CODES_UE_VALIDES = {ADM: True, CMP: True} # UE validée
|
||||
|
||||
|
||||
def code_semestre_validant(code):
|
||||
def code_semestre_validant(code: str) -> bool:
|
||||
"Vrai si ce CODE entraine la validation du semestre"
|
||||
return CODES_SEM_VALIDES.get(code, False)
|
||||
|
||||
|
||||
def code_semestre_attente(code):
|
||||
def code_semestre_attente(code: str) -> bool:
|
||||
"Vrai si ce CODE est un code d'attente (semestre validable plus tard par jury ou compensation)"
|
||||
return CODES_SEM_ATTENTES.get(code, False)
|
||||
|
||||
|
||||
def code_ue_validant(code):
|
||||
"Vrai si ce code entraine la validation de l'UE"
|
||||
def code_ue_validant(code: str) -> bool:
|
||||
"Vrai si ce code entraine la validation des UEs du semestre."
|
||||
return CODES_UE_VALIDES.get(code, False)
|
||||
|
||||
|
||||
@ -259,6 +259,7 @@ class TypeParcours(object):
|
||||
) # par defaut, autorise tous les types d'UE
|
||||
APC_SAE = False # Approche par compétences avec ressources et SAÉs
|
||||
USE_REFERENTIEL_COMPETENCES = False # Lien avec ref. comp.
|
||||
ECTS_FONDAMENTAUX_PER_YEAR = 0.0 # pour ISCID, deprecated
|
||||
|
||||
def check(self, formation=None):
|
||||
return True, "" # status, diagnostic_message
|
||||
|
@ -32,7 +32,7 @@ from flask_login import current_user
|
||||
|
||||
from app import db
|
||||
from app.models import Formation, UniteEns, Matiere, Module, FormSemestre, ModuleImpl
|
||||
from app.models.notes import ScolarFormSemestreValidation
|
||||
from app.models.validations import ScolarFormSemestreValidation
|
||||
from app.scodoc.sco_codes_parcours import UE_SPORT
|
||||
import app.scodoc.sco_utils as scu
|
||||
from app.scodoc import sco_groups
|
||||
|
@ -123,9 +123,6 @@ def module_create(
|
||||
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).
|
||||
"""
|
||||
from app.scodoc import sco_formations
|
||||
from app.scodoc import sco_edit_ue
|
||||
|
||||
if matiere_id:
|
||||
matiere = Matiere.query.get_or_404(matiere_id)
|
||||
ue = matiere.ue
|
||||
@ -160,7 +157,7 @@ def module_create(
|
||||
else:
|
||||
H += [
|
||||
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(
|
||||
ModuleImpl.formsemestre_id == FormSemestre.id,
|
||||
ModuleImpl.module_id == module_id,
|
||||
).all(),
|
||||
)
|
||||
.order_by(FormSemestre.date_debut)
|
||||
.all(),
|
||||
),
|
||||
]
|
||||
if not unlocked:
|
||||
H.append(
|
||||
"""<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:
|
||||
module_types = scu.ModuleType # tous les types
|
||||
else:
|
||||
@ -728,30 +721,9 @@ def module_edit(module_id=None):
|
||||
initvalues=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:
|
||||
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:
|
||||
return flask.redirect(
|
||||
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"""
|
||||
create = int(create)
|
||||
if not create:
|
||||
U = ue_list(args={"ue_id": ue_id})
|
||||
if not U:
|
||||
raise ScoValueError("UE inexistante !")
|
||||
U = U[0]
|
||||
formation_id = U["formation_id"]
|
||||
title = "Modification de l'UE %(titre)s" % U
|
||||
initvalues = U
|
||||
ue: UniteEns = UniteEns.query.get_or_404(ue_id)
|
||||
ue_dict = ue.to_dict()
|
||||
formation_id = ue.formation_id
|
||||
title = f"Modification de l'UE {ue.acronyme} {ue.titre}"
|
||||
initvalues = ue_dict
|
||||
submitlabel = "Modifier les valeurs"
|
||||
can_change_semestre_id = ue.modules.count() == 0
|
||||
else:
|
||||
ue = None
|
||||
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"
|
||||
can_change_semestre_id = True
|
||||
formation = Formation.query.get(formation_id)
|
||||
if not formation:
|
||||
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 = [str(x) for x in ue_types]
|
||||
|
||||
fw = [
|
||||
form_descr = [
|
||||
("ue_id", {"input_type": "hidden"}),
|
||||
("create", {"input_type": "hidden", "default": create}),
|
||||
("formation_id", {"input_type": "hidden", "default": formation_id}),
|
||||
@ -296,6 +300,9 @@ def ue_edit(ue_id=None, create=False, formation_id=None, default_semestre_idx=No
|
||||
"type": "int",
|
||||
},
|
||||
),
|
||||
]
|
||||
if can_change_semestre_id:
|
||||
form_descr += [
|
||||
(
|
||||
"semestre_idx",
|
||||
{
|
||||
@ -303,11 +310,18 @@ def ue_edit(ue_id=None, create=False, formation_id=None, default_semestre_idx=No
|
||||
"type": "int",
|
||||
"allow_null": False,
|
||||
"title": parcours.SESSION_NAME.capitalize(),
|
||||
"explanation": "%s de l'UE dans la formation" % parcours.SESSION_NAME,
|
||||
"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",
|
||||
{
|
||||
@ -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:
|
||||
fw.append(
|
||||
form_descr.append(
|
||||
(
|
||||
"create_matiere",
|
||||
{
|
||||
@ -391,14 +405,33 @@ def ue_edit(ue_id=None, create=False, formation_id=None, default_semestre_idx=No
|
||||
tf = TrivialFormulator(
|
||||
request.base_url,
|
||||
scu.get_request_args(),
|
||||
fw,
|
||||
form_descr,
|
||||
initvalues=initvalues,
|
||||
submitlabel=submitlabel,
|
||||
)
|
||||
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>"""
|
||||
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:
|
||||
if create:
|
||||
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
|
||||
)
|
||||
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:
|
||||
ue.guess_semestre_idx()
|
||||
# vérifie qu'on a bien au moins une matière dans chaque UE
|
||||
for ue in ues_obj:
|
||||
if ue.matieres.count() < 1:
|
||||
mat = Matiere(ue_id=ue.id)
|
||||
db.session.add(mat)
|
||||
# donne des couleurs aux UEs crées avant
|
||||
colorie_anciennes_ues(ues_obj)
|
||||
db.session.commit()
|
||||
ues = [ue.to_dict() for ue in ues_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)
|
||||
|
||||
|
||||
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
|
||||
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,
|
||||
@ -1378,3 +1412,45 @@ def ue_list_semestre_ids(ue):
|
||||
"""
|
||||
modules = sco_edit_module.module_list(args={"ue_id": ue["ue_id"]})
|
||||
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)
|
||||
etat = _eval_etat(evals)
|
||||
etat["attente"] = moduleimpl_id in [
|
||||
m["moduleimpl_id"] for m in nt.get_moduleimpls_attente()
|
||||
] # > liste moduleimpl en attente
|
||||
# Il y a-t-il des notes en attente dans ce module ?
|
||||
etat["attente"] = nt.modimpls_results[moduleimpl_id].en_attente
|
||||
return etat
|
||||
|
||||
|
||||
|
@ -991,9 +991,9 @@ def formsemestre_status(formsemestre_id=None):
|
||||
modimpls = sco_moduleimpl.moduleimpl_withmodule_list(
|
||||
formsemestre_id=formsemestre_id
|
||||
)
|
||||
nt = sco_cache.NotesTableCache.get(formsemestre_id)
|
||||
# WIP formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||
# WIP nt = res_sem.load_formsemestre_result(formsemestre)
|
||||
# nt = sco_cache.NotesTableCache.get(formsemestre_id)
|
||||
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||
nt = res_sem.load_formsemestre_results(formsemestre)
|
||||
|
||||
# Construit la liste de tous les enseignants de ce semestre:
|
||||
mails_enseignants = set(
|
||||
|
@ -31,15 +31,21 @@ import time
|
||||
|
||||
import flask
|
||||
from flask import url_for, g, request
|
||||
from app.api.sco_api import formsemestre
|
||||
|
||||
import app.scodoc.notesdb as ndb
|
||||
import app.scodoc.sco_utils as scu
|
||||
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.TrivialFormulator import TrivialFormulator, tf_error_message
|
||||
|
||||
from app.scodoc import html_sco_header
|
||||
from app.scodoc import sco_abs
|
||||
from app.scodoc import sco_codes_parcours
|
||||
@ -542,9 +548,9 @@ def formsemestre_recap_parcours_table(
|
||||
else:
|
||||
ass = ""
|
||||
|
||||
nt = sco_cache.NotesTableCache.get(
|
||||
sem["formsemestre_id"]
|
||||
) # > get_ues_stat_dict, get_etud_moy_gen, get_etud_ue_status
|
||||
formsemestre = FormSemestre.query.get(sem["formsemestre_id"])
|
||||
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
||||
# nt = sco_cache.NotesTableCache.get(sem["formsemestre_id"])
|
||||
if is_cur:
|
||||
type_sem = "*" # now unused
|
||||
class_sem = "sem_courant"
|
||||
@ -686,7 +692,7 @@ def formsemestre_recap_parcours_table(
|
||||
sco_preferences.get_preference("bul_show_ects", sem["formsemestre_id"])
|
||||
or nt.parcours.ECTS_ONLY
|
||||
):
|
||||
etud_moy_infos = nt.get_etud_moy_infos(etudid)
|
||||
etud_ects_infos = nt.get_etud_ects_pot(etudid)
|
||||
H.append(
|
||||
'<tr class="%s rcp_l2 sem_%s">' % (class_sem, sem["formsemestre_id"])
|
||||
)
|
||||
@ -697,7 +703,7 @@ def formsemestre_recap_parcours_table(
|
||||
# total ECTS (affiché sous la moyenne générale)
|
||||
H.append(
|
||||
'<td class="sem_ects_tit"><a title="crédit potentiels (dont nb de fondamentaux)">ECTS:</a></td><td class="sem_ects">%g <span class="ects_fond">%g</span></td>'
|
||||
% (etud_moy_infos["ects_pot"], etud_moy_infos["ects_pot_fond"])
|
||||
% (etud_ects_infos["ects_pot"], etud_ects_infos["ects_pot_fond"])
|
||||
)
|
||||
H.append('<td class="rcp_abs"></td>')
|
||||
# ECTS validables dans chaque UE
|
||||
@ -1056,7 +1062,7 @@ def formsemestre_validate_previous_ue(formsemestre_id, etudid):
|
||||
"title": "Indice du semestre",
|
||||
"explanation": "Facultatif: indice du semestre dans la formation",
|
||||
"allow_null": True,
|
||||
"allowed_values": [""] + [str(x) for x in range(11)],
|
||||
"allowed_values": [""] + [x for x in range(11)],
|
||||
"labels": ["-"] + list(range(11)),
|
||||
},
|
||||
),
|
||||
|
@ -837,7 +837,7 @@ def _add_apc_columns(
|
||||
# => On recharge tout dans les nouveaux modèles
|
||||
# rows est une liste de dict avec une clé "etudid"
|
||||
# on va y ajouter une clé par UE du semestre
|
||||
nt: NotesTableCompat = res_sem.load_formsemestre_result(modimpl.formsemestre)
|
||||
nt: NotesTableCompat = res_sem.load_formsemestre_results(modimpl.formsemestre)
|
||||
modimpl_results: ModuleImplResults = nt.modimpls_results[modimpl.id]
|
||||
|
||||
# XXX A ENLEVER TODO
|
||||
|
@ -28,6 +28,7 @@
|
||||
"""Semestres: gestion parcours DUT (Arreté du 13 août 2005)
|
||||
"""
|
||||
|
||||
from app.models.ues import UniteEns
|
||||
import app.scodoc.sco_utils as scu
|
||||
import app.scodoc.notesdb as ndb
|
||||
from app import log
|
||||
@ -678,10 +679,10 @@ class SituationEtudParcoursECTS(SituationEtudParcoursGeneric):
|
||||
|
||||
Dans ce type de parcours, on n'utilise que ADM, AJ, et ADJ (?).
|
||||
"""
|
||||
etud_moy_infos = self.nt.get_etud_moy_infos(self.etudid)
|
||||
etud_ects_infos = self.nt.get_etud_ects_pot(self.etudid)
|
||||
if (
|
||||
etud_moy_infos["ects_pot"] >= self.parcours.ECTS_BARRE_VALID_YEAR
|
||||
and etud_moy_infos["ects_pot"] >= self.parcours.ECTS_FONDAMENTAUX_PER_YEAR
|
||||
etud_ects_infos["ects_pot"] >= self.parcours.ECTS_BARRE_VALID_YEAR
|
||||
and etud_ects_infos["ects_pot"] >= self.parcours.ECTS_FONDAMENTAUX_PER_YEAR
|
||||
):
|
||||
choices = [
|
||||
DecisionSem(
|
||||
@ -954,6 +955,9 @@ def do_formsemestre_validate_ue(
|
||||
is_external=False,
|
||||
):
|
||||
"""Ajoute ou change validation UE"""
|
||||
if semestre_id is None:
|
||||
ue = UniteEns.query.get_or_404(ue_id)
|
||||
semestre_id = ue.semestre_idx
|
||||
args = {
|
||||
"formsemestre_id": formsemestre_id,
|
||||
"etudid": etudid,
|
||||
@ -971,7 +975,8 @@ def do_formsemestre_validate_ue(
|
||||
if formsemestre_id:
|
||||
cond += " and formsemestre_id=%(formsemestre_id)s"
|
||||
if semestre_id:
|
||||
cond += " and semestre_id=%(semestre_id)s"
|
||||
cond += " and (semestre_id=%(semestre_id)s or semestre_id is NULL)"
|
||||
log(f"formsemestre_validate_ue: deleting where {cond}, args={args})")
|
||||
cursor.execute("delete from scolar_formsemestre_validation where " + cond, args)
|
||||
# insert
|
||||
args["code"] = code
|
||||
@ -980,7 +985,7 @@ def do_formsemestre_validate_ue(
|
||||
# stocke la moyenne d'UE capitalisée:
|
||||
moy_ue = nt.get_etud_ue_status(etudid, ue_id)["moy"]
|
||||
args["moy_ue"] = moy_ue
|
||||
log("formsemestre_validate_ue: %s" % args)
|
||||
log("formsemestre_validate_ue: create %s" % args)
|
||||
if code != None:
|
||||
scolar_formsemestre_validation_create(cnx, args)
|
||||
else:
|
||||
@ -1039,7 +1044,9 @@ def formsemestre_get_autorisation_inscription(etudid, origin_formsemestre_id):
|
||||
)
|
||||
|
||||
|
||||
def formsemestre_get_etud_capitalisation(sem, etudid):
|
||||
def formsemestre_get_etud_capitalisation(
|
||||
formation_id: int, semestre_idx: int, date_debut, etudid: int
|
||||
) -> list[dict]:
|
||||
"""Liste des UE capitalisées (ADM) correspondant au semestre sem et à l'étudiant.
|
||||
|
||||
Recherche dans les semestres de la même formation (code) avec le même
|
||||
@ -1057,7 +1064,9 @@ def formsemestre_get_etud_capitalisation(sem, etudid):
|
||||
cnx = ndb.GetDBConnexion()
|
||||
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
|
||||
cursor.execute(
|
||||
"""select distinct SFV.*, ue.ue_code from notes_ue ue, notes_formations nf,
|
||||
"""
|
||||
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
|
||||
@ -1078,9 +1087,9 @@ def formsemestre_get_etud_capitalisation(sem, etudid):
|
||||
""",
|
||||
{
|
||||
"etudid": etudid,
|
||||
"formation_id": sem["formation_id"],
|
||||
"semestre_id": sem["semestre_id"],
|
||||
"date_debut": ndb.DateDMYtoISO(sem["date_debut"]),
|
||||
"formation_id": formation_id,
|
||||
"semestre_id": semestre_idx,
|
||||
"date_debut": date_debut,
|
||||
},
|
||||
)
|
||||
|
||||
|
@ -53,6 +53,7 @@ from reportlab.lib import styles
|
||||
|
||||
import flask
|
||||
from flask import url_for, g, request
|
||||
from app.models.ues import UniteEns
|
||||
|
||||
import app.scodoc.sco_utils as scu
|
||||
import app.scodoc.notesdb as ndb
|
||||
@ -140,23 +141,21 @@ def descr_autorisations(autorisations):
|
||||
return ", ".join(alist)
|
||||
|
||||
|
||||
def _comp_ects_by_ue_code_and_type(nt, decision_ues):
|
||||
def _comp_ects_by_ue_code(nt, decision_ues):
|
||||
"""Calcul somme des ECTS validés dans ce semestre (sans les UE capitalisées)
|
||||
decision_ues est le resultat de nt.get_etud_decision_ues
|
||||
Chaque resultat est un dict: { ue_code : ects }
|
||||
"""
|
||||
if not decision_ues:
|
||||
return {}, {}
|
||||
return {}
|
||||
|
||||
ects_by_ue_code = {}
|
||||
ects_by_ue_type = scu.DictDefault(defaultvalue=0) # { ue_type : ects validés }
|
||||
for ue_id in decision_ues:
|
||||
d = decision_ues[ue_id]
|
||||
ue = nt.uedict[ue_id]
|
||||
ects_by_ue_code[ue["ue_code"]] = d["ects"]
|
||||
ects_by_ue_type[ue["type"]] += d["ects"]
|
||||
ue = UniteEns.query.get(ue_id)
|
||||
ects_by_ue_code[ue.ue_code] = 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):
|
||||
@ -166,10 +165,7 @@ def _comp_ects_capitalises_by_ue_code(nt, etudid):
|
||||
for ue in ues:
|
||||
ue_status = nt.get_etud_ue_status(etudid, ue["ue_id"])
|
||||
if ue_status["is_capitalized"]:
|
||||
try:
|
||||
ects_val = float(ue_status["ue"]["ects"])
|
||||
except (ValueError, TypeError):
|
||||
ects_val = 0.0
|
||||
ects_val = float(ue_status["ue"]["ects"] or 0.0)
|
||||
ects_by_ue_code[ue["ue_code"]] = ects_val
|
||||
|
||||
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)
|
||||
d["sum_ects_capitalises"] = sum(ects_capitalises_by_ue_code.values())
|
||||
ects_by_ue_code, ects_by_ue_type = _comp_ects_by_ue_code_and_type(
|
||||
nt, d["decisions_ue"]
|
||||
)
|
||||
ects_by_ue_code = _comp_ects_by_ue_code(nt, d["decisions_ue"])
|
||||
d["sum_ects"] = _sum_ects_dicts(ects_capitalises_by_ue_code, ects_by_ue_code)
|
||||
d["sum_ects_by_type"] = ects_by_ue_type
|
||||
|
||||
if d["decision_sem"] and sco_codes_parcours.code_semestre_validant(
|
||||
d["decision_sem"]["code"]
|
||||
|
@ -25,7 +25,7 @@
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
"""Tableau recapitulatif des notes d'un semestre
|
||||
"""Tableau récapitulatif des notes d'un semestre
|
||||
"""
|
||||
import datetime
|
||||
import json
|
||||
@ -41,6 +41,7 @@ from app.comp import res_sem
|
||||
from app.comp.res_common import NotesTableCompat
|
||||
from app.models import FormSemestre
|
||||
from app.models.etudiants import Identite
|
||||
from app.models.evaluations import Evaluation
|
||||
|
||||
import app.scodoc.sco_utils as scu
|
||||
from app.scodoc import html_sco_header
|
||||
@ -308,8 +309,8 @@ def make_formsemestre_recapcomplet(
|
||||
|
||||
# nt = sco_cache.NotesTableCache.get(formsemestre_id) # sco91
|
||||
# sco92 :
|
||||
nt: NotesTableCompat = res_sem.load_formsemestre_result(formsemestre)
|
||||
modimpls = nt.get_modimpls_dict()
|
||||
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
||||
modimpls = formsemestre.modimpls_sorted
|
||||
ues = nt.get_ues_stat_dict() # incluant le(s) UE de sport
|
||||
#
|
||||
# if formsemestre.formation.is_apc():
|
||||
@ -367,15 +368,16 @@ def make_formsemestre_recapcomplet(
|
||||
pass
|
||||
if not hidemodules and not ue["is_external"]:
|
||||
for modimpl in modimpls:
|
||||
if modimpl["module"]["ue_id"] == ue["ue_id"]:
|
||||
code = modimpl["module"]["code"]
|
||||
if modimpl.module.ue_id == ue["ue_id"]:
|
||||
code = modimpl.module.code
|
||||
h.append(code)
|
||||
cod2mod[code] = modimpl # pour fabriquer le lien
|
||||
if format == "xlsall":
|
||||
evals = nt.get_mod_evaluation_etat_list(
|
||||
modimpl["moduleimpl_id"]
|
||||
)
|
||||
mod_evals[modimpl["moduleimpl_id"]] = evals
|
||||
evals = nt.modimpls_results[
|
||||
modimpl.id
|
||||
].get_evaluations_completes(modimpl)
|
||||
# evals = nt.get_mod_evaluation_etat_list(...
|
||||
mod_evals[modimpl.id] = evals
|
||||
h += _list_notes_evals_titles(code, evals)
|
||||
|
||||
h += admission_extra_cols
|
||||
@ -483,7 +485,7 @@ def make_formsemestre_recapcomplet(
|
||||
if not hidemodules and not ue["is_external"]:
|
||||
j = 0
|
||||
for modimpl in modimpls:
|
||||
if modimpl["module"]["ue_id"] == ue["ue_id"]:
|
||||
if modimpl.module.ue_id == ue["ue_id"]:
|
||||
l.append(
|
||||
fmtnum(
|
||||
scu.fmt_note(
|
||||
@ -492,9 +494,7 @@ def make_formsemestre_recapcomplet(
|
||||
)
|
||||
) # moyenne etud dans module
|
||||
if format == "xlsall":
|
||||
l += _list_notes_evals(
|
||||
mod_evals[modimpl["moduleimpl_id"]], etudid
|
||||
)
|
||||
l += _list_notes_evals(mod_evals[modimpl.id], etudid)
|
||||
j += 1
|
||||
if not hidebac:
|
||||
for k in admission_extra_cols:
|
||||
@ -509,9 +509,7 @@ def make_formsemestre_recapcomplet(
|
||||
if not hidemodules: # moy/min/max dans chaque module
|
||||
mods_stats = {} # moduleimpl_id : stats
|
||||
for modimpl in modimpls:
|
||||
mods_stats[modimpl["moduleimpl_id"]] = nt.get_mod_stats(
|
||||
modimpl["moduleimpl_id"]
|
||||
)
|
||||
mods_stats[modimpl.id] = nt.get_mod_stats(modimpl.id)
|
||||
|
||||
def add_bottom_stat(key, title, corner_value=""):
|
||||
l = ["", title]
|
||||
@ -551,16 +549,16 @@ def make_formsemestre_recapcomplet(
|
||||
# ue_index.append(len(l) - 1)
|
||||
if not hidemodules and not ue["is_external"]:
|
||||
for modimpl in modimpls:
|
||||
if modimpl["module"]["ue_id"] == ue["ue_id"]:
|
||||
if modimpl.module.ue_id == ue["ue_id"]:
|
||||
if key == "coef":
|
||||
coef = modimpl["module"]["coefficient"]
|
||||
coef = modimpl.module.coefficient
|
||||
if format[:3] != "xls":
|
||||
coef = str(coef)
|
||||
l.append(coef)
|
||||
elif key == "ects":
|
||||
l.append("") # ECTS module ?
|
||||
else:
|
||||
val = mods_stats[modimpl["moduleimpl_id"]][key]
|
||||
val = mods_stats[modimpl.id][key]
|
||||
if key == "nb_valid_evals":
|
||||
if (
|
||||
format[:3] != "xls"
|
||||
@ -571,9 +569,7 @@ def make_formsemestre_recapcomplet(
|
||||
l.append(val)
|
||||
|
||||
if format == "xlsall":
|
||||
l += _list_notes_evals_stats(
|
||||
mod_evals[modimpl["moduleimpl_id"]], key
|
||||
)
|
||||
l += _list_notes_evals_stats(mod_evals[modimpl.id], key)
|
||||
if modejury:
|
||||
l.append("") # case vide sur ligne "Moyennes"
|
||||
|
||||
@ -595,7 +591,7 @@ def make_formsemestre_recapcomplet(
|
||||
add_bottom_stat("nb_valid_evals", "Nb évals")
|
||||
add_bottom_stat("ects", "ECTS")
|
||||
|
||||
# Generation table au format demandé
|
||||
# Génération de la table au format demandé
|
||||
if format == "html":
|
||||
# Table format HTML
|
||||
H = [
|
||||
@ -649,12 +645,12 @@ def make_formsemestre_recapcomplet(
|
||||
): # Rang: force tri numerique pour sortable
|
||||
cls = cls + " sortnumeric"
|
||||
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>' % (
|
||||
cls,
|
||||
mod["moduleimpl_id"],
|
||||
mod["module"]["titre"],
|
||||
sco_users.user_info(mod["responsable_id"])["nomcomplet"],
|
||||
modimpl.id,
|
||||
modimpl.module.titre,
|
||||
sco_users.user_info(modimpl.responsable_id)["nomcomplet"],
|
||||
F[0][i],
|
||||
)
|
||||
else:
|
||||
@ -838,18 +834,13 @@ def make_formsemestre_recapcomplet(
|
||||
raise ValueError("unknown format %s" % format)
|
||||
|
||||
|
||||
def _list_notes_evals(evals, etudid):
|
||||
def _list_notes_evals(evals: list[Evaluation], etudid: int) -> list[str]:
|
||||
"""Liste des notes des evaluations completes de ce module
|
||||
(pour table xls avec evals)
|
||||
"""
|
||||
L = []
|
||||
for e in evals:
|
||||
if (
|
||||
e["etat"]["evalcomplete"]
|
||||
or e["etat"]["evalattente"]
|
||||
or e["publish_incomplete"]
|
||||
):
|
||||
notes_db = sco_evaluation_db.do_evaluation_get_all_notes(e["evaluation_id"])
|
||||
notes_db = sco_evaluation_db.do_evaluation_get_all_notes(e.evaluation_id)
|
||||
if etudid in notes_db:
|
||||
val = notes_db[etudid]["value"]
|
||||
else:
|
||||
@ -860,39 +851,31 @@ def _list_notes_evals(evals, etudid):
|
||||
return L
|
||||
|
||||
|
||||
def _list_notes_evals_titles(codemodule, evals):
|
||||
def _list_notes_evals_titles(codemodule: str, evals: list[Evaluation]) -> list[str]:
|
||||
"""Liste des titres des evals completes"""
|
||||
L = []
|
||||
eval_index = len(evals) - 1
|
||||
for e in evals:
|
||||
if (
|
||||
e["etat"]["evalcomplete"]
|
||||
or e["etat"]["evalattente"]
|
||||
or e["publish_incomplete"]
|
||||
):
|
||||
L.append(codemodule + "-" + str(eval_index) + "-" + e["jour"].isoformat())
|
||||
L.append(codemodule + "-" + str(eval_index) + "-" + e.jour.isoformat())
|
||||
eval_index -= 1
|
||||
return L
|
||||
|
||||
|
||||
def _list_notes_evals_stats(evals, key):
|
||||
def _list_notes_evals_stats(evals: list[Evaluation], key: str) -> list[str]:
|
||||
"""Liste des stats (moy, ou rien!) des evals completes"""
|
||||
L = []
|
||||
for e in evals:
|
||||
if (
|
||||
e["etat"]["evalcomplete"]
|
||||
or e["etat"]["evalattente"]
|
||||
or e["publish_incomplete"]
|
||||
):
|
||||
if key == "moy":
|
||||
val = e["etat"]["moy_num"]
|
||||
L.append(scu.fmt_note(val, keep_numeric=True))
|
||||
# TODO #sco92
|
||||
# val = e["etat"]["moy_num"]
|
||||
# L.append(scu.fmt_note(val, keep_numeric=True))
|
||||
L.append("")
|
||||
elif key == "max":
|
||||
L.append(e["note_max"])
|
||||
L.append(e.note_max)
|
||||
elif key == "min":
|
||||
L.append(0.0)
|
||||
elif key == "coef":
|
||||
L.append(e["coefficient"])
|
||||
L.append(e.coefficient)
|
||||
else:
|
||||
L.append("") # on n'a pas sous la main min/max
|
||||
return L
|
||||
|
@ -1771,18 +1771,28 @@ ul.notes_module_list {
|
||||
}
|
||||
|
||||
div#ue_list_code {
|
||||
background-color: rgb(220,220,220);
|
||||
font-size: small;
|
||||
padding-left: 4px;
|
||||
padding-bottom: 1px;
|
||||
background-color: rgb(155, 218, 155);
|
||||
padding: 10px;
|
||||
border: 1px solid blue;
|
||||
border-radius: 10px;
|
||||
padding: 10px;
|
||||
margin-top: 10px;
|
||||
margin: 3ex;
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
ul.notes_module_list {
|
||||
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 {
|
||||
background-color: rgb(220,250,220);
|
||||
padding-left: 4px;
|
||||
@ -1799,12 +1809,16 @@ span.ue_share {
|
||||
|
||||
div.ue_warning {
|
||||
border: 1px solid red;
|
||||
border-radius: 10px;
|
||||
background-color: rgb(250,220,220);
|
||||
margin: 3ex;
|
||||
padding-left: 1ex;
|
||||
padding-right: 1ex;
|
||||
margin-top: 10px;
|
||||
margin-right: 15px;
|
||||
margin-bottom: 10px;
|
||||
padding: 10px;
|
||||
}
|
||||
div.ue_warning:first-child {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
div.ue_warning span:before {
|
||||
content: url(/ScoDoc/static/icons/warning_img.png);
|
||||
vertical-align: -80%;
|
||||
|
@ -1,3 +1,4 @@
|
||||
{# -*- mode: jinja-html -*- #}
|
||||
<div class="help">
|
||||
<p class="help">
|
||||
Les modules sont décrits dans le programme pédagogique. Un module est pour ce
|
||||
@ -24,16 +25,18 @@
|
||||
<a href="https://scodoc.org/BUT" target="_blank">la documentation</a>.
|
||||
</p>
|
||||
{%endif%}
|
||||
</div>
|
||||
|
||||
{% if formsemestres %}
|
||||
<p class="help">
|
||||
{% if formsemestres %}
|
||||
<div class="ue_warning"> <span><b>Module déjà utilisé dans des semestres, soyez prudents !</b></span>
|
||||
<p class="help">
|
||||
Ce module est utilisé dans des semestres déjà mis en place, il faut prêter attention
|
||||
aux conséquences des changements effectués ici: par exemple les coefficients vont modifier
|
||||
les notes moyennes calculées. Les modules déjà utilisés ne peuvent pas être changés de semestre, ni détruits.
|
||||
Si vous souhaitez faire cela, allez d'abord modifier les semestres concernés pour déselectionner le module.
|
||||
</p>
|
||||
<h4>Semestres utilisant ce module:</h4>
|
||||
<ul>
|
||||
</p>
|
||||
<h4>Semestres utilisant ce module:</h4>
|
||||
<ul>
|
||||
{%for formsemestre in formsemestres %}
|
||||
<li><a class="stdlink" href="{{
|
||||
url_for('notes.formsemestre_status',
|
||||
@ -41,7 +44,7 @@
|
||||
}}">{{formsemestre.titre_mois()}}</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{%endif%}
|
||||
|
||||
</ul>
|
||||
</div>
|
||||
{%endif%}
|
||||
|
||||
|
@ -496,6 +496,7 @@ def SignaleAbsenceGrSemestre(
|
||||
require_module = sco_preferences.get_preference(
|
||||
"abs_require_module", formsemestre_id
|
||||
)
|
||||
sem = sco_formsemestre.do_formsemestre_list({"formsemestre_id": formsemestre_id})[0]
|
||||
etuds = [
|
||||
sco_etud.get_etud_info(etudid=m["etudid"], filled=True)[0]
|
||||
for m in groups_infos.members
|
||||
@ -526,9 +527,7 @@ def SignaleAbsenceGrSemestre(
|
||||
|
||||
if etuds:
|
||||
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()
|
||||
jourdebut = sco_abs.ddmmyyyy(datedebut, 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
|
||||
@permission_required(Permission.ScoView)
|
||||
@scodoc7func
|
||||
|
Loading…
Reference in New Issue
Block a user