forked from ScoDoc/ScoDoc
Merge branch 'dev92' of https://scodoc.org/git/ScoDoc/ScoDoc into entreprises
This commit is contained in:
commit
15f313fedd
@ -20,10 +20,10 @@ Flask, SQLAlchemy, au lien de Python2/Zope dans les versions précédentes).
|
||||
|
||||
### État actuel (26 jan 22)
|
||||
|
||||
- 9.1 (master) reproduit l'ensemble des fonctions de ScoDoc 7 (donc pas de BUT), sauf:
|
||||
- 9.1.5x (master) reproduit l'ensemble des fonctions de ScoDoc 7 (donc pas de BUT), sauf:
|
||||
- ancien module "Entreprises" (obsolète) et ajoute la gestion du BUT.
|
||||
|
||||
- 9.2 (branche refactor_nt) est la version de développement.
|
||||
- 9.2 (branche dev92) est la version de développement.
|
||||
|
||||
|
||||
### Lignes de commandes
|
||||
|
@ -76,7 +76,9 @@ class User(UserMixin, db.Model):
|
||||
"Departement",
|
||||
foreign_keys=[Departement.acronym],
|
||||
primaryjoin=(dept == Departement.acronym),
|
||||
lazy="dynamic",
|
||||
lazy="select",
|
||||
passive_deletes="all",
|
||||
uselist=False,
|
||||
)
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
@ -236,7 +238,7 @@ class User(UserMixin, db.Model):
|
||||
def get_dept_id(self) -> int:
|
||||
"returns user's department id, or None"
|
||||
if self.dept:
|
||||
return self._departement.first().id
|
||||
return self._departement.id
|
||||
return None
|
||||
|
||||
# Permissions management:
|
||||
|
@ -9,14 +9,15 @@
|
||||
|
||||
import datetime
|
||||
from flask import url_for, g
|
||||
from app.models.formsemestre import FormSemestre
|
||||
|
||||
from app.comp.res_but import ResultatsSemestreBUT
|
||||
from app.models import FormSemestre, Identite
|
||||
from app.scodoc import sco_utils as scu
|
||||
from app.scodoc import sco_bulletins_json
|
||||
from app.scodoc import sco_bulletins_pdf
|
||||
from app.scodoc import sco_preferences
|
||||
from app.scodoc.sco_codes_parcours import UE_SPORT
|
||||
from app.scodoc.sco_utils import fmt_note
|
||||
from app.comp.res_but import ResultatsSemestreBUT
|
||||
|
||||
|
||||
class BulletinBUT:
|
||||
@ -28,6 +29,7 @@ class BulletinBUT:
|
||||
def __init__(self, formsemestre: FormSemestre):
|
||||
""" """
|
||||
self.res = ResultatsSemestreBUT(formsemestre)
|
||||
self.prefs = sco_preferences.SemPreferences(formsemestre.id)
|
||||
|
||||
def etud_ue_mod_results(self, etud, ue, modimpls) -> dict:
|
||||
"dict synthèse résultats dans l'UE pour les modules indiqués"
|
||||
@ -84,7 +86,7 @@ class BulletinBUT:
|
||||
"saes": self.etud_ue_mod_results(etud, ue, res.saes),
|
||||
}
|
||||
if ue.type != UE_SPORT:
|
||||
if sco_preferences.get_preference("bul_show_ue_rangs", res.formsemestre.id):
|
||||
if self.prefs["bul_show_ue_rangs"]:
|
||||
rangs, effectif = res.ue_rangs[ue.id]
|
||||
rang = rangs[etud.id]
|
||||
else:
|
||||
@ -155,9 +157,7 @@ class BulletinBUT:
|
||||
if e.visibulletin
|
||||
and (
|
||||
modimpl_results.evaluations_etat[e.id].is_complete
|
||||
or sco_preferences.get_preference(
|
||||
"bul_show_all_evals", res.formsemestre.id
|
||||
)
|
||||
or self.prefs["bul_show_all_evals"]
|
||||
)
|
||||
],
|
||||
}
|
||||
@ -216,9 +216,11 @@ class BulletinBUT:
|
||||
else:
|
||||
return f"Bonus de {fmt_note(bonus_vect.iloc[0])}"
|
||||
|
||||
def bulletin_etud(self, etud, formsemestre, force_publishing=False) -> dict:
|
||||
"""Le bulletin de l'étudiant dans ce semestre.
|
||||
Si force_publishing, rempli le bulletin même si bul_hide_xml est vrai
|
||||
def bulletin_etud(
|
||||
self, etud: Identite, formsemestre, force_publishing=False
|
||||
) -> dict:
|
||||
"""Le bulletin de l'étudiant dans ce semestre: dict pour la version JSON / HTML.
|
||||
- Si force_publishing, rempli le bulletin même si bul_hide_xml est vrai
|
||||
(bulletins non publiés).
|
||||
"""
|
||||
res = self.res
|
||||
@ -239,7 +241,9 @@ class BulletinBUT:
|
||||
},
|
||||
"formsemestre_id": formsemestre.id,
|
||||
"etat_inscription": etat_inscription,
|
||||
"options": sco_preferences.bulletin_option_affichage(formsemestre.id),
|
||||
"options": sco_preferences.bulletin_option_affichage(
|
||||
formsemestre.id, self.prefs
|
||||
),
|
||||
}
|
||||
if not published:
|
||||
return d
|
||||
@ -254,7 +258,7 @@ class BulletinBUT:
|
||||
"inscription": "", # inutilisé mais nécessaire pour le js de Seb.
|
||||
"groupes": [], # XXX TODO
|
||||
"absences": {
|
||||
"injustifie": nbabsjust,
|
||||
"injustifie": nbabs - nbabsjust,
|
||||
"total": nbabs,
|
||||
},
|
||||
}
|
||||
@ -312,3 +316,12 @@ class BulletinBUT:
|
||||
)
|
||||
|
||||
return d
|
||||
|
||||
def bulletin_etud_complet(self, etud) -> dict:
|
||||
"""Bulletin dict complet avec toutes les infos pour les bulletins pdf"""
|
||||
d = self.bulletin_etud(force_publishing=True)
|
||||
d["filigranne"] = sco_bulletins_pdf.get_filigranne(
|
||||
self.res.get_etud_etat(etud.id), self.prefs
|
||||
)
|
||||
# XXX TODO A COMPLETER
|
||||
raise NotImplementedError()
|
||||
|
@ -145,7 +145,7 @@ def bulletin_but_xml_compat(
|
||||
doc.append(Element("note_max", value="20")) # notes toujours sur 20
|
||||
doc.append(Element("bonus_sport_culture", value=str(bonus)))
|
||||
# Liste les UE / modules /evals
|
||||
for ue in results.ues:
|
||||
for ue in results.ues: # avec bonus
|
||||
rang_ue = 0 # XXX TODO rang de l'étudiant dans cette UE
|
||||
nb_inscrits_ue = (
|
||||
nb_inscrits # approx: compliqué de définir le "nb d'inscrit à une UE"
|
||||
@ -161,25 +161,31 @@ def bulletin_but_xml_compat(
|
||||
doc.append(x_ue)
|
||||
if ue.type != sco_codes_parcours.UE_SPORT:
|
||||
v = results.etud_moy_ue[ue.id][etud.id]
|
||||
vmin = results.etud_moy_ue[ue.id].min()
|
||||
vmax = results.etud_moy_ue[ue.id].max()
|
||||
else:
|
||||
v = 0 # XXX TODO valeur bonus sport pour cet étudiant
|
||||
v = results.bonus or 0.0
|
||||
vmin = vmax = 0.0
|
||||
x_ue.append(
|
||||
Element(
|
||||
"note",
|
||||
value=scu.fmt_note(v),
|
||||
min=scu.fmt_note(results.etud_moy_ue[ue.id].min()),
|
||||
max=scu.fmt_note(results.etud_moy_ue[ue.id].max()),
|
||||
min=scu.fmt_note(vmin),
|
||||
max=scu.fmt_note(vmax),
|
||||
)
|
||||
)
|
||||
x_ue.append(Element("ects", value=str(ue.ects if ue.ects else 0)))
|
||||
x_ue.append(Element("rang", value=str(rang_ue)))
|
||||
x_ue.append(Element("effectif", value=str(nb_inscrits_ue)))
|
||||
# Liste les modules rattachés à cette UE
|
||||
for modimpl in results.modimpls:
|
||||
for modimpl in results.formsemestre.modimpls:
|
||||
# Liste ici uniquement les modules rattachés à cette UE
|
||||
if modimpl.module.ue.id == ue.id:
|
||||
# mod_moy = scu.fmt_note(results.etud_moy_ue[ue.id][etud.id])
|
||||
try:
|
||||
coef = results.modimpl_coefs_df[modimpl.id][ue.id]
|
||||
except KeyError:
|
||||
coef = 0.0
|
||||
x_mod = Element(
|
||||
"module",
|
||||
id=str(modimpl.id),
|
||||
@ -214,6 +220,7 @@ def bulletin_but_xml_compat(
|
||||
note_max_origin=str(e.note_max),
|
||||
)
|
||||
x_mod.append(x_eval)
|
||||
try:
|
||||
x_eval.append(
|
||||
Element(
|
||||
"note",
|
||||
@ -225,6 +232,11 @@ def bulletin_but_xml_compat(
|
||||
),
|
||||
)
|
||||
)
|
||||
except KeyError:
|
||||
x_eval.append(
|
||||
Element("note", value="", note_max="")
|
||||
)
|
||||
|
||||
# XXX TODO: Evaluations incomplètes ou futures: XXX
|
||||
# XXX TODO UE capitalisee (listee seulement si meilleure que l'UE courante)
|
||||
|
||||
|
@ -19,10 +19,12 @@ class FormationRefCompForm(FlaskForm):
|
||||
|
||||
|
||||
class RefCompLoadForm(FlaskForm):
|
||||
referentiel_standard = SelectField(
|
||||
"Choisir un référentiel de compétences officiel BUT"
|
||||
)
|
||||
upload = FileField(
|
||||
label="Sélectionner un fichier XML Orébut",
|
||||
label="Ou bien sélectionner un fichier XML au format Orébut",
|
||||
validators=[
|
||||
FileRequired(),
|
||||
FileAllowed(
|
||||
[
|
||||
"xml",
|
||||
@ -33,3 +35,13 @@ class RefCompLoadForm(FlaskForm):
|
||||
)
|
||||
submit = SubmitField("Valider")
|
||||
cancel = SubmitField("Annuler")
|
||||
|
||||
def validate(self):
|
||||
if not super().validate():
|
||||
return False
|
||||
if (self.referentiel_standard.data == "0") == (not self.upload.data):
|
||||
self.referentiel_standard.errors.append(
|
||||
"Choisir soit un référentiel, soit un fichier xml"
|
||||
)
|
||||
return False
|
||||
return True
|
||||
|
@ -6,6 +6,8 @@
|
||||
from xml.etree import ElementTree
|
||||
from typing import TextIO
|
||||
|
||||
import sqlalchemy
|
||||
|
||||
from app import db
|
||||
|
||||
from app.models.but_refcomp import (
|
||||
@ -19,7 +21,7 @@ from app.models.but_refcomp import (
|
||||
ApcAnneeParcours,
|
||||
ApcParcoursNiveauCompetence,
|
||||
)
|
||||
from app.scodoc.sco_exceptions import ScoFormatError
|
||||
from app.scodoc.sco_exceptions import ScoFormatError, ScoValueError
|
||||
|
||||
|
||||
def orebut_import_refcomp(xml_data: str, dept_id: int, orig_filename=None):
|
||||
@ -27,6 +29,16 @@ def orebut_import_refcomp(xml_data: str, dept_id: int, orig_filename=None):
|
||||
peut lever TypeError ou ScoFormatError
|
||||
Résultat: instance de ApcReferentielCompetences
|
||||
"""
|
||||
# Vérifie que le même fichier n'a pas déjà été chargé:
|
||||
if ApcReferentielCompetences.query.filter_by(
|
||||
scodoc_orig_filename=orig_filename, dept_id=dept_id
|
||||
).count():
|
||||
raise ScoValueError(
|
||||
f"""Un référentiel a déjà été chargé d'un fichier de même nom.
|
||||
({orig_filename})
|
||||
Supprimez-le ou changer le nom du fichier."""
|
||||
)
|
||||
|
||||
try:
|
||||
root = ElementTree.XML(xml_data)
|
||||
except ElementTree.ParseError as exc:
|
||||
@ -42,7 +54,16 @@ def orebut_import_refcomp(xml_data: str, dept_id: int, orig_filename=None):
|
||||
if not competences:
|
||||
raise ScoFormatError("élément 'competences' manquant")
|
||||
for competence in competences.findall("competence"):
|
||||
try:
|
||||
c = ApcCompetence(**ApcCompetence.attr_from_xml(competence.attrib))
|
||||
db.session.flush()
|
||||
except sqlalchemy.exc.IntegrityError:
|
||||
# ne devrait plus se produire car pas d'unicité de l'id: donc inutile
|
||||
db.session.rollback()
|
||||
raise ScoValueError(
|
||||
f"""Un référentiel a déjà été chargé avec les mêmes compétences ! ({competence.attrib["id"]})
|
||||
"""
|
||||
)
|
||||
ref.competences.append(c)
|
||||
# --- SITUATIONS
|
||||
situations = competence.find("situations")
|
||||
|
@ -21,7 +21,8 @@ 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 or len(vals) == 0:
|
||||
try:
|
||||
if vals is None or len(vals) == 0 or np.isnan(vals).all():
|
||||
self.moy = self.min = self.max = self.size = self.nb_vals = 0
|
||||
else:
|
||||
self.moy = np.nanmean(vals)
|
||||
@ -29,6 +30,8 @@ class StatsMoyenne:
|
||||
self.max = np.nanmax(vals)
|
||||
self.size = len(vals)
|
||||
self.nb_vals = self.size - np.count_nonzero(np.isnan(vals))
|
||||
except TypeError: # que des NaN dans un array d'objets, ou ce genre de choses exotiques...
|
||||
self.moy = self.min = self.max = self.size = self.nb_vals = 0
|
||||
|
||||
def to_dict(self):
|
||||
"Tous les attributs dans un dict"
|
||||
|
@ -87,6 +87,8 @@ class BonusSport:
|
||||
for m in formsemestre.modimpls_sorted
|
||||
]
|
||||
)
|
||||
if not len(modimpl_mask):
|
||||
modimpl_mask = np.s_[:] # il n'y a rien, on prend tout donc rien
|
||||
self.modimpls_spo = [
|
||||
modimpl
|
||||
for i, modimpl in enumerate(formsemestre.modimpls_sorted)
|
||||
@ -134,6 +136,9 @@ class BonusSport:
|
||||
modimpl_inscr_spo, sem_modimpl_moys_no_nan, 0.0
|
||||
)
|
||||
modimpl_coefs_spo = modimpl_coefs_spo.T
|
||||
if nb_etuds == 0:
|
||||
modimpl_coefs_etuds = modimpl_inscr_spo # vide
|
||||
else:
|
||||
modimpl_coefs_etuds = np.where(
|
||||
modimpl_inscr_spo, np.stack([modimpl_coefs_spo] * nb_etuds), 0.0
|
||||
)
|
||||
@ -198,6 +203,9 @@ class BonusSportAdditif(BonusSport):
|
||||
En APC: ndarray (nb_etuds, nb_mod_sport, nb_ues_non_bonus)
|
||||
modimpl_coefs_etuds_no_nan:
|
||||
"""
|
||||
if 0 in sem_modimpl_moys_inscrits.shape:
|
||||
# pas d'étudiants ou pas d'UE ou pas de module...
|
||||
return
|
||||
bonus_moy_arr = np.sum(
|
||||
np.where(
|
||||
sem_modimpl_moys_inscrits > self.seuil_moy_gen,
|
||||
@ -249,6 +257,9 @@ class BonusSportMultiplicatif(BonusSport):
|
||||
# bonus = m_0 (a - 1)
|
||||
def compute_bonus(self, sem_modimpl_moys_inscrits, modimpl_coefs_etuds_no_nan):
|
||||
"""calcul du bonus"""
|
||||
if 0 in sem_modimpl_moys_inscrits.shape:
|
||||
# pas d'étudiants ou pas d'UE ou pas de module...
|
||||
return
|
||||
# Calcule moyenne pondérée des notes de sport:
|
||||
notes = np.sum(
|
||||
sem_modimpl_moys_inscrits * modimpl_coefs_etuds_no_nan, axis=1
|
||||
|
@ -48,14 +48,15 @@ def compute_sem_moys_apc(
|
||||
return moy_gen
|
||||
|
||||
|
||||
def comp_ranks_series(notes: pd.Series) -> dict[int, str]:
|
||||
def comp_ranks_series(notes: pd.Series) -> (pd.Series, pd.Series):
|
||||
"""Calcul rangs à partir d'une séries ("vecteur") de notes (index etudid, valeur
|
||||
numérique) en tenant compte des ex-aequos.
|
||||
|
||||
Result: { etudid : rang:str } où rang est une chaine decrivant le rang.
|
||||
Result: Series { etudid : rang:str } où rang est une chaine decrivant le rang.
|
||||
"""
|
||||
notes = notes.sort_values(ascending=False) # Serie, tri par ordre décroissant
|
||||
rangs = pd.Series(index=notes.index, dtype=str) # le rang est une chaîne
|
||||
rangs_str = pd.Series(index=notes.index, dtype=str) # le rang est une chaîne
|
||||
rangs_int = pd.Series(index=notes.index, dtype=int) # le rang numérique pour tris
|
||||
N = len(notes)
|
||||
nb_ex = 0 # nb d'ex-aequo consécutifs en cours
|
||||
notes_i = notes.iat
|
||||
@ -67,6 +68,7 @@ def comp_ranks_series(notes: pd.Series) -> dict[int, str]:
|
||||
next = None
|
||||
val = notes_i[i]
|
||||
if nb_ex:
|
||||
rangs_int[etudid] = i + 1 - nb_ex
|
||||
srang = "%d ex" % (i + 1 - nb_ex)
|
||||
if val == next:
|
||||
nb_ex += 1
|
||||
@ -74,9 +76,11 @@ def comp_ranks_series(notes: pd.Series) -> dict[int, str]:
|
||||
nb_ex = 0
|
||||
else:
|
||||
if val == next:
|
||||
rangs_int[etudid] = i + 1 - nb_ex
|
||||
srang = "%d ex" % (i + 1 - nb_ex)
|
||||
nb_ex = 1
|
||||
else:
|
||||
rangs_int[etudid] = i + 1
|
||||
srang = "%d" % (i + 1)
|
||||
rangs[etudid] = srang
|
||||
return rangs
|
||||
rangs_str[etudid] = srang
|
||||
return rangs_str, rangs_int
|
||||
|
@ -136,8 +136,13 @@ def df_load_modimpl_coefs(
|
||||
)
|
||||
|
||||
for mod_coef in mod_coefs:
|
||||
modimpl_coefs_df[mod2impl[mod_coef.module_id]][mod_coef.ue_id] = mod_coef.coef
|
||||
|
||||
try:
|
||||
modimpl_coefs_df[mod2impl[mod_coef.module_id]][
|
||||
mod_coef.ue_id
|
||||
] = mod_coef.coef
|
||||
except IndexError:
|
||||
# il peut y avoir en base des coefs sur des modules ou UE qui ont depuis été retirés de la formation
|
||||
pass
|
||||
# Initialisation des poids non fixés:
|
||||
# 0 pour modules normaux, 1. pour bonus (car par défaut, on veut qu'un bonus agisse
|
||||
# sur toutes les UE)
|
||||
@ -229,12 +234,12 @@ def compute_ue_moys_apc(
|
||||
modimpl_inscr_df: matrice d'inscription du semestre (etud x modimpl)
|
||||
modimpl_coefs_df: matrice coefficients (UE x modimpl), sans UEs bonus sport
|
||||
|
||||
Résultat: DataFrame columns UE (sans sport), rows etudid
|
||||
Résultat: DataFrame columns UE (sans bonus), rows etudid
|
||||
"""
|
||||
nb_etuds, nb_modules, nb_ues_no_bonus = sem_cube.shape
|
||||
nb_ues_tot = len(ues)
|
||||
assert len(modimpls) == nb_modules
|
||||
if nb_modules == 0 or nb_etuds == 0:
|
||||
if nb_modules == 0 or nb_etuds == 0 or nb_ues_no_bonus == 0:
|
||||
return pd.DataFrame(
|
||||
index=modimpl_inscr_df.index, columns=modimpl_coefs_df.index
|
||||
)
|
||||
@ -310,6 +315,17 @@ def compute_ue_moys_classic(
|
||||
les coefficients effectifs de chaque UE pour chaque étudiant
|
||||
(sommes de coefs de modules pris en compte)
|
||||
"""
|
||||
if (not len(modimpl_mask)) or (
|
||||
sem_matrix.shape[0] == 0
|
||||
): # aucun module ou aucun étudiant
|
||||
# etud_moy_gen_s, etud_moy_ue_df, etud_coef_ue_df
|
||||
return (
|
||||
pd.Series(
|
||||
[0.0] * len(modimpl_inscr_df.index), index=modimpl_inscr_df.index
|
||||
),
|
||||
pd.DataFrame(columns=[ue.id for ue in ues], index=modimpl_inscr_df.index),
|
||||
pd.DataFrame(columns=[ue.id for ue in ues], index=modimpl_inscr_df.index),
|
||||
)
|
||||
# Restreint aux modules sélectionnés:
|
||||
sem_matrix = sem_matrix[:, modimpl_mask]
|
||||
modimpl_inscr = modimpl_inscr_df.values[:, modimpl_mask]
|
||||
@ -391,8 +407,9 @@ def compute_malus(
|
||||
) -> pd.DataFrame:
|
||||
"""Calcul le malus sur les UE
|
||||
Dans chaque UE, on peut avoir un ou plusieurs modules de MALUS.
|
||||
Leurs notes sont positives ou négatives. leur somme sera _soustraite_ à la moyenne
|
||||
de chaque UE.
|
||||
Leurs notes sont positives ou négatives.
|
||||
La somme des notes de malus somme est _soustraite_ à la moyenne de chaque UE.
|
||||
|
||||
Arguments:
|
||||
- sem_modimpl_moys :
|
||||
notes moyennes aux modules (tous les étuds x tous les modimpls)
|
||||
@ -415,6 +432,7 @@ def compute_malus(
|
||||
for m in formsemestre.modimpls_sorted
|
||||
]
|
||||
)
|
||||
if len(modimpl_mask):
|
||||
malus_moys = sem_modimpl_moys[:, modimpl_mask].sum(axis=1)
|
||||
malus[ue.id] = malus_moys
|
||||
|
||||
|
@ -6,9 +6,11 @@
|
||||
|
||||
"""Résultats semestres BUT
|
||||
"""
|
||||
import time
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
|
||||
from app import log
|
||||
from app.comp import moy_ue, moy_sem, inscr_mod
|
||||
from app.comp.res_common import NotesTableCompat
|
||||
from app.comp.bonus_spo import BonusSport
|
||||
@ -30,8 +32,14 @@ class ResultatsSemestreBUT(NotesTableCompat):
|
||||
super().__init__(formsemestre)
|
||||
|
||||
if not self.load_cached():
|
||||
t0 = time.time()
|
||||
self.compute()
|
||||
t1 = time.time()
|
||||
self.store()
|
||||
t2 = time.time()
|
||||
log(
|
||||
f"ResultatsSemestreBUT: cached formsemestre_id={formsemestre.id} ({(t1-t0):g}s +{(t2-t1):g}s)"
|
||||
)
|
||||
|
||||
def compute(self):
|
||||
"Charge les notes et inscriptions et calcule les moyennes d'UE et gen."
|
||||
|
@ -6,7 +6,7 @@
|
||||
|
||||
"""Résultats semestres classiques (non APC)
|
||||
"""
|
||||
|
||||
import time
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
from sqlalchemy.sql import text
|
||||
@ -40,8 +40,14 @@ class ResultatsSemestreClassic(NotesTableCompat):
|
||||
super().__init__(formsemestre)
|
||||
|
||||
if not self.load_cached():
|
||||
t0 = time.time()
|
||||
self.compute()
|
||||
t1 = time.time()
|
||||
self.store()
|
||||
t2 = time.time()
|
||||
log(
|
||||
f"ResultatsSemestreClassic: cached formsemestre_id={formsemestre.id} ({(t1-t0):g}s +{(t2-t1):g}s)"
|
||||
)
|
||||
# recalculé (aussi rapide que de les cacher)
|
||||
self.moy_min = self.etud_moy_gen.min()
|
||||
self.moy_max = self.etud_moy_gen.max()
|
||||
@ -110,9 +116,8 @@ class ResultatsSemestreClassic(NotesTableCompat):
|
||||
if bonus_mg is not None:
|
||||
self.etud_moy_gen += bonus_mg
|
||||
self.etud_moy_gen.clip(lower=0.0, upper=20.0, inplace=True)
|
||||
self.bonus = (
|
||||
bonus_mg # compat nt, utilisé pour l'afficher sur les bulletins
|
||||
)
|
||||
# compat nt, utilisé pour l'afficher sur les bulletins:
|
||||
self.bonus = bonus_mg
|
||||
# --- UE capitalisées
|
||||
self.apply_capitalisation()
|
||||
|
||||
@ -209,6 +214,8 @@ def notes_sem_assemble_matrix(modimpls_notes: list[pd.Series]) -> np.ndarray:
|
||||
(Series rendus par compute_module_moy, index: etud)
|
||||
Resultat: ndarray (etud x module)
|
||||
"""
|
||||
if not len(modimpls_notes):
|
||||
return np.zeros((0, 0), dtype=float)
|
||||
modimpls_notes_arr = [s.values for s in modimpls_notes]
|
||||
modimpls_notes = np.stack(modimpls_notes_arr)
|
||||
# passe de (mod x etud) à (etud x mod)
|
||||
|
@ -4,11 +4,12 @@
|
||||
# See LICENSE
|
||||
##############################################################################
|
||||
|
||||
from collections import defaultdict, Counter
|
||||
from collections import Counter
|
||||
from functools import cached_property
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
|
||||
from app import log
|
||||
from app.comp.aux_stats import StatsMoyenne
|
||||
from app.comp import moy_sem
|
||||
from app.comp.res_cache import ResultatsCache
|
||||
@ -19,8 +20,7 @@ from app.models import FormSemestreUECoef
|
||||
from app.models.ues import UniteEns
|
||||
from app.scodoc import sco_utils as scu
|
||||
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
|
||||
from app.scodoc.sco_codes_parcours import UE_SPORT, DEF
|
||||
|
||||
# Il faut bien distinguer
|
||||
# - ce qui est caché de façon persistente (via redis):
|
||||
@ -51,6 +51,7 @@ class ResultatsSemestre(ResultatsCache):
|
||||
"etud_moy_ue: DataFrame columns UE, rows etudid"
|
||||
self.etud_moy_gen = {}
|
||||
self.etud_moy_gen_ranks = {}
|
||||
self.etud_moy_gen_ranks_int = {}
|
||||
self.modimpls_results: ModuleImplResults = None
|
||||
"Résultats de chaque modimpl: dict { modimpl.id : ModuleImplResults(Classique ou BUT) }"
|
||||
self.etud_coef_ue_df = None
|
||||
@ -183,7 +184,7 @@ class ResultatsSemestre(ResultatsCache):
|
||||
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"]:
|
||||
if ue_cap and ue_cap["is_capitalized"]:
|
||||
recompute_mg = True
|
||||
coef = ue_cap["coef_ue"]
|
||||
if not np.isnan(ue_cap["moy"]):
|
||||
@ -213,15 +214,13 @@ class ResultatsSemestre(ResultatsCache):
|
||||
|
||||
def get_etud_ue_status(self, etudid: int, ue_id: int) -> dict:
|
||||
"""L'état de l'UE pour cet étudiant.
|
||||
L'UE doit être du semestre.
|
||||
Result: dict.
|
||||
Result: dict, ou None si l'UE n'est pas dans ce semestre.
|
||||
"""
|
||||
if not self.validations:
|
||||
self.validations = res_sem.load_formsemestre_validations(self.formsemestre)
|
||||
ue = UniteEns.query.get(ue_id) # TODO cacher nos UEs ?
|
||||
if ue.type == UE_SPORT:
|
||||
return {
|
||||
"is_capitalized": False,
|
||||
"was_capitalized": False,
|
||||
"is_external": False,
|
||||
"coef_ue": 0.0,
|
||||
"cur_moy_ue": 0.0,
|
||||
@ -232,9 +231,16 @@ class ResultatsSemestre(ResultatsCache):
|
||||
"capitalized_ue_id": None,
|
||||
"ects_pot": 0.0,
|
||||
}
|
||||
if not ue_id in self.etud_moy_ue:
|
||||
return None
|
||||
if not self.validations:
|
||||
self.validations = res_sem.load_formsemestre_validations(self.formsemestre)
|
||||
cur_moy_ue = self.etud_moy_ue[ue_id][etudid]
|
||||
moy_ue = cur_moy_ue
|
||||
is_capitalized = False
|
||||
is_capitalized = False # si l'UE prise en compte est une UE capitalisée
|
||||
was_capitalized = (
|
||||
False # s'il y a precedemment une UE capitalisée (pas forcement meilleure)
|
||||
)
|
||||
if etudid in self.validations.ue_capitalisees.index:
|
||||
ue_cap = self._get_etud_ue_cap(etudid, ue)
|
||||
if (
|
||||
@ -242,6 +248,7 @@ class ResultatsSemestre(ResultatsCache):
|
||||
and not ue_cap.empty
|
||||
and not np.isnan(ue_cap["moy_ue"])
|
||||
):
|
||||
was_capitalized = True
|
||||
if ue_cap["moy_ue"] > cur_moy_ue or np.isnan(cur_moy_ue):
|
||||
moy_ue = ue_cap["moy_ue"]
|
||||
is_capitalized = True
|
||||
@ -250,6 +257,7 @@ class ResultatsSemestre(ResultatsCache):
|
||||
|
||||
return {
|
||||
"is_capitalized": is_capitalized,
|
||||
"was_capitalized": was_capitalized,
|
||||
"is_external": ue_cap["is_external"] if is_capitalized else ue.is_external,
|
||||
"coef_ue": coef_ue,
|
||||
"ects_pot": ue.ects or 0.0,
|
||||
@ -301,6 +309,7 @@ class NotesTableCompat(ResultatsSemestre):
|
||||
"bonus_ues",
|
||||
"malus",
|
||||
"etud_moy_gen_ranks",
|
||||
"etud_moy_gen_ranks_int",
|
||||
"ue_rangs",
|
||||
)
|
||||
|
||||
@ -311,41 +320,54 @@ class NotesTableCompat(ResultatsSemestre):
|
||||
self.bonus = None # virtuel
|
||||
self.bonus_ues = None # virtuel
|
||||
self.ue_rangs = {u.id: (None, nb_etuds) for u in self.ues}
|
||||
self.mod_rangs = {
|
||||
m.id: (None, nb_etuds) for m in self.formsemestre.modimpls_sorted
|
||||
}
|
||||
self.mod_rangs = None # sera surchargé en Classic, mais pas en APC
|
||||
self.moy_min = "NA"
|
||||
self.moy_max = "NA"
|
||||
self.moy_moy = "NA"
|
||||
self.expr_diagnostics = ""
|
||||
self.parcours = self.formsemestre.formation.get_parcours()
|
||||
|
||||
def get_etudids(self, sorted=False) -> list[int]:
|
||||
"""Liste des etudids inscrits, incluant les démissionnaires.
|
||||
Si sorted, triée par moy. générale décroissante
|
||||
Sinon, triée par ordre alphabetique de NOM
|
||||
def get_inscrits(self, include_demdef=True, order_by=False) -> list[Identite]:
|
||||
"""Liste des étudiants inscrits
|
||||
order_by = False|'nom'|'moy' tri sur nom ou sur moyenne générale (indicative)
|
||||
|
||||
Note: pour récupérer les etudids des inscrits, non triés, il est plus efficace
|
||||
d'utiliser `[ ins.etudid for ins in nt.formsemestre.inscriptions ]`
|
||||
"""
|
||||
etuds = self.formsemestre.get_inscrits(
|
||||
include_demdef=include_demdef, order=(order_by == "nom")
|
||||
)
|
||||
if order_by == "moy":
|
||||
etuds.sort(
|
||||
key=lambda e: (
|
||||
self.etud_moy_gen_ranks_int.get(e.id, 100000),
|
||||
e.sort_key,
|
||||
)
|
||||
)
|
||||
return etuds
|
||||
|
||||
def get_etudids(self) -> list[int]:
|
||||
"""(deprecated)
|
||||
Liste des etudids inscrits, incluant les démissionnaires.
|
||||
triée par ordre alphabetique de NOM
|
||||
(à éviter: renvoie les etudids, mais est moins efficace que get_inscrits)
|
||||
"""
|
||||
# Note: pour avoir les inscrits non triés,
|
||||
# utiliser [ ins.etudid for ins in self.formsemestre.inscriptions ]
|
||||
if sorted:
|
||||
# Tri par moy. generale décroissante
|
||||
return [x[-1] for x in self.T]
|
||||
|
||||
return [x["etudid"] for x in self.inscrlist]
|
||||
|
||||
@cached_property
|
||||
def sem(self) -> dict:
|
||||
"""le formsemestre, comme un dict (nt.sem)"""
|
||||
return self.formsemestre.to_dict()
|
||||
"""le formsemestre, comme un gros et gras dict (nt.sem)"""
|
||||
return self.formsemestre.get_infos_dict()
|
||||
|
||||
@cached_property
|
||||
def inscrlist(self) -> list[dict]: # utilisé par PE seulement
|
||||
def inscrlist(self) -> list[dict]: # utilisé par PE
|
||||
"""Liste des inscrits au semestre (avec DEM et DEF),
|
||||
sous forme de dict etud,
|
||||
classée dans l'ordre alphabétique de noms.
|
||||
"""
|
||||
etuds = self.formsemestre.get_inscrits(include_demdef=True)
|
||||
etuds.sort(key=lambda e: e.sort_key)
|
||||
etuds = self.formsemestre.get_inscrits(include_demdef=True, order=True)
|
||||
return [e.to_dict_scodoc7() for e in etuds]
|
||||
|
||||
@cached_property
|
||||
@ -387,11 +409,14 @@ class NotesTableCompat(ResultatsSemestre):
|
||||
Moyenne générale: etud_moy_gen_ranks
|
||||
Par UE (sauf ue bonus)
|
||||
"""
|
||||
self.etud_moy_gen_ranks = moy_sem.comp_ranks_series(self.etud_moy_gen)
|
||||
(
|
||||
self.etud_moy_gen_ranks,
|
||||
self.etud_moy_gen_ranks_int,
|
||||
) = 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),
|
||||
moy_sem.comp_ranks_series(moy_ue)[0], # juste en chaine
|
||||
int(moy_ue.count()),
|
||||
)
|
||||
# .count() -> nb of non NaN values
|
||||
@ -420,12 +445,27 @@ class NotesTableCompat(ResultatsSemestre):
|
||||
|
||||
Return: True|False, message explicatif
|
||||
"""
|
||||
return self.parcours.check_barre_ues(
|
||||
[
|
||||
self.get_etud_ue_status(etudid, ue.id)
|
||||
for ue in self.formsemestre.query_ues()
|
||||
]
|
||||
)
|
||||
ue_status_list = []
|
||||
for ue in self.formsemestre.query_ues():
|
||||
ue_status = self.get_etud_ue_status(etudid, ue.id)
|
||||
if ue_status:
|
||||
ue_status_list.append(ue_status)
|
||||
return self.parcours.check_barre_ues(ue_status_list)
|
||||
|
||||
def all_etuds_have_sem_decisions(self):
|
||||
"""True si tous les étudiants du semestre ont une décision de jury.
|
||||
Ne regarde pas les décisions d'UE.
|
||||
"""
|
||||
for ins in self.formsemestre.inscriptions:
|
||||
if ins.etat != scu.INSCRIT:
|
||||
continue # skip démissionnaires
|
||||
if self.get_etud_decision_sem(ins.etudid) is None:
|
||||
return False
|
||||
return True
|
||||
|
||||
def etud_has_decision(self, etudid):
|
||||
"""True s'il y a une décision de jury pour cet étudiant"""
|
||||
return self.get_etud_decision_ues(etudid) or self.get_etud_decision_sem(etudid)
|
||||
|
||||
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.
|
||||
@ -525,25 +565,32 @@ class NotesTableCompat(ResultatsSemestre):
|
||||
Évaluation "complete" ssi toutes notes saisies ou en attente.
|
||||
"""
|
||||
modimpl = ModuleImpl.query.get(moduleimpl_id)
|
||||
modimpl_results = self.modimpls_results.get(moduleimpl_id)
|
||||
if not modimpl_results:
|
||||
return [] # safeguard
|
||||
evals_results = []
|
||||
for e in modimpl.evaluations:
|
||||
if self.modimpls_results[moduleimpl_id].evaluations_completes_dict[e.id]:
|
||||
if modimpl_results.evaluations_completes_dict.get(e.id, False):
|
||||
d = e.to_dict()
|
||||
moduleimpl_results = self.modimpls_results[e.moduleimpl_id]
|
||||
d["heure_debut"] = e.heure_debut # datetime.time
|
||||
d["heure_fin"] = e.heure_fin
|
||||
d["jour"] = e.jour # datetime
|
||||
d["notes"] = {
|
||||
etud.id: {
|
||||
"etudid": etud.id,
|
||||
"value": moduleimpl_results.evals_notes[e.id][etud.id],
|
||||
"value": modimpl_results.evals_notes[e.id][etud.id],
|
||||
}
|
||||
for etud in self.etuds
|
||||
}
|
||||
d["etat"] = {
|
||||
"evalattente": moduleimpl_results.evaluations_etat[e.id].nb_attente,
|
||||
"evalattente": modimpl_results.evaluations_etat[e.id].nb_attente,
|
||||
}
|
||||
evals_results.append(d)
|
||||
elif e.id not in modimpl_results.evaluations_completes_dict:
|
||||
# ne devrait pas arriver ? XXX
|
||||
log(
|
||||
f"Warning: 220213 get_evals_in_mod {e.id} not in mod {moduleimpl_id} ?"
|
||||
)
|
||||
return evals_results
|
||||
|
||||
def get_evaluations_etats(self):
|
||||
@ -608,7 +655,7 @@ class NotesTableCompat(ResultatsSemestre):
|
||||
"""
|
||||
table_moyennes = []
|
||||
etuds_inscriptions = self.formsemestre.etuds_inscriptions
|
||||
ues = self.formsemestre.query_ues() # sans bonus
|
||||
ues = self.formsemestre.query_ues(with_sport=True) # avec bonus
|
||||
for etudid in etuds_inscriptions:
|
||||
moy_gen = self.etud_moy_gen.get(etudid, False)
|
||||
if moy_gen is False:
|
||||
@ -623,8 +670,11 @@ class NotesTableCompat(ResultatsSemestre):
|
||||
ue_is_cap = {}
|
||||
for ue in ues:
|
||||
ue_status = self.get_etud_ue_status(etudid, ue.id)
|
||||
if ue_status:
|
||||
moy_ues.append(ue_status["moy"])
|
||||
ue_is_cap[ue.id] = ue_status["is_capitalized"]
|
||||
else:
|
||||
moy_ues.append("?")
|
||||
t = [moy_gen] + list(moy_ues)
|
||||
# Moyennes modules:
|
||||
for modimpl in self.formsemestre.modimpls_sorted:
|
||||
|
@ -25,7 +25,7 @@ def load_formsemestre_results(formsemestre: FormSemestre) -> ResultatsSemestre:
|
||||
"""
|
||||
# --- Try local cache (within the same request context)
|
||||
if not hasattr(g, "formsemestre_results_cache"):
|
||||
g.formsemestre_results_cache = {} # pylint: disable=C0237
|
||||
g.formsemestre_results_cache = {}
|
||||
else:
|
||||
if formsemestre.id in g.formsemestre_results_cache:
|
||||
return g.formsemestre_results_cache[formsemestre.id]
|
||||
|
@ -81,6 +81,9 @@ class ApcReferentielCompetences(db.Model, XMLModel):
|
||||
)
|
||||
formations = db.relationship("Formation", backref="referentiel_competence")
|
||||
|
||||
def __repr__(self):
|
||||
return f"<ApcReferentielCompetences {self.id} {self.specialite}>"
|
||||
|
||||
def to_dict(self):
|
||||
"""Représentation complète du ref. de comp.
|
||||
comme un dict.
|
||||
@ -110,7 +113,8 @@ class ApcCompetence(db.Model, XMLModel):
|
||||
db.Integer, db.ForeignKey("apc_referentiel_competences.id"), nullable=False
|
||||
)
|
||||
# les compétences dans Orébut sont identifiées par leur id unique
|
||||
id_orebut = db.Column(db.Text(), nullable=True, index=True, unique=True)
|
||||
# (mais id_orebut n'est pas unique car le même ref. pourra être chargé dans plusieurs depts)
|
||||
id_orebut = db.Column(db.Text(), nullable=True, index=True)
|
||||
titre = db.Column(db.Text(), nullable=False, index=True)
|
||||
titre_long = db.Column(db.Text())
|
||||
couleur = db.Column(db.Text())
|
||||
@ -139,6 +143,9 @@ class ApcCompetence(db.Model, XMLModel):
|
||||
cascade="all, delete-orphan",
|
||||
)
|
||||
|
||||
def __repr__(self):
|
||||
return f"<ApcCompetence {self.id} {self.titre}>"
|
||||
|
||||
def to_dict(self):
|
||||
return {
|
||||
"id_orebut": self.id_orebut,
|
||||
|
@ -7,12 +7,14 @@
|
||||
from functools import cached_property
|
||||
from flask import abort, url_for
|
||||
from flask import g, request
|
||||
import sqlalchemy
|
||||
|
||||
from app import db
|
||||
from app import models
|
||||
|
||||
from app.scodoc import notesdb as ndb
|
||||
from app.scodoc.sco_bac import Baccalaureat
|
||||
import app.scodoc.sco_utils as scu
|
||||
|
||||
|
||||
class Identite(db.Model):
|
||||
@ -73,6 +75,13 @@ class Identite(db.Model):
|
||||
"""
|
||||
return {"M": "M.", "F": "Mme", "X": ""}[self.civilite]
|
||||
|
||||
def sex_nom(self, no_accents=False) -> str:
|
||||
"'M. DUPONTÉ', ou si no_accents, 'M. DUPONTE'"
|
||||
s = f"{self.civilite_str} {(self.nom_usuel or self.nom).upper()}"
|
||||
if no_accents:
|
||||
return scu.suppress_accents(s)
|
||||
return s
|
||||
|
||||
def nom_disp(self) -> str:
|
||||
"Nom à afficher"
|
||||
if self.nom_usuel:
|
||||
@ -125,6 +134,7 @@ class Identite(db.Model):
|
||||
# ScoDoc7 output_formators: (backward compat)
|
||||
e["etudid"] = self.id
|
||||
e["date_naissance"] = ndb.DateISOtoDMY(e["date_naissance"])
|
||||
e["ne"] = {"M": "", "F": "ne"}.get(self.civilite, "(e)")
|
||||
return {k: e[k] or "" for k in e} # convert_null_outputs_to_empty
|
||||
|
||||
def to_dict_bul(self, include_urls=True):
|
||||
@ -302,6 +312,24 @@ class Admission(db.Model):
|
||||
"Le bac. utiliser bac.abbrev() pour avoir une chaine de caractères."
|
||||
return Baccalaureat(self.bac, specialite=self.specialite)
|
||||
|
||||
def to_dict(self, no_nulls=False):
|
||||
"""Représentation dictionnaire,"""
|
||||
e = dict(self.__dict__)
|
||||
e.pop("_sa_instance_state", None)
|
||||
if no_nulls:
|
||||
for k in e:
|
||||
if e[k] is None:
|
||||
col_type = getattr(
|
||||
sqlalchemy.inspect(models.Admission).columns, "apb_groupe"
|
||||
).expression.type
|
||||
if isinstance(col_type, sqlalchemy.Text):
|
||||
e[k] = ""
|
||||
elif isinstance(col_type, sqlalchemy.Integer):
|
||||
e[k] = 0
|
||||
elif isinstance(col_type, sqlalchemy.Boolean):
|
||||
e[k] = False
|
||||
return e
|
||||
|
||||
|
||||
# Suivi scolarité / débouchés
|
||||
class ItemSuivi(db.Model):
|
||||
|
@ -2,12 +2,16 @@
|
||||
|
||||
"""ScoDoc models: evaluations
|
||||
"""
|
||||
import datetime
|
||||
|
||||
from app import db
|
||||
from app.models import UniteEns
|
||||
from app.models import formsemestre
|
||||
from app.models.formsemestre import FormSemestre
|
||||
from app.models.moduleimpls import ModuleImpl
|
||||
from app.models.ues import UniteEns
|
||||
|
||||
from app.scodoc.sco_exceptions import ScoValueError
|
||||
import app.scodoc.notesdb as ndb
|
||||
from app.scodoc import sco_evaluation_db
|
||||
|
||||
|
||||
class Evaluation(db.Model):
|
||||
@ -51,11 +55,11 @@ class Evaluation(db.Model):
|
||||
e["evaluation_id"] = self.id
|
||||
e["jour"] = ndb.DateISOtoDMY(e["jour"])
|
||||
e["numero"] = ndb.int_null_is_zero(e["numero"])
|
||||
return sco_evaluation_db.evaluation_enrich_dict(e)
|
||||
return evaluation_enrich_dict(e)
|
||||
|
||||
def from_dict(self, data):
|
||||
"""Set evaluation attributes from given dict values."""
|
||||
sco_evaluation_db._check_evaluation_args(data)
|
||||
check_evaluation_args(data)
|
||||
for k in self.__dict__.keys():
|
||||
if k != "_sa_instance_state" and k != "id" and k in data:
|
||||
setattr(self, k, data[k])
|
||||
@ -145,3 +149,89 @@ class EvaluationUEPoids(db.Model):
|
||||
|
||||
def __repr__(self):
|
||||
return f"<EvaluationUEPoids {self.evaluation} {self.ue} poids={self.poids}>"
|
||||
|
||||
|
||||
# Fonction héritée de ScoDoc7 à refactorer
|
||||
def evaluation_enrich_dict(e):
|
||||
"""add or convert some fileds in an evaluation dict"""
|
||||
# For ScoDoc7 compat
|
||||
heure_debut_dt = e["heure_debut"] or datetime.time(
|
||||
8, 00
|
||||
) # au cas ou pas d'heure (note externe?)
|
||||
heure_fin_dt = e["heure_fin"] or datetime.time(8, 00)
|
||||
e["heure_debut"] = ndb.TimefromISO8601(e["heure_debut"])
|
||||
e["heure_fin"] = ndb.TimefromISO8601(e["heure_fin"])
|
||||
e["jouriso"] = ndb.DateDMYtoISO(e["jour"])
|
||||
heure_debut, heure_fin = e["heure_debut"], e["heure_fin"]
|
||||
d = ndb.TimeDuration(heure_debut, heure_fin)
|
||||
if d is not None:
|
||||
m = d % 60
|
||||
e["duree"] = "%dh" % (d / 60)
|
||||
if m != 0:
|
||||
e["duree"] += "%02d" % m
|
||||
else:
|
||||
e["duree"] = ""
|
||||
if heure_debut and (not heure_fin or heure_fin == heure_debut):
|
||||
e["descrheure"] = " à " + heure_debut
|
||||
elif heure_debut and heure_fin:
|
||||
e["descrheure"] = " de %s à %s" % (heure_debut, heure_fin)
|
||||
else:
|
||||
e["descrheure"] = ""
|
||||
# matin, apresmidi: utile pour se referer aux absences:
|
||||
if heure_debut_dt < datetime.time(12, 00):
|
||||
e["matin"] = 1
|
||||
else:
|
||||
e["matin"] = 0
|
||||
if heure_fin_dt > datetime.time(12, 00):
|
||||
e["apresmidi"] = 1
|
||||
else:
|
||||
e["apresmidi"] = 0
|
||||
return e
|
||||
|
||||
|
||||
def check_evaluation_args(args):
|
||||
"Check coefficient, dates and duration, raises exception if invalid"
|
||||
moduleimpl_id = args["moduleimpl_id"]
|
||||
# check bareme
|
||||
note_max = args.get("note_max", None)
|
||||
if note_max is None:
|
||||
raise ScoValueError("missing note_max")
|
||||
try:
|
||||
note_max = float(note_max)
|
||||
except ValueError:
|
||||
raise ScoValueError("Invalid note_max value")
|
||||
if note_max < 0:
|
||||
raise ScoValueError("Invalid note_max value (must be positive or null)")
|
||||
# check coefficient
|
||||
coef = args.get("coefficient", None)
|
||||
if coef is None:
|
||||
raise ScoValueError("missing coefficient")
|
||||
try:
|
||||
coef = float(coef)
|
||||
except ValueError:
|
||||
raise ScoValueError("Invalid coefficient value")
|
||||
if coef < 0:
|
||||
raise ScoValueError("Invalid coefficient value (must be positive or null)")
|
||||
# check date
|
||||
jour = args.get("jour", None)
|
||||
args["jour"] = jour
|
||||
if jour:
|
||||
modimpl = ModuleImpl.query.get(moduleimpl_id)
|
||||
formsemestre = modimpl.formsemestre
|
||||
y, m, d = [int(x) for x in ndb.DateDMYtoISO(jour).split("-")]
|
||||
jour = datetime.date(y, m, d)
|
||||
if (jour > formsemestre.date_fin) or (jour < formsemestre.date_debut):
|
||||
raise ScoValueError(
|
||||
"La date de l'évaluation (%s/%s/%s) n'est pas dans le semestre !"
|
||||
% (d, m, y),
|
||||
dest_url="javascript:history.back();",
|
||||
)
|
||||
heure_debut = args.get("heure_debut", None)
|
||||
args["heure_debut"] = heure_debut
|
||||
heure_fin = args.get("heure_fin", None)
|
||||
args["heure_fin"] = heure_fin
|
||||
if jour and ((not heure_debut) or (not heure_fin)):
|
||||
raise ScoValueError("Les heures doivent être précisées")
|
||||
d = ndb.TimeDuration(heure_debut, heure_fin)
|
||||
if d and ((d < 0) or (d > 60 * 12)):
|
||||
raise ScoValueError("Heures de l'évaluation incohérentes !")
|
||||
|
@ -117,10 +117,12 @@ class FormSemestre(db.Model):
|
||||
return f"<{self.__class__.__name__} {self.id} {self.titre_num()}>"
|
||||
|
||||
def to_dict(self):
|
||||
"dict (compatible ScoDoc7)"
|
||||
d = dict(self.__dict__)
|
||||
d.pop("_sa_instance_state", None)
|
||||
# ScoDoc7 output_formators: (backward compat)
|
||||
d["formsemestre_id"] = self.id
|
||||
d["titre_num"] = self.titre_num()
|
||||
if self.date_debut:
|
||||
d["date_debut"] = self.date_debut.strftime("%d/%m/%Y")
|
||||
d["date_debut_iso"] = self.date_debut.isoformat()
|
||||
@ -144,6 +146,8 @@ class FormSemestre(db.Model):
|
||||
d["annee_debut"] = str(self.date_debut.year)
|
||||
d["annee"] = d["annee_debut"]
|
||||
d["annee_fin"] = str(self.date_fin.year)
|
||||
if d["annee_fin"] != d["annee_debut"]:
|
||||
d["annee"] += "-" + str(d["annee_fin"])
|
||||
d["mois_debut_ord"] = self.date_debut.month
|
||||
d["mois_fin_ord"] = self.date_fin.month
|
||||
# La période: considère comme "S1" (ou S3) les débuts en aout-sept-octobre
|
||||
@ -152,15 +156,8 @@ class FormSemestre(db.Model):
|
||||
d["periode"] = 1 # typiquement, début en septembre: S1, S3...
|
||||
else:
|
||||
d["periode"] = 2 # typiquement, début en février: S2, S4...
|
||||
d["titre_num"] = self.titre_num
|
||||
d["titreannee"] = "%s %s %s" % (
|
||||
d["titre_num"],
|
||||
self.modalite or "",
|
||||
self.date_debut.year,
|
||||
)
|
||||
if d["annee_fin"] != d["annee_debut"]:
|
||||
d["titreannee"] += "-" + str(d["annee_fin"])
|
||||
d["annee"] += "-" + str(d["annee_fin"])
|
||||
d["titre_num"] = self.titre_num()
|
||||
d["titreannee"] = self.titre_annee()
|
||||
d["mois_debut"] = f"{self.date_debut.month} {self.date_debut.year}"
|
||||
d["mois_fin"] = f"{self.date_fin.month} {self.date_fin.year}"
|
||||
d["titremois"] = "%s %s (%s - %s)" % (
|
||||
@ -332,6 +329,15 @@ class FormSemestre(db.Model):
|
||||
"-".join((imputation_dept, parcours_name, modalite, semestre_id, annee_sco))
|
||||
)
|
||||
|
||||
def titre_annee(self) -> str:
|
||||
""" """
|
||||
titre_annee = (
|
||||
f"{self.titre_num()} {self.modalite or ''} {self.date_debut.year}"
|
||||
)
|
||||
if self.date_fin.year != self.date_debut.year:
|
||||
titre_annee += "-" + str(self.date_fin.year)
|
||||
return titre_annee
|
||||
|
||||
def titre_mois(self) -> str:
|
||||
"""Le titre et les dates du semestre, pour affichage dans des listes
|
||||
Ex: "BUT QLIO (PN 2022) semestre 1 FI (Sept 2022 - Jan 2023)"
|
||||
@ -359,15 +365,19 @@ class FormSemestre(db.Model):
|
||||
etudid, self.date_debut.isoformat(), self.date_fin.isoformat()
|
||||
)
|
||||
|
||||
def get_inscrits(self, include_demdef=False) -> list[Identite]:
|
||||
def get_inscrits(self, include_demdef=False, order=False) -> list[Identite]:
|
||||
"""Liste des étudiants inscrits à ce semestre
|
||||
Si include_demdef, tous les étudiants, avec les démissionnaires
|
||||
et défaillants.
|
||||
Si order, tri par clé sort_key
|
||||
"""
|
||||
if include_demdef:
|
||||
return [ins.etud for ins in self.inscriptions]
|
||||
etuds = [ins.etud for ins in self.inscriptions]
|
||||
else:
|
||||
return [ins.etud for ins in self.inscriptions if ins.etat == scu.INSCRIT]
|
||||
etuds = [ins.etud for ins in self.inscriptions if ins.etat == scu.INSCRIT]
|
||||
if order:
|
||||
etuds.sort(key=lambda e: e.sort_key)
|
||||
return etuds
|
||||
|
||||
@cached_property
|
||||
def etudids_actifs(self) -> set:
|
||||
|
@ -79,7 +79,7 @@ class ModuleImpl(db.Model):
|
||||
)
|
||||
|
||||
def to_dict(self):
|
||||
"""as a dict, with the same conversions as in ScoDoc7"""
|
||||
"""as a dict, with the same conversions as in ScoDoc7, including module"""
|
||||
e = dict(self.__dict__)
|
||||
e.pop("_sa_instance_state", None)
|
||||
# ScoDoc7 output_formators: (backward compat)
|
||||
|
@ -4,8 +4,9 @@
|
||||
"""
|
||||
|
||||
from app import db
|
||||
from app.models import SHORT_STR_LEN
|
||||
from app.models import CODE_STR_LEN
|
||||
|
||||
import app.scodoc.notesdb as ndb
|
||||
import app.scodoc.sco_utils as scu
|
||||
|
||||
|
||||
class BulAppreciations(db.Model):
|
||||
@ -67,3 +68,32 @@ class NotesNotesLog(db.Model):
|
||||
comment = db.Column(db.Text) # texte libre
|
||||
date = db.Column(db.DateTime(timezone=True), server_default=db.func.now())
|
||||
uid = db.Column(db.Integer, db.ForeignKey("user.id"))
|
||||
|
||||
|
||||
def etud_has_notes_attente(etudid, formsemestre_id):
|
||||
"""Vrai si cet etudiant a au moins une note en attente dans ce semestre.
|
||||
(ne compte que les notes en attente dans des évaluation avec coef. non nul).
|
||||
"""
|
||||
# XXX ancienne méthode de notes_table à ré-écrire
|
||||
cnx = ndb.GetDBConnexion()
|
||||
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
|
||||
cursor.execute(
|
||||
"""SELECT n.*
|
||||
FROM notes_notes n, notes_evaluation e, notes_moduleimpl m,
|
||||
notes_moduleimpl_inscription i
|
||||
WHERE n.etudid = %(etudid)s
|
||||
and n.value = %(code_attente)s
|
||||
and n.evaluation_id = e.id
|
||||
and e.moduleimpl_id = m.id
|
||||
and m.formsemestre_id = %(formsemestre_id)s
|
||||
and e.coefficient != 0
|
||||
and m.id = i.moduleimpl_id
|
||||
and i.etudid=%(etudid)s
|
||||
""",
|
||||
{
|
||||
"formsemestre_id": formsemestre_id,
|
||||
"etudid": etudid,
|
||||
"code_attente": scu.NOTES_ATTENTE,
|
||||
},
|
||||
)
|
||||
return len(cursor.fetchall()) > 0
|
||||
|
@ -46,9 +46,12 @@ import io
|
||||
import os
|
||||
from zipfile import ZipFile
|
||||
|
||||
from app.comp import res_sem
|
||||
from app.comp.res_common import NotesTableCompat
|
||||
from app.models import FormSemestre
|
||||
|
||||
from app.scodoc.gen_tables import GenTable, SeqGenTable
|
||||
import app.scodoc.sco_utils as scu
|
||||
from app.scodoc import sco_cache
|
||||
from app.scodoc import sco_codes_parcours # sco_codes_parcours.NEXT -> sem suivant
|
||||
from app.scodoc import sco_etud
|
||||
from app.scodoc import sco_formsemestre
|
||||
@ -174,6 +177,8 @@ class JuryPE(object):
|
||||
self.PARCOURSINFO_DICT = {} # Les parcours des étudiants
|
||||
self.syntheseJury = {} # Le jury de synthèse
|
||||
|
||||
self.semestresDeScoDoc = sco_formsemestre.do_formsemestre_list()
|
||||
|
||||
# Calcul du jury PE
|
||||
self.exe_calculs_juryPE(semBase)
|
||||
self.synthetise_juryPE()
|
||||
@ -317,12 +322,10 @@ class JuryPE(object):
|
||||
etudiants = []
|
||||
for sem in semsListe: # pour chacun des semestres de la liste
|
||||
|
||||
# nt = self.get_notes_d_un_semestre( sem['formsemestre_id'] )
|
||||
nt = self.get_cache_notes_d_un_semestre(sem["formsemestre_id"])
|
||||
# sco_cache.NotesTableCache.get( sem['formsemestre_id'])
|
||||
etudiantsDuSemestre = (
|
||||
nt.get_etudids()
|
||||
) # nt.identdict.keys() # identification des etudiants du semestre
|
||||
) # identification des etudiants du semestre
|
||||
|
||||
if pe_tools.PE_DEBUG:
|
||||
pe_tools.pe_print(
|
||||
@ -486,14 +489,14 @@ class JuryPE(object):
|
||||
lastdate = max(sesdates) # date de fin de l'inscription la plus récente
|
||||
|
||||
# if PETable.AFFICHAGE_DEBUG_PE == True : pe_tools.pe_print(" derniere inscription = ", lastDateSem)
|
||||
semestresDeScoDoc = sco_formsemestre.do_formsemestre_list()
|
||||
|
||||
if sonDernierSidValide is None:
|
||||
# si l'étudiant n'a validé aucun semestre, les prend tous ? (à vérifier)
|
||||
semestresSuperieurs = semestresDeScoDoc
|
||||
semestresSuperieurs = self.semestresDeScoDoc
|
||||
else:
|
||||
semestresSuperieurs = [
|
||||
sem
|
||||
for sem in semestresDeScoDoc
|
||||
for sem in self.semestresDeScoDoc
|
||||
if sem["semestre_id"] > sonDernierSidValide
|
||||
] # Semestre de rang plus élevé que son dernier sem valide
|
||||
datesDesSemestresSuperieurs = [
|
||||
@ -1127,9 +1130,10 @@ class JuryPE(object):
|
||||
# ------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
# ------------------------------------------------------------------------------------------------------------------
|
||||
def get_cache_notes_d_un_semestre(self, formsemestre_id): # inutile en realité !
|
||||
def get_cache_notes_d_un_semestre(self, formsemestre_id: int) -> NotesTableCompat:
|
||||
"""Charge la table des notes d'un formsemestre"""
|
||||
return sco_cache.NotesTableCache.get(formsemestre_id)
|
||||
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||
return res_sem.load_formsemestre_results(formsemestre)
|
||||
|
||||
# ------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
|
@ -37,9 +37,13 @@ Created on Fri Sep 9 09:15:05 2016
|
||||
"""
|
||||
|
||||
from app import log
|
||||
from app.comp import res_sem
|
||||
from app.comp.res_common import NotesTableCompat
|
||||
from app.models import FormSemestre
|
||||
from app.models.moduleimpls import ModuleImpl
|
||||
|
||||
from app.models.ues import UniteEns
|
||||
from app.scodoc import sco_codes_parcours
|
||||
from app.scodoc import sco_cache
|
||||
from app.scodoc import sco_tag_module
|
||||
from app.pe import pe_tagtable
|
||||
|
||||
@ -52,7 +56,7 @@ class SemestreTag(pe_tagtable.TableTag):
|
||||
- nt: le tableau de notes du semestre considéré
|
||||
- nt.inscrlist: étudiants inscrits à ce semestre, par ordre alphabétique (avec demissions)
|
||||
- nt.identdict: { etudid : ident }
|
||||
- nt._modimpls : liste des moduleimpl { ... 'module_id', ...}
|
||||
- liste des moduleimpl { ... 'module_id', ...}
|
||||
|
||||
Attributs supplémentaires :
|
||||
- inscrlist/identdict: étudiants inscrits hors démissionnaires ou défaillants
|
||||
@ -97,7 +101,11 @@ class SemestreTag(pe_tagtable.TableTag):
|
||||
self.nt = notetable
|
||||
|
||||
# Les attributs hérités : la liste des étudiants
|
||||
self.inscrlist = [etud for etud in self.nt.inscrlist if etud["etat"] == "I"]
|
||||
self.inscrlist = [
|
||||
etud
|
||||
for etud in self.nt.inscrlist
|
||||
if self.nt.get_etud_etat(etud["etudid"]) == "I"
|
||||
]
|
||||
self.identdict = {
|
||||
etudid: ident
|
||||
for (etudid, ident) in self.nt.identdict.items()
|
||||
@ -107,12 +115,15 @@ class SemestreTag(pe_tagtable.TableTag):
|
||||
# Les modules pris en compte dans le calcul des moyennes par tag => ceux des UE standards
|
||||
self.modimpls = [
|
||||
modimpl
|
||||
for modimpl in self.nt._modimpls
|
||||
if modimpl["ue"]["type"] == sco_codes_parcours.UE_STANDARD
|
||||
for modimpl in self.nt.formsemestre.modimpls_sorted
|
||||
if modimpl.module.ue.type == sco_codes_parcours.UE_STANDARD
|
||||
] # la liste des modules (objet modimpl)
|
||||
# self._modimpl_ids = [modimpl['moduleimpl_id'] for modimpl in self._modimpls] # la liste de id des modules (modimpl_id)
|
||||
self.somme_coeffs = sum(
|
||||
[modimpl["module"]["coefficient"] for modimpl in self.modimpls]
|
||||
[
|
||||
modimpl.module.coefficient
|
||||
for modimpl in self.modimpls
|
||||
if modimpl.module.coefficient is not None
|
||||
]
|
||||
)
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
@ -156,9 +167,9 @@ class SemestreTag(pe_tagtable.TableTag):
|
||||
tagdict = {}
|
||||
|
||||
for modimpl in self.modimpls:
|
||||
modimpl_id = modimpl["moduleimpl_id"]
|
||||
modimpl_id = modimpl.id
|
||||
# liste des tags pour le modimpl concerné:
|
||||
tags = sco_tag_module.module_tag_list(modimpl["module_id"])
|
||||
tags = sco_tag_module.module_tag_list(modimpl.module.id)
|
||||
|
||||
for (
|
||||
tag
|
||||
@ -172,17 +183,13 @@ class SemestreTag(pe_tagtable.TableTag):
|
||||
|
||||
# Ajout du modimpl au tagname considéré
|
||||
tagdict[tagname][modimpl_id] = {
|
||||
"module_id": modimpl["module_id"], # les données sur le module
|
||||
"coeff": modimpl["module"][
|
||||
"coefficient"
|
||||
], # le coeff du module dans le semestre
|
||||
"module_id": modimpl.module.id, # les données sur le module
|
||||
"coeff": modimpl.module.coefficient, # le coeff du module dans le semestre
|
||||
"ponderation": ponderation, # la pondération demandée pour le tag sur le module
|
||||
"module_code": modimpl["module"][
|
||||
"code"
|
||||
], # le code qui doit se retrouver à l'identique dans des ue capitalisee
|
||||
"ue_id": modimpl["ue"]["ue_id"], # les données sur l'ue
|
||||
"ue_code": modimpl["ue"]["ue_code"],
|
||||
"ue_acronyme": modimpl["ue"]["acronyme"],
|
||||
"module_code": modimpl.module.code, # le code qui doit se retrouver à l'identique dans des ue capitalisee
|
||||
"ue_id": modimpl.module.ue.id, # les données sur l'ue
|
||||
"ue_code": modimpl.module.ue.ue_code,
|
||||
"ue_acronyme": modimpl.module.ue.acronyme,
|
||||
}
|
||||
return tagdict
|
||||
|
||||
@ -218,7 +225,9 @@ class SemestreTag(pe_tagtable.TableTag):
|
||||
def get_moyennes_DUT(self):
|
||||
"""Lit les moyennes DUT du semestre pour tous les étudiants
|
||||
et les renvoie au même format que comp_MoyennesTag"""
|
||||
return [(self.nt.moy_gen[etudid], 1.0, etudid) for etudid in self.get_etudids()]
|
||||
return [
|
||||
(self.nt.etud_moy_gen[etudid], 1.0, etudid) for etudid in self.get_etudids()
|
||||
]
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
def get_noteEtCoeff_modimpl(self, modimpl_id, etudid, profondeur=2):
|
||||
@ -230,7 +239,7 @@ class SemestreTag(pe_tagtable.TableTag):
|
||||
"""
|
||||
(note, coeff_norm) = (None, None)
|
||||
|
||||
modimpl = get_moduleimpl(self.nt, modimpl_id) # Le module considéré
|
||||
modimpl = get_moduleimpl(modimpl_id) # Le module considéré
|
||||
if modimpl == None or profondeur < 0:
|
||||
return (None, None)
|
||||
|
||||
@ -238,14 +247,14 @@ class SemestreTag(pe_tagtable.TableTag):
|
||||
ue_capitalisees = self.get_ue_capitalisees(
|
||||
etudid
|
||||
) # les ue capitalisées des étudiants
|
||||
ue_capitalisees_id = [
|
||||
ue.id for ue in ue_capitalisees
|
||||
] # les id des ue capitalisées
|
||||
ue_capitalisees_id = {
|
||||
ue_cap["ue_id"] for ue_cap in ue_capitalisees
|
||||
} # les id des ue capitalisées
|
||||
|
||||
# Si le module ne fait pas partie des UE capitalisées
|
||||
if modimpl["module"]["ue_id"] not in ue_capitalisees_id:
|
||||
if modimpl.module.ue.id not in ue_capitalisees_id:
|
||||
note = self.nt.get_etud_mod_moy(modimpl_id, etudid) # lecture de la note
|
||||
coeff = modimpl["module"]["coefficient"] # le coeff
|
||||
coeff = modimpl.module.coefficient # le coeff
|
||||
coeff_norm = (
|
||||
coeff / self.somme_coeffs if self.somme_coeffs != 0 else 0
|
||||
) # le coeff normalisé
|
||||
@ -256,29 +265,30 @@ class SemestreTag(pe_tagtable.TableTag):
|
||||
self.nt, etudid, modimpl_id
|
||||
) # la moyenne actuelle
|
||||
# A quel semestre correspond l'ue capitalisée et quelles sont ses notes ?
|
||||
# fid_prec = [ ue['formsemestre_id'] for ue in ue_capitalisees if ue['ue_id'] == modimpl['module']['ue_id'] ][0]
|
||||
# semestre_id = modimpl['module']['semestre_id']
|
||||
fids_prec = [
|
||||
ue["formsemestre_id"]
|
||||
for ue in ue_capitalisees
|
||||
if ue.ue_code == modimpl["ue"]["ue_code"]
|
||||
ue_cap["formsemestre_id"]
|
||||
for ue_cap in ue_capitalisees
|
||||
if ue_cap["ue_code"] == modimpl.module.ue.ue_code
|
||||
] # and ue['semestre_id'] == semestre_id]
|
||||
if len(fids_prec) > 0:
|
||||
# => le formsemestre_id du semestre dont vient la capitalisation
|
||||
fid_prec = fids_prec[0]
|
||||
# Lecture des notes de ce semestre
|
||||
nt_prec = sco_cache.NotesTableCache.get(
|
||||
fid_prec
|
||||
) # le tableau de note du semestre considéré
|
||||
# le tableau de note du semestre considéré:
|
||||
formsemestre_prec = FormSemestre.query.get_or_404(fid_prec)
|
||||
nt_prec: NotesTableCompat = res_sem.load_formsemestre_results(
|
||||
formsemestre_prec
|
||||
)
|
||||
|
||||
# Y-a-t-il un module équivalent c'est à dire correspondant au même code (le module_id n'étant pas significatif en cas de changement de PPN)
|
||||
|
||||
modimpl_prec = [
|
||||
module
|
||||
for module in nt_prec._modimpls
|
||||
if module["module"]["code"] == modimpl["module"]["code"]
|
||||
modi
|
||||
for modi in nt_prec.formsemestre.modimpls_sorted
|
||||
if modi.module.code == modimpl.module.code
|
||||
]
|
||||
if len(modimpl_prec) > 0: # si une correspondance est trouvée
|
||||
modprec_id = modimpl_prec[0]["moduleimpl_id"]
|
||||
modprec_id = modimpl_prec[0].id
|
||||
moy_ue_capitalisee = get_moy_ue_from_nt(nt_prec, etudid, modprec_id)
|
||||
if (
|
||||
moy_ue_capitalisee is None
|
||||
@ -286,7 +296,7 @@ class SemestreTag(pe_tagtable.TableTag):
|
||||
note = self.nt.get_etud_mod_moy(
|
||||
modimpl_id, etudid
|
||||
) # lecture de la note
|
||||
coeff = modimpl["module"]["coefficient"] # le coeff
|
||||
coeff = modimpl.module.coefficient # le coeff
|
||||
coeff_norm = (
|
||||
coeff / self.somme_coeffs if self.somme_coeffs != 0 else 0
|
||||
) # le coeff normalisé
|
||||
@ -300,13 +310,11 @@ class SemestreTag(pe_tagtable.TableTag):
|
||||
return (note, coeff_norm)
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
def get_ue_capitalisees(self, etudid) -> list[UniteEns]:
|
||||
"""Renvoie la liste des ue_id effectivement capitalisées par un étudiant"""
|
||||
ue_ids = [
|
||||
ue_id
|
||||
for ue_id in self.nt.validations.ue_capitalisees.loc[[etudid]]["ue_id"]
|
||||
]
|
||||
return [UniteEns.query.get(ue_id) for ue_id in ue_ids]
|
||||
def get_ue_capitalisees(self, etudid) -> list[dict]:
|
||||
"""Renvoie la liste des capitalisation effectivement capitalisées par un étudiant"""
|
||||
if etudid in self.nt.validations.ue_capitalisees.index:
|
||||
return self.nt.validations.ue_capitalisees.loc[[etudid]].to_dict("records")
|
||||
return []
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
def get_listesNotesEtCoeffsTagEtudiant(self, tag, etudid):
|
||||
@ -472,37 +480,27 @@ def comp_coeff_pond(coeffs, ponderations):
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
def get_moduleimpl(nt, modimpl_id):
|
||||
"""Renvoie l'objet modimpl dont l'id est modimpl_id fourni dans la note table nt,
|
||||
en utilisant l'attribut nt._modimpls"""
|
||||
modimplids = [
|
||||
modimpl["moduleimpl_id"] for modimpl in nt._modimpls
|
||||
] # la liste de id des modules (modimpl_id)
|
||||
if modimpl_id not in modimplids:
|
||||
def get_moduleimpl(modimpl_id) -> dict:
|
||||
"""Renvoie l'objet modimpl dont l'id est modimpl_id"""
|
||||
modimpl = ModuleImpl.query.get(modimpl_id)
|
||||
if modimpl:
|
||||
return modimpl
|
||||
if SemestreTag.DEBUG:
|
||||
log(
|
||||
"SemestreTag.get_moduleimpl( %s ) : le modimpl recherche n'existe pas"
|
||||
% (modimpl_id)
|
||||
)
|
||||
return None
|
||||
return nt._modimpls[modimplids.index(modimpl_id)]
|
||||
|
||||
|
||||
# **********************************************
|
||||
def get_moy_ue_from_nt(nt, etudid, modimpl_id):
|
||||
"""Renvoie la moyenne de l'UE d'un etudid dans laquelle se trouve le module de modimpl_id
|
||||
en partant du note table nt"""
|
||||
mod = get_moduleimpl(nt, modimpl_id) # le module
|
||||
indice = 0
|
||||
while indice < len(nt._ues):
|
||||
if (
|
||||
nt._ues[indice]["ue_id"] == mod["module"]["ue_id"]
|
||||
): # si les ue_id correspond
|
||||
data = [
|
||||
ligne for ligne in nt.T if ligne[-1] == etudid
|
||||
] # les notes de l'étudiant
|
||||
if data:
|
||||
return data[0][indice + 1] # la moyenne à l'ue
|
||||
else:
|
||||
indice += 1
|
||||
return None # si non trouvé
|
||||
def get_moy_ue_from_nt(nt, etudid, modimpl_id) -> float:
|
||||
"""Renvoie la moyenne de l'UE d'un etudid dans laquelle se trouve
|
||||
le module de modimpl_id
|
||||
"""
|
||||
# ré-écrit
|
||||
modimpl = get_moduleimpl(modimpl_id) # le module
|
||||
ue_status = nt.get_etud_ue_status(etudid, modimpl.module.ue.id)
|
||||
if ue_status is None:
|
||||
return None
|
||||
return ue_status["moy"]
|
||||
|
@ -68,7 +68,7 @@ class TableTag(object):
|
||||
self.taglist = []
|
||||
|
||||
self.resultats = {}
|
||||
self.etud_moy_gen_ranks = {}
|
||||
self.rangs = {}
|
||||
self.statistiques = {}
|
||||
|
||||
# *****************************************************************************************************************
|
||||
|
@ -254,13 +254,13 @@ class TF(object):
|
||||
continue # allowed empty field, skip
|
||||
# type
|
||||
typ = descr.get("type", "string")
|
||||
if val != "" and val != None:
|
||||
if val != "" and val is not None:
|
||||
# check only non-null values
|
||||
if typ[:3] == "int":
|
||||
try:
|
||||
val = int(val)
|
||||
self.values[field] = val
|
||||
except:
|
||||
except ValueError:
|
||||
msg.append(
|
||||
"La valeur du champ '%s' doit être un nombre entier" % field
|
||||
)
|
||||
@ -270,30 +270,24 @@ class TF(object):
|
||||
try:
|
||||
val = float(val.replace(",", ".")) # allow ,
|
||||
self.values[field] = val
|
||||
except:
|
||||
except ValueError:
|
||||
msg.append(
|
||||
"La valeur du champ '%s' doit être un nombre" % field
|
||||
)
|
||||
ok = 0
|
||||
if typ[:3] == "int" or typ == "float" or typ == "real":
|
||||
if (
|
||||
val != ""
|
||||
ok
|
||||
and (typ[:3] == "int" or typ == "float" or typ == "real")
|
||||
and val != ""
|
||||
and val != None
|
||||
and "min_value" in descr
|
||||
and val < descr["min_value"]
|
||||
):
|
||||
if "min_value" in descr and self.values[field] < descr["min_value"]:
|
||||
msg.append(
|
||||
"La valeur (%d) du champ '%s' est trop petite (min=%s)"
|
||||
% (val, field, descr["min_value"])
|
||||
)
|
||||
ok = 0
|
||||
|
||||
if (
|
||||
val != ""
|
||||
and val != None
|
||||
and "max_value" in descr
|
||||
and val > descr["max_value"]
|
||||
):
|
||||
if "max_value" in descr and self.values[field] > descr["max_value"]:
|
||||
msg.append(
|
||||
"La valeur (%s) du champ '%s' est trop grande (max=%s)"
|
||||
% (val, field, descr["max_value"])
|
||||
|
@ -171,7 +171,7 @@ class NotesTable:
|
||||
|
||||
def __init__(self, formsemestre_id):
|
||||
# log(f"NotesTable( formsemestre_id={formsemestre_id} )")
|
||||
# raise NotImplementedError() # XXX
|
||||
raise NotImplementedError() # XXX
|
||||
if not formsemestre_id:
|
||||
raise ValueError("invalid formsemestre_id (%s)" % formsemestre_id)
|
||||
self.formsemestre_id = formsemestre_id
|
||||
@ -954,9 +954,12 @@ class NotesTable:
|
||||
|
||||
Return: True|False, message explicatif
|
||||
"""
|
||||
return self.parcours.check_barre_ues(
|
||||
[self.get_etud_ue_status(etudid, ue["ue_id"]) for ue in self._ues]
|
||||
)
|
||||
ue_status_list = []
|
||||
for ue in self._ues:
|
||||
ue_status = self.get_etud_ue_status(etudid, ue["ue_id"])
|
||||
if ue_status:
|
||||
ue_status_list.append(ue_status)
|
||||
return self.parcours.check_barre_ues(ue_status_list)
|
||||
|
||||
def get_table_moyennes_triees(self):
|
||||
return self.T
|
||||
@ -1160,9 +1163,11 @@ class NotesTable:
|
||||
nt_cap = sco_cache.NotesTableCache.get(
|
||||
ue_cap["formsemestre_id"]
|
||||
) # > UE capitalisees par un etud
|
||||
moy_ue_cap = nt_cap.get_etud_ue_status(etudid, ue_cap["ue_id"])[
|
||||
"moy"
|
||||
]
|
||||
ue_cap_status = nt_cap.get_etud_ue_status(etudid, ue_cap["ue_id"])
|
||||
if ue_cap_status:
|
||||
moy_ue_cap = ue_cap_status["moy"]
|
||||
else:
|
||||
moy_ue_cap = ""
|
||||
ue_cap["moy_ue"] = moy_ue_cap
|
||||
if (
|
||||
isinstance(moy_ue_cap, float)
|
||||
|
@ -479,7 +479,7 @@ def _get_abs_description(a, cursor=None):
|
||||
)
|
||||
if Mlist:
|
||||
M = Mlist[0]
|
||||
module += "%s " % M["module"]["code"]
|
||||
module += "%s " % (M["module"]["code"] or "(module sans code)")
|
||||
|
||||
if desc:
|
||||
return "(%s) %s" % (desc, module)
|
||||
|
@ -33,7 +33,9 @@ import datetime
|
||||
from flask import url_for, g, request, abort
|
||||
|
||||
from app import log
|
||||
from app.models import Identite
|
||||
from app.comp import res_sem
|
||||
from app.comp.res_common import NotesTableCompat
|
||||
from app.models import Identite, FormSemestre
|
||||
import app.scodoc.sco_utils as scu
|
||||
from app.scodoc import notesdb as ndb
|
||||
from app.scodoc.scolog import logdb
|
||||
@ -118,13 +120,16 @@ def doSignaleAbsence(
|
||||
if moduleimpl_id and moduleimpl_id != "NULL":
|
||||
mod = sco_moduleimpl.moduleimpl_list(moduleimpl_id=moduleimpl_id)[0]
|
||||
formsemestre_id = mod["formsemestre_id"]
|
||||
nt = sco_cache.NotesTableCache.get(formsemestre_id)
|
||||
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
||||
ues = nt.get_ues_stat_dict()
|
||||
for ue in ues:
|
||||
modimpls = nt.get_modimpls_dict(ue_id=ue["ue_id"])
|
||||
for modimpl in modimpls:
|
||||
if modimpl["moduleimpl_id"] == moduleimpl_id:
|
||||
indication_module = "dans le module %s" % modimpl["module"]["code"]
|
||||
indication_module = "dans le module %s" % (
|
||||
modimpl["module"]["code"] or "(pas de code)"
|
||||
)
|
||||
H = [
|
||||
html_sco_header.sco_header(
|
||||
page_title=f"Signalement d'une absence pour {etud.nomprenom}",
|
||||
@ -179,11 +184,12 @@ def SignaleAbsenceEtud(): # etudid implied
|
||||
menu_module = ""
|
||||
else:
|
||||
formsemestre_id = etud["cursem"]["formsemestre_id"]
|
||||
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
||||
ues = nt.get_ues_stat_dict()
|
||||
require_module = sco_preferences.get_preference(
|
||||
"abs_require_module", formsemestre_id
|
||||
)
|
||||
nt = sco_cache.NotesTableCache.get(formsemestre_id)
|
||||
ues = nt.get_ues_stat_dict()
|
||||
if require_module:
|
||||
menu_module = """
|
||||
<script type="text/javascript">
|
||||
@ -214,7 +220,7 @@ def SignaleAbsenceEtud(): # etudid implied
|
||||
"""<option value="%(modimpl_id)s">%(modname)s</option>\n"""
|
||||
% {
|
||||
"modimpl_id": modimpl["moduleimpl_id"],
|
||||
"modname": modimpl["module"]["code"],
|
||||
"modname": modimpl["module"]["code"] or "",
|
||||
}
|
||||
)
|
||||
menu_module += """</select></p>"""
|
||||
@ -960,10 +966,10 @@ def _tables_abs_etud(
|
||||
ex.append(
|
||||
f"""<a href="{url_for('notes.moduleimpl_status',
|
||||
scodoc_dept=g.scodoc_dept, moduleimpl_id=mod["moduleimpl_id"])}
|
||||
">{mod["module"]["code"]}</a>"""
|
||||
">{mod["module"]["code"] or "(module sans code)"}</a>"""
|
||||
)
|
||||
else:
|
||||
ex.append(mod["module"]["code"])
|
||||
ex.append(mod["module"]["code"] or "(module sans code)")
|
||||
if ex:
|
||||
return ", ".join(ex)
|
||||
return ""
|
||||
@ -978,10 +984,10 @@ def _tables_abs_etud(
|
||||
ex.append(
|
||||
f"""<a href="{url_for('notes.moduleimpl_status',
|
||||
scodoc_dept=g.scodoc_dept, moduleimpl_id=mod["moduleimpl_id"])}
|
||||
">{mod["module"]["code"]}</a>"""
|
||||
">{mod["module"]["code"] or '(module sans code)'}</a>"""
|
||||
)
|
||||
else:
|
||||
ex.append(mod["module"]["code"])
|
||||
ex.append(mod["module"]["code"] or "(module sans code)")
|
||||
if ex:
|
||||
return ", ".join(ex)
|
||||
return ""
|
||||
|
@ -95,9 +95,12 @@ from flask import send_file
|
||||
# Pour la détection auto de l'encodage des fichiers Apogée:
|
||||
from chardet import detect as chardet_detect
|
||||
|
||||
from app import log
|
||||
from app.comp import res_sem
|
||||
from app.comp.res_common import NotesTableCompat
|
||||
from app.models import FormSemestre
|
||||
from app.models.config import ScoDocSiteConfig
|
||||
import app.scodoc.sco_utils as scu
|
||||
from app import log
|
||||
from app.scodoc.sco_exceptions import ScoValueError, ScoFormatError
|
||||
from app.scodoc.gen_tables import GenTable
|
||||
from app.scodoc.sco_vdi import ApoEtapeVDI
|
||||
@ -370,7 +373,9 @@ class ApoEtud(dict):
|
||||
dict: with N, B, J, R keys, ou None si elt non trouvé
|
||||
"""
|
||||
etudid = self.etud["etudid"]
|
||||
nt = sco_cache.NotesTableCache.get(sem["formsemestre_id"])
|
||||
formsemestre = FormSemestre.query.get_or_404(sem["formsemestre_id"])
|
||||
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
||||
|
||||
if etudid not in nt.identdict:
|
||||
return None # etudiant non inscrit dans ce semestre
|
||||
|
||||
@ -419,7 +424,7 @@ class ApoEtud(dict):
|
||||
ue_status = nt.get_etud_ue_status(etudid, ue["ue_id"])
|
||||
code_decision_ue = decisions_ue[ue["ue_id"]]["code"]
|
||||
return dict(
|
||||
N=_apo_fmt_note(ue_status["moy"]),
|
||||
N=_apo_fmt_note(ue_status["moy"] if ue_status else ""),
|
||||
B=20,
|
||||
J="",
|
||||
R=ScoDocSiteConfig.get_code_apo(code_decision_ue),
|
||||
@ -476,7 +481,8 @@ class ApoEtud(dict):
|
||||
# l'étudiant n'a pas de semestre courant ?!
|
||||
log("comp_elt_annuel: etudid %s has no cur_sem" % etudid)
|
||||
return VOID_APO_RES
|
||||
cur_nt = sco_cache.NotesTableCache.get(cur_sem["formsemestre_id"])
|
||||
cur_formsemestre = FormSemestre.query.get_or_404(cur_sem["formsemestre_id"])
|
||||
cur_nt: NotesTableCompat = res_sem.load_formsemestre_results(cur_formsemestre)
|
||||
cur_decision = cur_nt.get_etud_decision_sem(etudid)
|
||||
if not cur_decision:
|
||||
# pas de decision => pas de résultat annuel
|
||||
@ -493,7 +499,10 @@ class ApoEtud(dict):
|
||||
|
||||
decision_apo = ScoDocSiteConfig.get_code_apo(cur_decision["code"])
|
||||
|
||||
autre_nt = sco_cache.NotesTableCache.get(autre_sem["formsemestre_id"])
|
||||
autre_formsemestre = FormSemestre.query.get_or_404(autre_sem["formsemestre_id"])
|
||||
autre_nt: NotesTableCompat = res_sem.load_formsemestre_results(
|
||||
autre_formsemestre
|
||||
)
|
||||
autre_decision = autre_nt.get_etud_decision_sem(etudid)
|
||||
if not autre_decision:
|
||||
# pas de decision dans l'autre => pas de résultat annuel
|
||||
@ -554,7 +563,8 @@ class ApoEtud(dict):
|
||||
# prend le plus recent avec decision
|
||||
cur_sem = None
|
||||
for sem in cur_sems:
|
||||
nt = sco_cache.NotesTableCache.get(sem["formsemestre_id"])
|
||||
formsemestre = FormSemestre.query.get_or_404(sem["formsemestre_id"])
|
||||
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
||||
decision = nt.get_etud_decision_sem(self.etud["etudid"])
|
||||
if decision:
|
||||
cur_sem = sem
|
||||
@ -614,7 +624,8 @@ class ApoEtud(dict):
|
||||
else:
|
||||
autre_sem = None
|
||||
for sem in autres_sems:
|
||||
nt = sco_cache.NotesTableCache.get(sem["formsemestre_id"])
|
||||
formsemestre = FormSemestre.query.get_or_404(sem["formsemestre_id"])
|
||||
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
||||
decision = nt.get_etud_decision_sem(self.etud["etudid"])
|
||||
if decision:
|
||||
autre_sem = sem
|
||||
@ -947,7 +958,8 @@ class ApoData(object):
|
||||
s.add(code)
|
||||
continue
|
||||
# associé à une UE:
|
||||
nt = sco_cache.NotesTableCache.get(sem["formsemestre_id"])
|
||||
formsemestre = FormSemestre.query.get_or_404(sem["formsemestre_id"])
|
||||
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
||||
for ue in nt.get_ues_stat_dict():
|
||||
if ue["code_apogee"] and code in ue["code_apogee"].split(","):
|
||||
s.add(code)
|
||||
|
@ -132,19 +132,29 @@ BACS_S = {t[0]: t[2:] for t in _BACS}
|
||||
|
||||
class Baccalaureat:
|
||||
def __init__(self, bac, specialite=""):
|
||||
self.bac = bac
|
||||
self.specialite = specialite
|
||||
self._abbrev, self._type = BACS_SSP.get((bac, specialite), (None, None))
|
||||
self.bac = bac or ""
|
||||
self.specialite = specialite or ""
|
||||
self._abbrev, self._type = BACS_SSP.get(
|
||||
(self.bac, self.specialite), (None, None)
|
||||
)
|
||||
# Parfois, la specialite commence par la serie: essaye
|
||||
if self._type is None and specialite and specialite.startswith(bac):
|
||||
specialite = specialite[len(bac) :].strip(" -")
|
||||
self._abbrev, self._type = BACS_SSP.get((bac, specialite), (None, None))
|
||||
if (
|
||||
self._type is None
|
||||
and self.specialite
|
||||
and self.specialite.startswith(self.bac)
|
||||
):
|
||||
specialite = self.specialite[len(self.bac) :].strip(" -")
|
||||
self._abbrev, self._type = BACS_SSP.get(
|
||||
(self.bac, specialite), (None, None)
|
||||
)
|
||||
# Cherche la forme serie specialite
|
||||
if self._type is None and specialite:
|
||||
self._abbrev, self._type = BACS_S.get(bac + " " + specialite, (None, None))
|
||||
self._abbrev, self._type = BACS_S.get(
|
||||
self.bac + " " + specialite, (None, None)
|
||||
)
|
||||
# Cherche avec juste le bac, sans specialite
|
||||
if self._type is None:
|
||||
self._abbrev, self._type = BACS_S.get(bac, (None, None))
|
||||
self._abbrev, self._type = BACS_S.get(self.bac, (None, None))
|
||||
|
||||
def abbrev(self):
|
||||
"abbreviation for this bac"
|
||||
|
@ -28,30 +28,21 @@
|
||||
"""Génération des bulletins de notes
|
||||
|
||||
"""
|
||||
from app.models import formsemestre
|
||||
import time
|
||||
import pprint
|
||||
import email
|
||||
from email.mime.multipart import MIMEMultipart
|
||||
from email.mime.text import MIMEText
|
||||
from email.mime.base import MIMEBase
|
||||
from email.header import Header
|
||||
from reportlab.lib.colors import Color
|
||||
import urllib
|
||||
import pprint
|
||||
import time
|
||||
|
||||
from flask import g, request
|
||||
from flask import url_for
|
||||
from flask_login import current_user
|
||||
from flask_mail import Message
|
||||
from app.models.moduleimpls import ModuleImplInscription
|
||||
|
||||
import app.scodoc.sco_utils as scu
|
||||
from app.scodoc.sco_utils import ModuleType
|
||||
import app.scodoc.notesdb as ndb
|
||||
from app import email
|
||||
from app import log
|
||||
from app.but import bulletin_but
|
||||
from app.comp import res_sem
|
||||
from app.comp.res_common import NotesTableCompat
|
||||
from app.models import FormSemestre
|
||||
from app.models import FormSemestre, Identite, ModuleImplInscription
|
||||
from app.scodoc.sco_permissions import Permission
|
||||
from app.scodoc.sco_exceptions import AccessDenied, ScoValueError
|
||||
from app.scodoc import html_sco_header
|
||||
@ -60,9 +51,9 @@ from app.scodoc import sco_abs
|
||||
from app.scodoc import sco_abs_views
|
||||
from app.scodoc import sco_bulletins_generator
|
||||
from app.scodoc import sco_bulletins_json
|
||||
from app.scodoc import sco_bulletins_pdf
|
||||
from app.scodoc import sco_bulletins_xml
|
||||
from app.scodoc import sco_codes_parcours
|
||||
from app.scodoc import sco_cache
|
||||
from app.scodoc import sco_etud
|
||||
from app.scodoc import sco_evaluation_db
|
||||
from app.scodoc import sco_formations
|
||||
@ -73,7 +64,9 @@ from app.scodoc import sco_photos
|
||||
from app.scodoc import sco_preferences
|
||||
from app.scodoc import sco_pvjury
|
||||
from app.scodoc import sco_users
|
||||
from app import email
|
||||
import app.scodoc.sco_utils as scu
|
||||
from app.scodoc.sco_utils import ModuleType
|
||||
import app.scodoc.notesdb as ndb
|
||||
|
||||
# ----- CLASSES DE BULLETINS DE NOTES
|
||||
from app.scodoc import sco_bulletins_standard
|
||||
@ -140,7 +133,6 @@ def formsemestre_bulletinetud_dict(formsemestre_id, etudid, version="long"):
|
||||
raise ValueError("invalid version code !")
|
||||
|
||||
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_results(formsemestre)
|
||||
if not nt.get_etud_etat(etudid):
|
||||
@ -191,28 +183,18 @@ def formsemestre_bulletinetud_dict(formsemestre_id, etudid, version="long"):
|
||||
show_mention=prefs["bul_show_mention"],
|
||||
)
|
||||
|
||||
if dpv:
|
||||
I["decision_sem"] = dpv["decisions"][0]["decision_sem"]
|
||||
else:
|
||||
I["decision_sem"] = ""
|
||||
I.update(infos)
|
||||
|
||||
I["etud_etat_html"] = _get_etud_etat_html(
|
||||
formsemestre.etuds_inscriptions[etudid].etat
|
||||
)
|
||||
I["etud_etat"] = nt.get_etud_etat(etudid)
|
||||
I["filigranne"] = ""
|
||||
I["filigranne"] = sco_bulletins_pdf.get_filigranne(I["etud_etat"], prefs)
|
||||
I["demission"] = ""
|
||||
if I["etud_etat"] == "D":
|
||||
if I["etud_etat"] == scu.DEMISSION:
|
||||
I["demission"] = "(Démission)"
|
||||
I["filigranne"] = "Démission"
|
||||
elif I["etud_etat"] == sco_codes_parcours.DEF:
|
||||
I["demission"] = "(Défaillant)"
|
||||
I["filigranne"] = "Défaillant"
|
||||
elif (prefs["bul_show_temporary"] and not I["decision_sem"]) or prefs[
|
||||
"bul_show_temporary_forced"
|
||||
]:
|
||||
I["filigranne"] = prefs["bul_temporary_txt"]
|
||||
|
||||
# --- Appreciations
|
||||
cnx = ndb.GetDBConnexion()
|
||||
@ -363,10 +345,7 @@ def formsemestre_bulletinetud_dict(formsemestre_id, etudid, version="long"):
|
||||
>{u["ue_descr_txt"]} pouet</a>
|
||||
"""
|
||||
if ue_status["moy"] != "NA" and ue_status["formsemestre_id"]:
|
||||
# detail des modules de l'UE capitalisee
|
||||
# nt_cap = sco_cache.NotesTableCache.get(
|
||||
# ue_status["formsemestre_id"]
|
||||
# ) # > toutes notes
|
||||
# détail des modules de l'UE capitalisée
|
||||
formsemestre_cap = FormSemestre.query.get_or_404(
|
||||
ue_status["formsemestre_id"]
|
||||
)
|
||||
@ -518,7 +497,7 @@ def _ue_mod_bulletin(etudid, formsemestre_id, ue_id, modimpls, nt, version):
|
||||
)
|
||||
if sco_preferences.get_preference("bul_show_codemodules", formsemestre_id):
|
||||
mod["code"] = modimpl["module"]["code"]
|
||||
mod["code_html"] = link_mod + mod["code"] + "</a>"
|
||||
mod["code_html"] = link_mod + (mod["code"] or "") + "</a>"
|
||||
else:
|
||||
mod["code"] = mod["code_html"] = ""
|
||||
mod["name"] = (
|
||||
@ -536,7 +515,7 @@ def _ue_mod_bulletin(etudid, formsemestre_id, ue_id, modimpls, nt, version):
|
||||
% (modimpl["moduleimpl_id"], mod_descr)
|
||||
)
|
||||
if sco_preferences.get_preference("bul_show_codemodules", formsemestre_id):
|
||||
mod["code_txt"] = modimpl["module"]["code"]
|
||||
mod["code_txt"] = modimpl["module"]["code"] or ""
|
||||
mod["code_html"] = link_mod + mod["code_txt"] + "</a>"
|
||||
else:
|
||||
mod["code_txt"] = ""
|
||||
@ -621,7 +600,12 @@ def _ue_mod_bulletin(etudid, formsemestre_id, ue_id, modimpls, nt, version):
|
||||
e["note_txt"] = e["note_html"] = ""
|
||||
e["coef_txt"] = scu.fmt_coef(e["coefficient"])
|
||||
# Classement
|
||||
if bul_show_mod_rangs and mod["mod_moy_txt"] != "-" and not is_malus:
|
||||
if (
|
||||
bul_show_mod_rangs
|
||||
and (nt.mod_rangs is not None)
|
||||
and mod["mod_moy_txt"] != "-"
|
||||
and not is_malus
|
||||
):
|
||||
rg = nt.mod_rangs[modimpl["moduleimpl_id"]]
|
||||
if rg[0] is None:
|
||||
mod["mod_rang_txt"] = ""
|
||||
@ -686,6 +670,7 @@ def etud_descr_situation_semestre(
|
||||
descr_defaillance : "Défaillant" ou vide si non défaillant.
|
||||
decision_jury : "Validé", "Ajourné", ... (code semestre)
|
||||
descr_decision_jury : "Décision jury: Validé" (une phrase)
|
||||
decision_sem :
|
||||
decisions_ue : noms (acronymes) des UE validées, séparées par des virgules.
|
||||
descr_decisions_ue : ' UE acquises: UE1, UE2', ou vide si pas de dec. ou si pas show_uevalid
|
||||
descr_mention : 'Mention Bien', ou vide si pas de mention ou si pas show_mention
|
||||
@ -695,7 +680,7 @@ def etud_descr_situation_semestre(
|
||||
|
||||
# --- Situation et décisions jury
|
||||
|
||||
# demission/inscription ?
|
||||
# démission/inscription ?
|
||||
events = sco_etud.scolar_events_list(
|
||||
cnx, args={"etudid": etudid, "formsemestre_id": formsemestre_id}
|
||||
)
|
||||
@ -762,11 +747,15 @@ def etud_descr_situation_semestre(
|
||||
infos["situation"] += " " + infos["descr_defaillance"]
|
||||
|
||||
dpv = sco_pvjury.dict_pvjury(formsemestre_id, etudids=[etudid])
|
||||
if dpv:
|
||||
infos["decision_sem"] = dpv["decisions"][0]["decision_sem"]
|
||||
else:
|
||||
infos["decision_sem"] = ""
|
||||
|
||||
if not show_decisions:
|
||||
return infos, dpv
|
||||
|
||||
# Decisions de jury:
|
||||
# Décisions de jury:
|
||||
pv = dpv["decisions"][0]
|
||||
dec = ""
|
||||
if pv["decision_sem_descr"]:
|
||||
@ -818,11 +807,15 @@ def formsemestre_bulletinetud(
|
||||
except:
|
||||
sco_etud.log_unknown_etud()
|
||||
raise ScoValueError("étudiant inconnu")
|
||||
# API, donc erreurs admises en ScoValueError
|
||||
sem = sco_formsemestre.get_formsemestre(formsemestre_id, raise_soft_exc=True)
|
||||
|
||||
formsemestre: FormSemestre = FormSemestre.query.get(formsemestre_id)
|
||||
if not formsemestre:
|
||||
# API, donc erreurs admises
|
||||
raise ScoValueError(f"semestre {formsemestre_id} inconnu !")
|
||||
sem = formsemestre.to_dict()
|
||||
|
||||
bulletin = do_formsemestre_bulletinetud(
|
||||
formsemestre_id,
|
||||
formsemestre,
|
||||
etudid,
|
||||
format=format,
|
||||
version=version,
|
||||
@ -834,7 +827,6 @@ def formsemestre_bulletinetud(
|
||||
filename = scu.bul_filename(sem, etud, format)
|
||||
return scu.send_file(bulletin, filename, mime=scu.get_mime_suffix(format)[0])
|
||||
|
||||
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
||||
H = [
|
||||
_formsemestre_bulletinetud_header_html(
|
||||
etud, etudid, sem, formsemestre_id, format, version
|
||||
@ -891,14 +883,14 @@ def can_send_bulletin_by_mail(formsemestre_id):
|
||||
|
||||
|
||||
def do_formsemestre_bulletinetud(
|
||||
formsemestre_id,
|
||||
etudid,
|
||||
formsemestre: FormSemestre,
|
||||
etudid: int,
|
||||
version="long", # short, long, selectedevals
|
||||
format="html",
|
||||
nohtml=False,
|
||||
xml_with_decisions=False, # force decisions dans XML
|
||||
force_publishing=False, # force publication meme si semestre non publie sur "portail"
|
||||
prefer_mail_perso=False, # mails envoyes sur adresse perso si non vide
|
||||
xml_with_decisions=False, # force décisions dans XML
|
||||
force_publishing=False, # force publication meme si semestre non publié sur "portail"
|
||||
prefer_mail_perso=False, # mails envoyés sur adresse perso si non vide
|
||||
):
|
||||
"""Génère le bulletin au format demandé.
|
||||
Retourne: (bul, filigranne)
|
||||
@ -907,7 +899,7 @@ def do_formsemestre_bulletinetud(
|
||||
"""
|
||||
if format == "xml":
|
||||
bul = sco_bulletins_xml.make_xml_formsemestre_bulletinetud(
|
||||
formsemestre_id,
|
||||
formsemestre.id,
|
||||
etudid,
|
||||
xml_with_decisions=xml_with_decisions,
|
||||
force_publishing=force_publishing,
|
||||
@ -918,7 +910,7 @@ def do_formsemestre_bulletinetud(
|
||||
|
||||
elif format == "json":
|
||||
bul = sco_bulletins_json.make_json_formsemestre_bulletinetud(
|
||||
formsemestre_id,
|
||||
formsemestre.id,
|
||||
etudid,
|
||||
xml_with_decisions=xml_with_decisions,
|
||||
force_publishing=force_publishing,
|
||||
@ -926,7 +918,12 @@ def do_formsemestre_bulletinetud(
|
||||
)
|
||||
return bul, ""
|
||||
|
||||
I = formsemestre_bulletinetud_dict(formsemestre_id, etudid)
|
||||
if formsemestre.formation.is_apc():
|
||||
etud = Identite.query.get(etudid)
|
||||
r = bulletin_but.BulletinBUT(formsemestre)
|
||||
I = r.bulletin_etud_complet(etud, formsemestre)
|
||||
else:
|
||||
I = formsemestre_bulletinetud_dict(formsemestre.id, etudid)
|
||||
etud = I["etud"]
|
||||
|
||||
if format == "html":
|
||||
@ -953,7 +950,7 @@ def do_formsemestre_bulletinetud(
|
||||
elif format == "pdfmail":
|
||||
# format pdfmail: envoie le pdf par mail a l'etud, et affiche le html
|
||||
# check permission
|
||||
if not can_send_bulletin_by_mail(formsemestre_id):
|
||||
if not can_send_bulletin_by_mail(formsemestre.id):
|
||||
raise AccessDenied("Vous n'avez pas le droit d'effectuer cette opération !")
|
||||
|
||||
if nohtml:
|
||||
@ -982,7 +979,7 @@ def do_formsemestre_bulletinetud(
|
||||
) + htm
|
||||
return h, I["filigranne"]
|
||||
#
|
||||
mail_bulletin(formsemestre_id, I, pdfdata, filename, recipient_addr)
|
||||
mail_bulletin(formsemestre.id, I, pdfdata, filename, recipient_addr)
|
||||
emaillink = '<a class="stdlink" href="mailto:%s">%s</a>' % (
|
||||
recipient_addr,
|
||||
recipient_addr,
|
||||
|
@ -99,7 +99,7 @@ def bulletin_get_class_name_displayed(formsemestre_id):
|
||||
return "invalide ! (voir paramètres)"
|
||||
|
||||
|
||||
class BulletinGenerator(object):
|
||||
class BulletinGenerator:
|
||||
"Virtual superclass for PDF bulletin generators" ""
|
||||
# Here some helper methods
|
||||
# see sco_bulletins_standard.BulletinGeneratorStandard subclass for real methods
|
||||
|
@ -209,7 +209,7 @@ def formsemestre_bulletinetud_published_dict(
|
||||
acronyme=scu.quote_xml_attr(ue["acronyme"]),
|
||||
titre=scu.quote_xml_attr(ue["titre"]),
|
||||
note=dict(
|
||||
value=scu.fmt_note(ue_status["cur_moy_ue"]),
|
||||
value=scu.fmt_note(ue_status["cur_moy_ue"] if ue_status else ""),
|
||||
min=scu.fmt_note(ue["min"]),
|
||||
max=scu.fmt_note(ue["max"]),
|
||||
moy=scu.fmt_note(
|
||||
@ -254,7 +254,10 @@ def formsemestre_bulletinetud_published_dict(
|
||||
m["note"][k] = scu.fmt_note(m["note"][k])
|
||||
|
||||
u["module"].append(m)
|
||||
if sco_preferences.get_preference("bul_show_mod_rangs", formsemestre_id):
|
||||
if (
|
||||
sco_preferences.get_preference("bul_show_mod_rangs", formsemestre_id)
|
||||
and nt.mod_rangs is not None
|
||||
):
|
||||
m["rang"] = dict(
|
||||
value=nt.mod_rangs[modimpl["moduleimpl_id"]][0][etudid]
|
||||
)
|
||||
|
@ -476,8 +476,8 @@ def _bulletin_pdf_table_legacy(I, version="long"):
|
||||
else:
|
||||
rang_minmax = mod["mod_rang_txt"] # vide si pas option rang
|
||||
t = [
|
||||
mod["code"],
|
||||
mod["name"],
|
||||
mod["code"] or "",
|
||||
mod["name"] or "",
|
||||
rang_minmax,
|
||||
mod["mod_moy_txt"],
|
||||
mod["mod_coef_txt"],
|
||||
|
@ -56,19 +56,22 @@ import time
|
||||
import traceback
|
||||
from pydoc import html
|
||||
|
||||
from reportlab.platypus.doctemplate import PageTemplate, BaseDocTemplate
|
||||
from reportlab.platypus.doctemplate import BaseDocTemplate
|
||||
|
||||
from flask import g, request
|
||||
|
||||
import app.scodoc.sco_utils as scu
|
||||
from app import log, ScoValueError
|
||||
from app.models import FormSemestre
|
||||
|
||||
from app.scodoc import sco_cache
|
||||
from app.scodoc import sco_formsemestre
|
||||
from app.scodoc import sco_codes_parcours
|
||||
from app.scodoc import sco_pdf
|
||||
from app.scodoc import sco_preferences
|
||||
from app.scodoc import sco_etud
|
||||
import sco_version
|
||||
from app.scodoc.sco_logos import find_logo
|
||||
import app.scodoc.sco_utils as scu
|
||||
|
||||
import sco_version
|
||||
|
||||
|
||||
def pdfassemblebulletins(
|
||||
@ -178,22 +181,21 @@ def get_formsemestre_bulletins_pdf(formsemestre_id, version="selectedevals"):
|
||||
if cached:
|
||||
return cached[1], cached[0]
|
||||
fragments = []
|
||||
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
||||
# Make each bulletin
|
||||
nt = sco_cache.NotesTableCache.get(formsemestre_id) # > get_etudids, get_sexnom
|
||||
formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||
bookmarks = {}
|
||||
filigrannes = {}
|
||||
i = 1
|
||||
for etudid in nt.get_etudids():
|
||||
for etud in formsemestre.get_inscrits(include_demdef=True, order=True):
|
||||
frag, filigranne = sco_bulletins.do_formsemestre_bulletinetud(
|
||||
formsemestre_id,
|
||||
etudid,
|
||||
formsemestre,
|
||||
etud.id,
|
||||
format="pdfpart",
|
||||
version=version,
|
||||
)
|
||||
fragments += frag
|
||||
filigrannes[i] = filigranne
|
||||
bookmarks[i] = scu.suppress_accents(nt.get_sexnom(etudid))
|
||||
bookmarks[i] = etud.sex_nom(no_accents=True)
|
||||
i = i + 1
|
||||
#
|
||||
infos = {"DeptName": sco_preferences.get_preference("DeptName", formsemestre_id)}
|
||||
@ -206,7 +208,7 @@ def get_formsemestre_bulletins_pdf(formsemestre_id, version="selectedevals"):
|
||||
pdfdoc = pdfassemblebulletins(
|
||||
formsemestre_id,
|
||||
fragments,
|
||||
sem["titremois"],
|
||||
formsemestre.titre_mois(),
|
||||
infos,
|
||||
bookmarks,
|
||||
filigranne=filigrannes,
|
||||
@ -216,7 +218,7 @@ def get_formsemestre_bulletins_pdf(formsemestre_id, version="selectedevals"):
|
||||
sco_pdf.PDFLOCK.release()
|
||||
#
|
||||
dt = time.strftime("%Y-%m-%d")
|
||||
filename = "bul-%s-%s.pdf" % (sem["titre_num"], dt)
|
||||
filename = "bul-%s-%s.pdf" % (formsemestre.titre_num(), dt)
|
||||
filename = scu.unescape_html(filename).replace(" ", "_").replace("&", "")
|
||||
# fill cache
|
||||
sco_cache.SemBulletinsPDFCache.set(
|
||||
@ -235,8 +237,9 @@ def get_etud_bulletins_pdf(etudid, version="selectedevals"):
|
||||
filigrannes = {}
|
||||
i = 1
|
||||
for sem in etud["sems"]:
|
||||
formsemestre = FormSemestre.query.get(sem["formsemestre_id"])
|
||||
frag, filigranne = sco_bulletins.do_formsemestre_bulletinetud(
|
||||
sem["formsemestre_id"],
|
||||
formsemestre,
|
||||
etudid,
|
||||
format="pdfpart",
|
||||
version=version,
|
||||
@ -271,3 +274,16 @@ def get_etud_bulletins_pdf(etudid, version="selectedevals"):
|
||||
)
|
||||
|
||||
return pdfdoc, filename
|
||||
|
||||
|
||||
def get_filigranne(etud_etat: str, prefs) -> str:
|
||||
"""Texte à placer en "filigranne" sur le bulletin pdf"""
|
||||
if etud_etat == scu.DEMISSION:
|
||||
return "Démission"
|
||||
elif etud_etat == sco_codes_parcours.DEF:
|
||||
return "Défaillant"
|
||||
elif (prefs["bul_show_temporary"] and not I["decision_sem"]) or prefs[
|
||||
"bul_show_temporary_forced"
|
||||
]:
|
||||
return prefs["bul_temporary_txt"]
|
||||
return ""
|
||||
|
@ -471,9 +471,10 @@ class BulletinGeneratorStandard(sco_bulletins_generator.BulletinGenerator):
|
||||
ects_txt = str(int(ue["ects"]))
|
||||
except:
|
||||
ects_txt = "-"
|
||||
titre = f"{ue['acronyme'] or ''} {ue['titre'] or ''}"
|
||||
t = {
|
||||
"titre": ue["acronyme"] + " " + ue["titre"],
|
||||
"_titre_html": minuslink + ue["acronyme"] + " " + ue["titre"],
|
||||
"titre": titre,
|
||||
"_titre_html": minuslink + titre,
|
||||
"_titre_colspan": 2,
|
||||
"module": ue["titre"],
|
||||
"rang": ue_descr,
|
||||
|
@ -217,7 +217,7 @@ def make_xml_formsemestre_bulletinetud(
|
||||
)
|
||||
doc.append(x_ue)
|
||||
if ue["type"] != sco_codes_parcours.UE_SPORT:
|
||||
v = ue_status["cur_moy_ue"]
|
||||
v = ue_status["cur_moy_ue"] if ue_status else ""
|
||||
else:
|
||||
v = nt.bonus[etudid] if nt.bonus is not None else 0.0
|
||||
x_ue.append(
|
||||
@ -252,7 +252,7 @@ def make_xml_formsemestre_bulletinetud(
|
||||
x_mod = Element(
|
||||
"module",
|
||||
id=str(modimpl["moduleimpl_id"]),
|
||||
code=str(mod["code"]),
|
||||
code=str(mod["code"] or ""),
|
||||
coefficient=str(mod["coefficient"]),
|
||||
numero=str(mod["numero"]),
|
||||
titre=scu.quote_xml_attr(mod["titre"]),
|
||||
@ -271,7 +271,10 @@ def make_xml_formsemestre_bulletinetud(
|
||||
moy=scu.fmt_note(modstat["moy"]),
|
||||
)
|
||||
)
|
||||
if sco_preferences.get_preference("bul_show_mod_rangs", formsemestre_id):
|
||||
if (
|
||||
sco_preferences.get_preference("bul_show_mod_rangs", formsemestre_id)
|
||||
and nt.mod_rangs is not None
|
||||
):
|
||||
x_mod.append(
|
||||
Element(
|
||||
"rang",
|
||||
|
@ -65,7 +65,7 @@ def formsemestre_table_estim_cost(
|
||||
Mod = M["module"]
|
||||
T.append(
|
||||
{
|
||||
"code": Mod["code"],
|
||||
"code": Mod["code"] or "",
|
||||
"titre": Mod["titre"],
|
||||
"heures_cours": Mod["heures_cours"],
|
||||
"heures_td": Mod["heures_td"] * n_group_td,
|
||||
|
@ -31,15 +31,17 @@ Rapport (table) avec dernier semestre fréquenté et débouché de chaque étudi
|
||||
import http
|
||||
from flask import url_for, g, request
|
||||
|
||||
from app import log
|
||||
from app.comp import res_sem
|
||||
from app.comp.res_common import NotesTableCompat
|
||||
from app.models import FormSemestre
|
||||
import app.scodoc.sco_utils as scu
|
||||
import app.scodoc.notesdb as ndb
|
||||
from app import log
|
||||
from app.scodoc.sco_exceptions import AccessDenied
|
||||
from app.scodoc.scolog import logdb
|
||||
from app.scodoc.gen_tables import GenTable
|
||||
from app.scodoc import safehtml
|
||||
from app.scodoc import html_sco_header
|
||||
from app.scodoc import sco_cache
|
||||
from app.scodoc import sco_permissions_check
|
||||
from app.scodoc import sco_preferences
|
||||
from app.scodoc import sco_tag_module
|
||||
@ -115,7 +117,7 @@ def get_etudids_with_debouche(start_year):
|
||||
|
||||
|
||||
def table_debouche_etudids(etudids, keep_numeric=True):
|
||||
"""Rapport pour ces etudiants"""
|
||||
"""Rapport pour ces étudiants"""
|
||||
L = []
|
||||
for etudid in etudids:
|
||||
etud = sco_etud.get_etud_info(filled=True, etudid=etudid)[0]
|
||||
@ -124,7 +126,8 @@ def table_debouche_etudids(etudids, keep_numeric=True):
|
||||
es = [(s["date_fin_iso"], i) for i, s in enumerate(sems)]
|
||||
imax = max(es)[1]
|
||||
last_sem = sems[imax]
|
||||
nt = sco_cache.NotesTableCache.get(last_sem["formsemestre_id"])
|
||||
formsemestre = FormSemestre.query.get_or_404(last_sem["formsemestre_id"])
|
||||
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
||||
row = {
|
||||
"etudid": etudid,
|
||||
"civilite": etud["civilite"],
|
||||
|
@ -365,6 +365,7 @@ def ue_move(ue_id, after=0, redirect=1):
|
||||
if len({o.numero for o in others}) != len(others):
|
||||
# il y a des numeros identiques !
|
||||
scu.objects_renumber(db, others)
|
||||
ue.formation.invalidate_cached_sems()
|
||||
if len(others) > 1:
|
||||
idx = [u.id for u in others].index(ue.id)
|
||||
neigh = None # object to swap with
|
||||
|
@ -88,13 +88,14 @@ def do_matiere_create(args):
|
||||
r = _matiereEditor.create(cnx, args)
|
||||
|
||||
# news
|
||||
F = sco_formations.formation_list(args={"formation_id": ue["formation_id"]})[0]
|
||||
formation = Formation.query.get(ue["formation_id"])
|
||||
sco_news.add(
|
||||
typ=sco_news.NEWS_FORM,
|
||||
object=ue["formation_id"],
|
||||
text="Modification de la formation %(acronyme)s" % F,
|
||||
text="Modification de la formation {formation.acronyme}",
|
||||
max_frequency=3,
|
||||
)
|
||||
formation.invalidate_cached_sems()
|
||||
return r
|
||||
|
||||
|
||||
@ -195,13 +196,14 @@ def do_matiere_delete(oid):
|
||||
_matiereEditor.delete(cnx, oid)
|
||||
|
||||
# news
|
||||
F = sco_formations.formation_list(args={"formation_id": ue["formation_id"]})[0]
|
||||
formation = Formation.query.get(ue["formation_id"])
|
||||
sco_news.add(
|
||||
typ=sco_news.NEWS_FORM,
|
||||
object=ue["formation_id"],
|
||||
text="Modification de la formation %(acronyme)s" % F,
|
||||
text="Modification de la formation {formation.acronyme}",
|
||||
max_frequency=3,
|
||||
)
|
||||
formation.invalidate_cached_sems()
|
||||
|
||||
|
||||
def matiere_delete(matiere_id=None):
|
||||
|
@ -41,7 +41,6 @@ from app.models import FormSemestre, ModuleImpl
|
||||
|
||||
import app.scodoc.notesdb as ndb
|
||||
import app.scodoc.sco_utils as scu
|
||||
from app.scodoc.sco_utils import ModuleType
|
||||
from app.scodoc.TrivialFormulator import TrivialFormulator
|
||||
from app.scodoc.sco_permissions import Permission
|
||||
from app.scodoc.sco_exceptions import (
|
||||
@ -105,13 +104,14 @@ def do_module_create(args) -> int:
|
||||
r = _moduleEditor.create(cnx, args)
|
||||
|
||||
# news
|
||||
F = sco_formations.formation_list(args={"formation_id": args["formation_id"]})[0]
|
||||
formation = Formation.query.get(args["formation_id"])
|
||||
sco_news.add(
|
||||
typ=sco_news.NEWS_FORM,
|
||||
object=args["formation_id"],
|
||||
text="Modification de la formation %(acronyme)s" % F,
|
||||
object=formation.id,
|
||||
text=f"Modification de la formation {formation.acronyme}",
|
||||
max_frequency=3,
|
||||
)
|
||||
formation.invalidate_cached_sems()
|
||||
return r
|
||||
|
||||
|
||||
@ -196,7 +196,6 @@ def module_create(
|
||||
},
|
||||
),
|
||||
]
|
||||
semestres_indices = list(range(1, parcours.NB_SEM + 1))
|
||||
|
||||
if is_apc:
|
||||
module_types = scu.ModuleType # tous les types
|
||||
@ -396,13 +395,14 @@ def do_module_delete(oid):
|
||||
_moduleEditor.delete(cnx, oid)
|
||||
|
||||
# news
|
||||
F = sco_formations.formation_list(args={"formation_id": mod["formation_id"]})[0]
|
||||
formation = module.formation
|
||||
sco_news.add(
|
||||
typ=sco_news.NEWS_FORM,
|
||||
object=mod["formation_id"],
|
||||
text="Modification de la formation %(acronyme)s" % F,
|
||||
text=f"Modification de la formation {formation.acronyme}",
|
||||
max_frequency=3,
|
||||
)
|
||||
formation.invalidate_cached_sems()
|
||||
|
||||
|
||||
def module_delete(module_id=None):
|
||||
@ -451,8 +451,6 @@ def module_delete(module_id=None):
|
||||
|
||||
def do_module_edit(vals: dict) -> None:
|
||||
"edit a module"
|
||||
from app.scodoc import sco_edit_formation
|
||||
|
||||
# check
|
||||
mod = module_list({"module_id": vals["module_id"]})[0]
|
||||
if module_is_locked(mod["module_id"]):
|
||||
@ -546,7 +544,7 @@ def module_edit(module_id=None):
|
||||
# ne propose pas SAE et Ressources, sauf si déjà de ce type...
|
||||
module_types = (
|
||||
set(scu.ModuleType) - {scu.ModuleType.RESSOURCE, scu.ModuleType.SAE}
|
||||
) | {a_module.module_type}
|
||||
) | {a_module.module_type or scu.ModuleType.STANDARD}
|
||||
|
||||
descr = [
|
||||
(
|
||||
@ -845,7 +843,7 @@ def formation_add_malus_modules(formation_id, titre=None, redirect=True):
|
||||
[
|
||||
mod
|
||||
for mod in module_list(args={"ue_id": ue["ue_id"]})
|
||||
if mod["module_type"] == ModuleType.MALUS
|
||||
if mod["module_type"] == scu.ModuleType.MALUS
|
||||
]
|
||||
)
|
||||
if nb_mod_malus == 0:
|
||||
@ -897,7 +895,7 @@ def ue_add_malus_module(ue_id, titre=None, code=None):
|
||||
"matiere_id": matiere_id,
|
||||
"formation_id": ue["formation_id"],
|
||||
"semestre_id": semestre_id,
|
||||
"module_type": ModuleType.MALUS,
|
||||
"module_type": scu.ModuleType.MALUS,
|
||||
},
|
||||
)
|
||||
|
||||
|
@ -128,13 +128,14 @@ def do_ue_create(args):
|
||||
formation = Formation.query.get(args["formation_id"])
|
||||
formation.invalidate_module_coefs()
|
||||
# news
|
||||
F = sco_formations.formation_list(args={"formation_id": args["formation_id"]})[0]
|
||||
formation = Formation.query.get(args["formation_id"])
|
||||
sco_news.add(
|
||||
typ=sco_news.NEWS_FORM,
|
||||
object=args["formation_id"],
|
||||
text="Modification de la formation %(acronyme)s" % F,
|
||||
text="Modification de la formation {formation.acronyme}",
|
||||
max_frequency=3,
|
||||
)
|
||||
formation.invalidate_cached_sems()
|
||||
return ue_id
|
||||
|
||||
|
||||
@ -250,7 +251,7 @@ def ue_edit(ue_id=None, create=False, formation_id=None, default_semestre_idx=No
|
||||
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
|
||||
can_change_semestre_id = (ue.modules.count() == 0) or (ue.semestre_idx is None)
|
||||
else:
|
||||
ue = None
|
||||
title = "Création d'une UE"
|
||||
@ -361,7 +362,7 @@ def ue_edit(ue_id=None, create=False, formation_id=None, default_semestre_idx=No
|
||||
{
|
||||
"size": 12,
|
||||
"title": "Code UE",
|
||||
"explanation": "code interne (optionnel). Toutes les UE partageant le même code (et le même code de formation) sont compatibles (compensation de semestres, capitalisation d'UE). Voir liste ci-dessous.",
|
||||
"explanation": "code interne (non vide). Toutes les UE partageant le même code (et le même code de formation) sont compatibles (compensation de semestres, capitalisation d'UE). Voir liste ci-dessous.",
|
||||
},
|
||||
),
|
||||
(
|
||||
@ -410,7 +411,7 @@ def ue_edit(ue_id=None, create=False, formation_id=None, default_semestre_idx=No
|
||||
submitlabel=submitlabel,
|
||||
)
|
||||
if tf[0] == 0:
|
||||
if ue and ue.modules.count():
|
||||
if ue and ue.modules.count() and ue.semestre_idx is not None:
|
||||
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},
|
||||
@ -575,7 +576,9 @@ def ue_table(formation_id=None, semestre_idx=1, msg=""): # was ue_list
|
||||
semestre_ids = range(1, parcours.NB_SEM + 1)
|
||||
# transition: on requete ici via l'ORM mais on utilise les fonctions ScoDoc7
|
||||
# basées sur des dicts
|
||||
ues_obj = UniteEns.query.filter_by(formation_id=formation_id, is_external=False)
|
||||
ues_obj = UniteEns.query.filter_by(
|
||||
formation_id=formation_id, is_external=False
|
||||
).order_by(UniteEns.semestre_idx, UniteEns.numero)
|
||||
ues_externes_obj = UniteEns.query.filter_by(
|
||||
formation_id=formation_id, is_external=True
|
||||
)
|
||||
@ -1415,15 +1418,15 @@ def ue_list_semestre_ids(ue: dict):
|
||||
|
||||
|
||||
UE_PALETTE = [
|
||||
"#EFA00B",
|
||||
"#99C24D",
|
||||
"#EC9192",
|
||||
"#0075C4",
|
||||
"#D65108",
|
||||
"#DEC0F1",
|
||||
"#B02E0C",
|
||||
"#151E3F",
|
||||
"#FB3640",
|
||||
"#B80004", # rouge
|
||||
"#F97B3D", # Orange Crayola
|
||||
"#FEB40B", # Honey Yellow
|
||||
"#80CB3F", # Yellow Green
|
||||
"#05162E", # Oxford Blue
|
||||
"#548687", # Steel Teal
|
||||
"#444054", # Independence
|
||||
"#889696", # Spanish Gray
|
||||
"#0CA4A5", # Viridian Green
|
||||
]
|
||||
|
||||
|
||||
@ -1436,8 +1439,8 @@ def colorie_anciennes_ues(ues: list[UniteEns]) -> None:
|
||||
last_sem_idx = 0
|
||||
for ue in ues:
|
||||
if ue.semestre_idx != last_sem_idx:
|
||||
last_sem_idx = ue.semestre_idx
|
||||
index = 0
|
||||
last_sem_idx = ue.semestre_idx
|
||||
if ue.color is None:
|
||||
ue.color = UE_PALETTE[index % nb_colors]
|
||||
index += 1
|
||||
|
@ -384,8 +384,8 @@ print apo_csv_list_stored_archives()
|
||||
|
||||
groups_infos = sco_groups_view.DisplayedGroupsInfos( [sco_groups.get_default_group(formsemestre_id)], formsemestre_id=formsemestre_id)
|
||||
|
||||
nt = sco_cache.NotesTableCache.get( formsemestre_id)
|
||||
|
||||
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
||||
#
|
||||
s = SemSet('NSS29902')
|
||||
apo_data = sco_apogee_csv.ApoData(open('/opt/scodoc/var/scodoc/archives/apo_csv/RT/2015-2/2016-07-10-11-26-15/V1RT.csv').read(), periode=1)
|
||||
|
@ -38,6 +38,7 @@ from flask_mail import Message
|
||||
|
||||
from app import email
|
||||
from app import log
|
||||
from app.models import Admission
|
||||
from app.models.etudiants import make_etud_args
|
||||
import app.scodoc.sco_utils as scu
|
||||
import app.scodoc.notesdb as ndb
|
||||
@ -859,19 +860,24 @@ def list_scolog(etudid):
|
||||
return cursor.dictfetchall()
|
||||
|
||||
|
||||
def fill_etuds_info(etuds):
|
||||
def fill_etuds_info(etuds, add_admission=True):
|
||||
"""etuds est une liste d'etudiants (mappings)
|
||||
Pour chaque etudiant, ajoute ou formatte les champs
|
||||
-> informations pour fiche etudiant ou listes diverses
|
||||
"""
|
||||
from app.scodoc import sco_formsemestre
|
||||
from app.scodoc import sco_formsemestre_inscriptions
|
||||
|
||||
Si add_admission: ajoute au dict le schamps "admission" s'il n'y sont pas déjà.
|
||||
"""
|
||||
cnx = ndb.GetDBConnexion()
|
||||
# open('/tmp/t','w').write( str(etuds) )
|
||||
for etud in etuds:
|
||||
etudid = etud["etudid"]
|
||||
etud["dept"] = g.scodoc_dept
|
||||
# Admission
|
||||
if add_admission and "nomlycee" not in etud:
|
||||
admission = (
|
||||
Admission.query.filter_by(etudid=etudid).first().to_dict(no_nulls=True)
|
||||
)
|
||||
etud.update(admission)
|
||||
#
|
||||
adrs = adresse_list(cnx, {"etudid": etudid})
|
||||
if not adrs:
|
||||
# certains "vieux" etudiants n'ont pas d'adresse
|
||||
@ -884,6 +890,50 @@ def fill_etuds_info(etuds):
|
||||
etud.update(adr)
|
||||
format_etud_ident(etud)
|
||||
|
||||
etud.update(etud_inscriptions_infos(etudid, etud["ne"]))
|
||||
|
||||
# nettoyage champs souvent vides
|
||||
if etud.get("nomlycee"):
|
||||
etud["ilycee"] = "Lycée " + format_lycee(etud["nomlycee"])
|
||||
if etud["villelycee"]:
|
||||
etud["ilycee"] += " (%s)" % etud.get("villelycee", "")
|
||||
etud["ilycee"] += "<br/>"
|
||||
else:
|
||||
if etud.get("codelycee"):
|
||||
etud["ilycee"] = format_lycee_from_code(etud["codelycee"])
|
||||
else:
|
||||
etud["ilycee"] = ""
|
||||
rap = ""
|
||||
if etud.get("rapporteur") or etud.get("commentaire"):
|
||||
rap = "Note du rapporteur"
|
||||
if etud.get("rapporteur"):
|
||||
rap += " (%s)" % etud["rapporteur"]
|
||||
rap += ": "
|
||||
if etud.get("commentaire"):
|
||||
rap += "<em>%s</em>" % etud["commentaire"]
|
||||
etud["rap"] = rap
|
||||
|
||||
# if etud['boursier_prec']:
|
||||
# pass
|
||||
|
||||
if etud.get("telephone"):
|
||||
etud["telephonestr"] = "<b>Tél.:</b> " + format_telephone(etud["telephone"])
|
||||
else:
|
||||
etud["telephonestr"] = ""
|
||||
if etud.get("telephonemobile"):
|
||||
etud["telephonemobilestr"] = "<b>Mobile:</b> " + format_telephone(
|
||||
etud["telephonemobile"]
|
||||
)
|
||||
else:
|
||||
etud["telephonemobilestr"] = ""
|
||||
|
||||
|
||||
def etud_inscriptions_infos(etudid: int, ne="") -> dict:
|
||||
"""Dict avec les informations sur les semestres passés et courant"""
|
||||
from app.scodoc import sco_formsemestre
|
||||
from app.scodoc import sco_formsemestre_inscriptions
|
||||
|
||||
etud = {}
|
||||
# Semestres dans lesquel il est inscrit
|
||||
ins = sco_formsemestre_inscriptions.do_formsemestre_inscription_list(
|
||||
{"etudid": etudid}
|
||||
@ -908,13 +958,10 @@ def fill_etuds_info(etuds):
|
||||
etud["inscriptionstr"] = "Inscrit en " + cursem["titremois"]
|
||||
etud["inscription_formsemestre_id"] = cursem["formsemestre_id"]
|
||||
etud["etatincursem"] = curi["etat"]
|
||||
etud["situation"] = descr_situation_etud(etudid, etud["ne"])
|
||||
# XXX est-ce utile ? sco_groups.etud_add_group_infos( etud, cursem)
|
||||
etud["situation"] = descr_situation_etud(etudid, ne)
|
||||
else:
|
||||
if etud["sems"]:
|
||||
if etud["sems"][0]["dateord"] > time.strftime(
|
||||
"%Y-%m-%d", time.localtime()
|
||||
):
|
||||
if etud["sems"][0]["dateord"] > time.strftime("%Y-%m-%d", time.localtime()):
|
||||
etud["inscription"] = "futur"
|
||||
etud["situation"] = "futur élève"
|
||||
else:
|
||||
@ -925,47 +972,12 @@ def fill_etuds_info(etuds):
|
||||
etud["situation"] = etud["inscription"]
|
||||
etud["inscriptionstr"] = etud["inscription"]
|
||||
etud["inscription_formsemestre_id"] = None
|
||||
# XXXetud['partitions'] = {} # ne va pas chercher les groupes des anciens semestres
|
||||
etud["etatincursem"] = "?"
|
||||
|
||||
# nettoyage champs souvents vides
|
||||
if etud["nomlycee"]:
|
||||
etud["ilycee"] = "Lycée " + format_lycee(etud["nomlycee"])
|
||||
if etud["villelycee"]:
|
||||
etud["ilycee"] += " (%s)" % etud["villelycee"]
|
||||
etud["ilycee"] += "<br/>"
|
||||
else:
|
||||
if etud["codelycee"]:
|
||||
etud["ilycee"] = format_lycee_from_code(etud["codelycee"])
|
||||
else:
|
||||
etud["ilycee"] = ""
|
||||
rap = ""
|
||||
if etud["rapporteur"] or etud["commentaire"]:
|
||||
rap = "Note du rapporteur"
|
||||
if etud["rapporteur"]:
|
||||
rap += " (%s)" % etud["rapporteur"]
|
||||
rap += ": "
|
||||
if etud["commentaire"]:
|
||||
rap += "<em>%s</em>" % etud["commentaire"]
|
||||
etud["rap"] = rap
|
||||
|
||||
# if etud['boursier_prec']:
|
||||
# pass
|
||||
|
||||
if etud["telephone"]:
|
||||
etud["telephonestr"] = "<b>Tél.:</b> " + format_telephone(etud["telephone"])
|
||||
else:
|
||||
etud["telephonestr"] = ""
|
||||
if etud["telephonemobile"]:
|
||||
etud["telephonemobilestr"] = "<b>Mobile:</b> " + format_telephone(
|
||||
etud["telephonemobile"]
|
||||
)
|
||||
else:
|
||||
etud["telephonemobilestr"] = ""
|
||||
return etud
|
||||
|
||||
|
||||
def descr_situation_etud(etudid, ne=""):
|
||||
"""chaine decrivant la situation actuelle de l'etudiant"""
|
||||
def descr_situation_etud(etudid: int, ne="") -> str:
|
||||
"""chaîne décrivant la situation actuelle de l'étudiant"""
|
||||
from app.scodoc import sco_formsemestre
|
||||
|
||||
cnx = ndb.GetDBConnexion()
|
||||
@ -982,7 +994,7 @@ def descr_situation_etud(etudid, ne=""):
|
||||
)
|
||||
r = cursor.dictfetchone()
|
||||
if not r:
|
||||
situation = "non inscrit"
|
||||
situation = "non inscrit" + ne
|
||||
else:
|
||||
sem = sco_formsemestre.get_formsemestre(r["formsemestre_id"])
|
||||
if r["etat"] == "I":
|
||||
|
@ -237,7 +237,11 @@ def formsemestre_check_absences_html(formsemestre_id):
|
||||
if evals:
|
||||
H.append(
|
||||
'<div class="module_check_absences"><h2><a href="moduleimpl_status?moduleimpl_id=%s">%s: %s</a></h2>'
|
||||
% (M["moduleimpl_id"], M["module"]["code"], M["module"]["abbrev"])
|
||||
% (
|
||||
M["moduleimpl_id"],
|
||||
M["module"]["code"] or "",
|
||||
M["module"]["abbrev"] or "",
|
||||
)
|
||||
)
|
||||
for E in evals:
|
||||
H.append(
|
||||
|
@ -36,6 +36,8 @@ from flask import url_for, g
|
||||
from flask_login import current_user
|
||||
|
||||
from app import log
|
||||
|
||||
from app.models.evaluations import evaluation_enrich_dict, check_evaluation_args
|
||||
import app.scodoc.sco_utils as scu
|
||||
import app.scodoc.notesdb as ndb
|
||||
from app.scodoc.sco_exceptions import AccessDenied, ScoValueError
|
||||
@ -81,43 +83,6 @@ _evaluationEditor = ndb.EditableTable(
|
||||
)
|
||||
|
||||
|
||||
def evaluation_enrich_dict(e):
|
||||
"""add or convert some fileds in an evaluation dict"""
|
||||
# For ScoDoc7 compat
|
||||
heure_debut_dt = e["heure_debut"] or datetime.time(
|
||||
8, 00
|
||||
) # au cas ou pas d'heure (note externe?)
|
||||
heure_fin_dt = e["heure_fin"] or datetime.time(8, 00)
|
||||
e["heure_debut"] = ndb.TimefromISO8601(e["heure_debut"])
|
||||
e["heure_fin"] = ndb.TimefromISO8601(e["heure_fin"])
|
||||
e["jouriso"] = ndb.DateDMYtoISO(e["jour"])
|
||||
heure_debut, heure_fin = e["heure_debut"], e["heure_fin"]
|
||||
d = ndb.TimeDuration(heure_debut, heure_fin)
|
||||
if d is not None:
|
||||
m = d % 60
|
||||
e["duree"] = "%dh" % (d / 60)
|
||||
if m != 0:
|
||||
e["duree"] += "%02d" % m
|
||||
else:
|
||||
e["duree"] = ""
|
||||
if heure_debut and (not heure_fin or heure_fin == heure_debut):
|
||||
e["descrheure"] = " à " + heure_debut
|
||||
elif heure_debut and heure_fin:
|
||||
e["descrheure"] = " de %s à %s" % (heure_debut, heure_fin)
|
||||
else:
|
||||
e["descrheure"] = ""
|
||||
# matin, apresmidi: utile pour se referer aux absences:
|
||||
if heure_debut_dt < datetime.time(12, 00):
|
||||
e["matin"] = 1
|
||||
else:
|
||||
e["matin"] = 0
|
||||
if heure_fin_dt > datetime.time(12, 00):
|
||||
e["apresmidi"] = 1
|
||||
else:
|
||||
e["apresmidi"] = 0
|
||||
return e
|
||||
|
||||
|
||||
def do_evaluation_list(args, sortkey=None):
|
||||
"""List evaluations, sorted by numero (or most recent date first).
|
||||
|
||||
@ -127,7 +92,7 @@ def do_evaluation_list(args, sortkey=None):
|
||||
'apresmidi' : 1 (termine après 12:00) ou 0
|
||||
'descrheure' : ' de 15h00 à 16h30'
|
||||
"""
|
||||
# Attention: transformation fonction ScoDc7 en SQLAlchemy
|
||||
# Attention: transformation fonction ScoDoc7 en SQLAlchemy
|
||||
cnx = ndb.GetDBConnexion()
|
||||
evals = _evaluationEditor.list(cnx, args, sortkey=sortkey)
|
||||
# calcule duree (chaine de car.) de chaque evaluation et ajoute jouriso, matin, apresmidi
|
||||
@ -146,59 +111,6 @@ def do_evaluation_list_in_formsemestre(formsemestre_id):
|
||||
return evals
|
||||
|
||||
|
||||
def _check_evaluation_args(args):
|
||||
"Check coefficient, dates and duration, raises exception if invalid"
|
||||
moduleimpl_id = args["moduleimpl_id"]
|
||||
# check bareme
|
||||
note_max = args.get("note_max", None)
|
||||
if note_max is None:
|
||||
raise ScoValueError("missing note_max")
|
||||
try:
|
||||
note_max = float(note_max)
|
||||
except ValueError:
|
||||
raise ScoValueError("Invalid note_max value")
|
||||
if note_max < 0:
|
||||
raise ScoValueError("Invalid note_max value (must be positive or null)")
|
||||
# check coefficient
|
||||
coef = args.get("coefficient", None)
|
||||
if coef is None:
|
||||
raise ScoValueError("missing coefficient")
|
||||
try:
|
||||
coef = float(coef)
|
||||
except ValueError:
|
||||
raise ScoValueError("Invalid coefficient value")
|
||||
if coef < 0:
|
||||
raise ScoValueError("Invalid coefficient value (must be positive or null)")
|
||||
# check date
|
||||
jour = args.get("jour", None)
|
||||
args["jour"] = jour
|
||||
if jour:
|
||||
M = sco_moduleimpl.moduleimpl_list(moduleimpl_id=moduleimpl_id)[0]
|
||||
sem = sco_formsemestre.get_formsemestre(M["formsemestre_id"])
|
||||
d, m, y = [int(x) for x in sem["date_debut"].split("/")]
|
||||
date_debut = datetime.date(y, m, d)
|
||||
d, m, y = [int(x) for x in sem["date_fin"].split("/")]
|
||||
date_fin = datetime.date(y, m, d)
|
||||
# passe par ndb.DateDMYtoISO pour avoir date pivot
|
||||
y, m, d = [int(x) for x in ndb.DateDMYtoISO(jour).split("-")]
|
||||
jour = datetime.date(y, m, d)
|
||||
if (jour > date_fin) or (jour < date_debut):
|
||||
raise ScoValueError(
|
||||
"La date de l'évaluation (%s/%s/%s) n'est pas dans le semestre !"
|
||||
% (d, m, y),
|
||||
dest_url="javascript:history.back();",
|
||||
)
|
||||
heure_debut = args.get("heure_debut", None)
|
||||
args["heure_debut"] = heure_debut
|
||||
heure_fin = args.get("heure_fin", None)
|
||||
args["heure_fin"] = heure_fin
|
||||
if jour and ((not heure_debut) or (not heure_fin)):
|
||||
raise ScoValueError("Les heures doivent être précisées")
|
||||
d = ndb.TimeDuration(heure_debut, heure_fin)
|
||||
if d and ((d < 0) or (d > 60 * 12)):
|
||||
raise ScoValueError("Heures de l'évaluation incohérentes !")
|
||||
|
||||
|
||||
def do_evaluation_create(
|
||||
moduleimpl_id=None,
|
||||
jour=None,
|
||||
@ -220,7 +132,7 @@ def do_evaluation_create(
|
||||
)
|
||||
args = locals()
|
||||
log("do_evaluation_create: args=" + str(args))
|
||||
_check_evaluation_args(args)
|
||||
check_evaluation_args(args)
|
||||
# Check numeros
|
||||
module_evaluation_renumber(moduleimpl_id, only_if_unumbered=True)
|
||||
if not "numero" in args or args["numero"] is None:
|
||||
@ -263,6 +175,7 @@ def do_evaluation_create(
|
||||
|
||||
# news
|
||||
M = sco_moduleimpl.moduleimpl_list(moduleimpl_id=moduleimpl_id)[0]
|
||||
sco_cache.invalidate_formsemestre(formsemestre_id=M["formsemestre_id"])
|
||||
mod = sco_edit_module.module_list(args={"module_id": M["module_id"]})[0]
|
||||
mod["moduleimpl_id"] = M["moduleimpl_id"]
|
||||
mod["url"] = "Notes/moduleimpl_status?moduleimpl_id=%(moduleimpl_id)s" % mod
|
||||
@ -288,7 +201,7 @@ def do_evaluation_edit(args):
|
||||
"Modification évaluation impossible pour %s" % current_user.get_nomplogin()
|
||||
)
|
||||
args["moduleimpl_id"] = moduleimpl_id
|
||||
_check_evaluation_args(args)
|
||||
check_evaluation_args(args)
|
||||
|
||||
cnx = ndb.GetDBConnexion()
|
||||
_evaluationEditor.edit(cnx, args)
|
||||
|
@ -122,7 +122,7 @@ def evaluation_create_form(
|
||||
#
|
||||
mod_descr = '<a href="moduleimpl_status?moduleimpl_id=%s">%s %s</a> %s' % (
|
||||
moduleimpl_id,
|
||||
mod["code"],
|
||||
mod["code"] or "module sans code",
|
||||
mod["titre"],
|
||||
link,
|
||||
)
|
||||
|
@ -403,8 +403,9 @@ def do_evaluation_etat_in_mod(nt, moduleimpl_id):
|
||||
|
||||
def formsemestre_evaluations_cal(formsemestre_id):
|
||||
"""Page avec calendrier de toutes les evaluations de ce semestre"""
|
||||
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
||||
nt = sco_cache.NotesTableCache.get(formsemestre_id) # > liste evaluations
|
||||
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
||||
sem = formsemestre.to_dict()
|
||||
|
||||
evals = nt.get_evaluations_etats()
|
||||
nb_evals = len(evals)
|
||||
@ -538,8 +539,9 @@ def formsemestre_evaluations_delai_correction(formsemestre_id, format="html"):
|
||||
|
||||
N'indique pas les évaluations de ratrapage ni celles des modules de bonus/malus.
|
||||
"""
|
||||
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
||||
nt = sco_cache.NotesTableCache.get(formsemestre_id) # > liste evaluations
|
||||
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
||||
sem = formsemestre.to_dict()
|
||||
|
||||
evals = nt.get_evaluations_etats()
|
||||
T = []
|
||||
@ -635,7 +637,14 @@ def evaluation_describe(evaluation_id="", edit_in_place=True):
|
||||
)
|
||||
mod_descr = (
|
||||
'<a href="moduleimpl_status?moduleimpl_id=%s">%s %s</a> <span class="resp">(resp. <a title="%s">%s</a>)</span> %s'
|
||||
% (moduleimpl_id, Mod["code"], Mod["titre"], nomcomplet, resp, link)
|
||||
% (
|
||||
moduleimpl_id,
|
||||
Mod["code"] or "",
|
||||
Mod["titre"] or "?",
|
||||
nomcomplet,
|
||||
resp,
|
||||
link,
|
||||
)
|
||||
)
|
||||
|
||||
etit = E["description"] or ""
|
||||
|
@ -29,6 +29,9 @@
|
||||
"""
|
||||
from flask import url_for, g, request
|
||||
|
||||
from app.comp import res_sem
|
||||
from app.comp.res_common import NotesTableCompat
|
||||
from app.models import FormSemestre
|
||||
import app.scodoc.notesdb as ndb
|
||||
import app.scodoc.sco_utils as scu
|
||||
from app import log
|
||||
@ -77,7 +80,8 @@ def _build_results_table(start_date=None, end_date=None, types_parcours=[]):
|
||||
{}
|
||||
) # etudid : { formsemestre_id d'inscription le plus recent dans les dates considérées, etud }
|
||||
for formsemestre_id in formsemestre_ids_parcours:
|
||||
nt = sco_cache.NotesTableCache.get(formsemestre_id) # > get_etudids
|
||||
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
||||
etudids = nt.get_etudids()
|
||||
for etudid in etudids:
|
||||
if etudid not in etuds_infos: # pas encore traité ?
|
||||
|
@ -601,7 +601,7 @@ def do_formsemestre_createwithmodules(edit=False):
|
||||
"input_type": "text_suggest",
|
||||
"size": 50,
|
||||
"withcheckbox": True,
|
||||
"title": "%s %s" % (mod["code"], mod["titre"]),
|
||||
"title": "%s %s" % (mod["code"] or "", mod["titre"] or ""),
|
||||
"allowed_values": allowed_user_names,
|
||||
"template": itemtemplate,
|
||||
"text_suggest_options": {
|
||||
@ -802,7 +802,9 @@ def do_formsemestre_createwithmodules(edit=False):
|
||||
}
|
||||
moduleimpl_id = sco_moduleimpl.do_moduleimpl_create(modargs)
|
||||
mod = sco_edit_module.module_list({"module_id": module_id})[0]
|
||||
msg += ["création de %s (%s)" % (mod["code"], mod["titre"])]
|
||||
msg += [
|
||||
"création de %s (%s)" % (mod["code"] or "?", mod["titre"] or "?")
|
||||
]
|
||||
# INSCRIPTIONS DES ETUDIANTS
|
||||
log(
|
||||
'inscription module: %s = "%s"'
|
||||
@ -824,7 +826,7 @@ def do_formsemestre_createwithmodules(edit=False):
|
||||
)
|
||||
msg += [
|
||||
"inscription de %d étudiants au module %s"
|
||||
% (len(etudids), mod["code"])
|
||||
% (len(etudids), mod["code"] or "(module sans code)")
|
||||
]
|
||||
else:
|
||||
log(
|
||||
@ -919,11 +921,19 @@ def formsemestre_delete_moduleimpls(formsemestre_id, module_ids_to_del):
|
||||
if evals:
|
||||
msg += [
|
||||
'<b>impossible de supprimer %s (%s) car il y a %d évaluations définies (<a href="moduleimpl_status?moduleimpl_id=%s" class="stdlink">supprimer les d\'abord</a>)</b>'
|
||||
% (mod["code"], mod["titre"], len(evals), moduleimpl_id)
|
||||
% (
|
||||
mod["code"] or "(module sans code)",
|
||||
mod["titre"],
|
||||
len(evals),
|
||||
moduleimpl_id,
|
||||
)
|
||||
]
|
||||
ok = False
|
||||
else:
|
||||
msg += ["suppression de %s (%s)" % (mod["code"], mod["titre"])]
|
||||
msg += [
|
||||
"suppression de %s (%s)"
|
||||
% (mod["code"] or "(module sans code)", mod["titre"] or "")
|
||||
]
|
||||
sco_moduleimpl.do_moduleimpl_delete(
|
||||
moduleimpl_id, formsemestre_id=formsemestre_id
|
||||
)
|
||||
|
@ -37,6 +37,9 @@ import flask
|
||||
from flask import url_for, g, request
|
||||
from flask_login import current_user
|
||||
|
||||
from app.comp import res_sem
|
||||
from app.comp.res_common import NotesTableCompat
|
||||
from app.models import FormSemestre
|
||||
import app.scodoc.sco_utils as scu
|
||||
import app.scodoc.notesdb as ndb
|
||||
from app import log
|
||||
@ -260,7 +263,8 @@ def formsemestre_ext_edit_ue_validations(formsemestre_id, etudid):
|
||||
|
||||
|
||||
def _make_page(etud, sem, tf, message=""):
|
||||
nt = sco_cache.NotesTableCache.get(sem["formsemestre_id"])
|
||||
formsemestre = FormSemestre.query.get_or_404(sem["formsemestre_id"])
|
||||
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
||||
moy_gen = nt.get_etud_moy_gen(etud["etudid"])
|
||||
H = [
|
||||
html_sco_header.sco_header(
|
||||
|
@ -32,14 +32,16 @@ import time
|
||||
import flask
|
||||
from flask import url_for, g, request
|
||||
|
||||
from app.comp import res_sem
|
||||
from app.comp.res_common import NotesTableCompat
|
||||
from app.models import FormSemestre
|
||||
import app.scodoc.sco_utils as scu
|
||||
from app import log
|
||||
from app.scodoc.scolog import logdb
|
||||
from app.scodoc.sco_exceptions import ScoException, ScoValueError
|
||||
from app.scodoc.sco_permissions import Permission
|
||||
from app.scodoc.sco_codes_parcours import UE_STANDARD, UE_SPORT, UE_TYPE_NAME
|
||||
import app.scodoc.notesdb as ndb
|
||||
from app.scodoc.TrivialFormulator import TrivialFormulator, TF
|
||||
from app.scodoc.TrivialFormulator import TrivialFormulator
|
||||
from app.scodoc import sco_find_etud
|
||||
from app.scodoc import sco_formsemestre
|
||||
from app.scodoc import sco_moduleimpl
|
||||
@ -186,7 +188,9 @@ def do_formsemestre_desinscription(etudid, formsemestre_id):
|
||||
raise ScoValueError("desinscription impossible: semestre verrouille")
|
||||
|
||||
# -- Si decisions de jury, desinscription interdite
|
||||
nt = sco_cache.NotesTableCache.get(formsemestre_id)
|
||||
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
||||
|
||||
if nt.etud_has_decision(etudid):
|
||||
raise ScoValueError(
|
||||
"desinscription impossible: l'étudiant a une décision de jury (la supprimer avant si nécessaire)"
|
||||
@ -475,7 +479,8 @@ def formsemestre_inscription_option(etudid, formsemestre_id):
|
||||
raise ScoValueError("Modification impossible: semestre verrouille")
|
||||
|
||||
etud = sco_etud.get_etud_info(etudid=etudid, filled=True)[0]
|
||||
nt = sco_cache.NotesTableCache.get(formsemestre_id) # > get_etud_ue_status
|
||||
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
||||
|
||||
F = html_sco_header.sco_footer()
|
||||
H = [
|
||||
@ -503,7 +508,7 @@ def formsemestre_inscription_option(etudid, formsemestre_id):
|
||||
modimpls_by_ue_ids[ue_id].append(mod["moduleimpl_id"])
|
||||
|
||||
modimpls_by_ue_names[ue_id].append(
|
||||
"%s %s" % (mod["module"]["code"], mod["module"]["titre"])
|
||||
"%s %s" % (mod["module"]["code"] or "", mod["module"]["titre"] or "")
|
||||
)
|
||||
vals = scu.get_request_args()
|
||||
if not vals.get("tf_submitted", False):
|
||||
@ -527,7 +532,7 @@ def formsemestre_inscription_option(etudid, formsemestre_id):
|
||||
if ue["type"] != UE_STANDARD:
|
||||
ue_descr += " <em>%s</em>" % UE_TYPE_NAME[ue["type"]]
|
||||
ue_status = nt.get_etud_ue_status(etudid, ue_id)
|
||||
if ue_status["is_capitalized"]:
|
||||
if ue_status and ue_status["is_capitalized"]:
|
||||
sem_origin = sco_formsemestre.get_formsemestre(ue_status["formsemestre_id"])
|
||||
ue_descr += ' <a class="discretelink" href="formsemestre_bulletinetud?formsemestre_id=%s&etudid=%s" title="%s">(capitalisée le %s)' % (
|
||||
sem_origin["formsemestre_id"],
|
||||
@ -648,7 +653,7 @@ function chkbx_select(field_id, state) {
|
||||
"%s (%s)"
|
||||
% (
|
||||
modsdict[x]["module"]["titre"],
|
||||
modsdict[x]["module"]["code"],
|
||||
modsdict[x]["module"]["code"] or "(module sans code)",
|
||||
)
|
||||
for x in a_desinscrire
|
||||
]
|
||||
@ -667,7 +672,7 @@ function chkbx_select(field_id, state) {
|
||||
"%s (%s)"
|
||||
% (
|
||||
modsdict[x]["module"]["titre"],
|
||||
modsdict[x]["module"]["code"],
|
||||
modsdict[x]["module"]["code"] or "(module sans code)",
|
||||
)
|
||||
for x in a_inscrire
|
||||
]
|
||||
@ -785,7 +790,9 @@ def list_inscrits_ailleurs(formsemestre_id):
|
||||
Pour chacun, donne la liste des semestres.
|
||||
{ etudid : [ liste de sems ] }
|
||||
"""
|
||||
nt = sco_cache.NotesTableCache.get(formsemestre_id) # > get_etudids
|
||||
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
||||
|
||||
etudids = nt.get_etudids()
|
||||
d = {}
|
||||
for etudid in etudids:
|
||||
|
@ -36,8 +36,8 @@ from flask_login import current_user
|
||||
|
||||
from app import log
|
||||
from app.comp import res_sem
|
||||
from app.comp.res_common import NotesTableCompat
|
||||
from app.models import Module
|
||||
from app.models import formsemestre
|
||||
from app.models.formsemestre import FormSemestre
|
||||
import app.scodoc.sco_utils as scu
|
||||
from app.scodoc.sco_utils import ModuleType
|
||||
@ -51,7 +51,6 @@ from app.scodoc import sco_archives
|
||||
from app.scodoc import sco_bulletins
|
||||
from app.scodoc import sco_codes_parcours
|
||||
from app.scodoc import sco_compute_moy
|
||||
from app.scodoc import sco_cache
|
||||
from app.scodoc import sco_edit_ue
|
||||
from app.scodoc import sco_evaluations
|
||||
from app.scodoc import sco_evaluation_db
|
||||
@ -597,7 +596,8 @@ def formsemestre_description_table(formsemestre_id, with_evals=False):
|
||||
Liste des modules et de leurs coefficients
|
||||
"""
|
||||
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
||||
nt = sco_cache.NotesTableCache.get(formsemestre_id) # > liste evaluations
|
||||
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
||||
use_ue_coefs = sco_preferences.get_preference("use_ue_coefs", formsemestre_id)
|
||||
F = sco_formations.formation_list(args={"formation_id": sem["formation_id"]})[0]
|
||||
parcours = sco_codes_parcours.get_parcours_from_code(F["type_parcours"])
|
||||
@ -638,7 +638,7 @@ def formsemestre_description_table(formsemestre_id, with_evals=False):
|
||||
)
|
||||
l = {
|
||||
"UE": M["ue"]["acronyme"],
|
||||
"Code": M["module"]["code"],
|
||||
"Code": M["module"]["code"] or "",
|
||||
"Module": M["module"]["abbrev"] or M["module"]["titre"],
|
||||
"_Module_class": "scotext",
|
||||
"Inscrits": len(ModInscrits),
|
||||
@ -991,7 +991,6 @@ def formsemestre_status(formsemestre_id=None):
|
||||
modimpls = sco_moduleimpl.moduleimpl_withmodule_list(
|
||||
formsemestre_id=formsemestre_id
|
||||
)
|
||||
# nt = sco_cache.NotesTableCache.get(formsemestre_id)
|
||||
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||
nt = res_sem.load_formsemestre_results(formsemestre)
|
||||
|
||||
|
@ -31,7 +31,6 @@ 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
|
||||
@ -40,6 +39,7 @@ from app import log
|
||||
from app.comp import res_sem
|
||||
from app.comp.res_common import NotesTableCompat
|
||||
from app.models import FormSemestre
|
||||
from app.models.notes import etud_has_notes_attente
|
||||
|
||||
from app.scodoc.sco_exceptions import ScoValueError
|
||||
from app.scodoc.scolog import logdb
|
||||
@ -53,9 +53,7 @@ from app.scodoc import sco_cache
|
||||
from app.scodoc import sco_edit_ue
|
||||
from app.scodoc import sco_etud
|
||||
from app.scodoc import sco_formsemestre
|
||||
from app.scodoc import sco_formsemestre_edit
|
||||
from app.scodoc import sco_formsemestre_inscriptions
|
||||
from app.scodoc import sco_formsemestre_status
|
||||
from app.scodoc import sco_parcours_dut
|
||||
from app.scodoc.sco_parcours_dut import etud_est_inscrit_ue
|
||||
from app.scodoc import sco_photos
|
||||
@ -72,9 +70,8 @@ def formsemestre_validation_etud_form(
|
||||
sortcol=None,
|
||||
readonly=True,
|
||||
):
|
||||
nt = sco_cache.NotesTableCache.get(
|
||||
formsemestre_id
|
||||
) # > get_table_moyennes_triees, get_etud_decision_sem
|
||||
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
||||
T = nt.get_table_moyennes_triees()
|
||||
if not etudid and etud_index is None:
|
||||
raise ValueError("formsemestre_validation_etud_form: missing argument etudid")
|
||||
@ -202,7 +199,7 @@ def formsemestre_validation_etud_form(
|
||||
decision_jury = Se.nt.get_etud_decision_sem(etudid)
|
||||
|
||||
# Bloque si note en attente
|
||||
if nt.etud_has_notes_attente(etudid):
|
||||
if etud_has_notes_attente(etudid, formsemestre_id):
|
||||
H.append(
|
||||
tf_error_message(
|
||||
f"""Impossible de statuer sur cet étudiant: il a des notes en
|
||||
@ -550,7 +547,6 @@ def formsemestre_recap_parcours_table(
|
||||
|
||||
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"
|
||||
@ -649,7 +645,7 @@ def formsemestre_recap_parcours_table(
|
||||
else:
|
||||
code = ""
|
||||
ue_status = nt.get_etud_ue_status(etudid, ue["ue_id"])
|
||||
moy_ue = ue_status["moy"]
|
||||
moy_ue = ue_status["moy"] if ue_status else ""
|
||||
explanation_ue = [] # list of strings
|
||||
if code == ADM:
|
||||
class_ue = "ue_adm"
|
||||
@ -657,12 +653,12 @@ def formsemestre_recap_parcours_table(
|
||||
class_ue = "ue_cmp"
|
||||
else:
|
||||
class_ue = "ue"
|
||||
if ue_status["is_external"]: # validation externe
|
||||
if ue_status and ue_status["is_external"]: # validation externe
|
||||
explanation_ue.append("UE externe.")
|
||||
# log('x'*12+' EXTERNAL %s' % notes_table.fmt_note(moy_ue)) XXXXXXX
|
||||
# log('UE=%s' % pprint.pformat(ue))
|
||||
# log('explanation_ue=%s\n'%explanation_ue)
|
||||
if ue_status["is_capitalized"]:
|
||||
if ue_status and ue_status["is_capitalized"]:
|
||||
class_ue += " ue_capitalized"
|
||||
explanation_ue.append(
|
||||
"Capitalisée le %s." % (ue_status["event_date"] or "?")
|
||||
@ -709,7 +705,10 @@ def formsemestre_recap_parcours_table(
|
||||
# ECTS validables dans chaque UE
|
||||
for ue in ues:
|
||||
ue_status = nt.get_etud_ue_status(etudid, ue["ue_id"])
|
||||
H.append('<td class="ue">%g</td>' % (ue_status["ects_pot"]))
|
||||
H.append(
|
||||
'<td class="ue">%g</td>'
|
||||
% (ue_status["ects_pot"] if ue_status else "")
|
||||
)
|
||||
H.append("<td></td></tr>")
|
||||
|
||||
H.append("</table>")
|
||||
@ -878,9 +877,8 @@ def do_formsemestre_validation_auto(formsemestre_id):
|
||||
"Saisie automatisee des decisions d'un semestre"
|
||||
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
||||
next_semestre_id = sem["semestre_id"] + 1
|
||||
nt = sco_cache.NotesTableCache.get(
|
||||
formsemestre_id
|
||||
) # > get_etudids, get_etud_decision_sem,
|
||||
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
||||
etudids = nt.get_etudids()
|
||||
nb_valid = 0
|
||||
conflicts = [] # liste des etudiants avec decision differente déjà saisie
|
||||
@ -899,7 +897,7 @@ def do_formsemestre_validation_auto(formsemestre_id):
|
||||
)
|
||||
and Se.barre_moy_ok
|
||||
and Se.barres_ue_ok
|
||||
and not nt.etud_has_notes_attente(etudid)
|
||||
and not etud_has_notes_attente(etudid, formsemestre_id)
|
||||
):
|
||||
# check: s'il existe une decision ou autorisation et qu'elles sont differentes,
|
||||
# warning (et ne fait rien)
|
||||
@ -1133,9 +1131,11 @@ def do_formsemestre_validate_previous_ue(
|
||||
Si le coefficient est spécifié, modifie le coefficient de
|
||||
cette UE (utile seulement pour les semestres extérieurs).
|
||||
"""
|
||||
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
||||
|
||||
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
||||
cnx = ndb.GetDBConnexion()
|
||||
nt = sco_cache.NotesTableCache.get(formsemestre_id) # > get_etud_ue_status
|
||||
if ue_coefficient != None:
|
||||
sco_formsemestre.do_formsemestre_uecoef_edit_or_create(
|
||||
cnx, formsemestre_id, ue_id, ue_coefficient
|
||||
|
@ -45,6 +45,9 @@ from flask import g, request
|
||||
from flask import url_for, make_response
|
||||
|
||||
from app import db
|
||||
from app.comp import res_sem
|
||||
from app.comp.res_common import NotesTableCompat
|
||||
from app.models import FormSemestre, formsemestre
|
||||
from app.models import GROUPNAME_STR_LEN, SHORT_STR_LEN
|
||||
from app.models.groups import Partition
|
||||
import app.scodoc.sco_utils as scu
|
||||
@ -488,17 +491,14 @@ def XMLgetGroupsInPartition(partition_id): # was XMLgetGroupesTD
|
||||
<group ...>
|
||||
...
|
||||
"""
|
||||
from app.scodoc import sco_formsemestre
|
||||
|
||||
cnx = ndb.GetDBConnexion()
|
||||
|
||||
t0 = time.time()
|
||||
partition = get_partition(partition_id)
|
||||
formsemestre_id = partition["formsemestre_id"]
|
||||
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
||||
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||
etuds_set = {ins.etudid for ins in formsemestre.inscriptions}
|
||||
|
||||
sem = formsemestre.get_infos_dict() # transition TODO
|
||||
groups = get_partition_groups(partition)
|
||||
nt = sco_cache.NotesTableCache.get(formsemestre_id) # > inscrdict
|
||||
etuds_set = set(nt.inscrdict)
|
||||
# Build XML:
|
||||
t1 = time.time()
|
||||
doc = Element("ajax-response")
|
||||
@ -1277,13 +1277,13 @@ def groups_auto_repartition(partition_id=None):
|
||||
|
||||
partition = get_partition(partition_id)
|
||||
formsemestre_id = partition["formsemestre_id"]
|
||||
formsemestre = FormSemestre.query.get(formsemestre_id)
|
||||
# renvoie sur page édition groupes
|
||||
dest_url = url_for(
|
||||
"scolar.affect_groups", scodoc_dept=g.scodoc_dept, partition_id=partition_id
|
||||
)
|
||||
if not sco_permissions_check.can_change_groups(formsemestre_id):
|
||||
raise AccessDenied("Vous n'avez pas le droit d'effectuer cette opération !")
|
||||
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
||||
|
||||
descr = [
|
||||
("partition_id", {"input_type": "hidden"}),
|
||||
@ -1301,7 +1301,7 @@ def groups_auto_repartition(partition_id=None):
|
||||
H = [
|
||||
html_sco_header.sco_header(page_title="Répartition des groupes"),
|
||||
"<h2>Répartition des groupes de %s</h2>" % partition["partition_name"],
|
||||
"<p>Semestre %s</p>" % sem["titreannee"],
|
||||
f"<p>Semestre {formsemestre.titre_annee()}</p>",
|
||||
"""<p class="help">Les groupes existants seront <b>effacés</b> et remplacés par
|
||||
ceux créés ici. La répartition aléatoire tente d'uniformiser le niveau
|
||||
des groupes (en utilisant la dernière moyenne générale disponible pour
|
||||
@ -1343,7 +1343,7 @@ def groups_auto_repartition(partition_id=None):
|
||||
# return '\n'.join(H) + tf[1] + html_sco_header.sco_footer()
|
||||
group_ids.append(create_group(partition_id, group_name))
|
||||
#
|
||||
nt = sco_cache.NotesTableCache.get(formsemestre_id) # > identdict
|
||||
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
||||
identdict = nt.identdict
|
||||
# build: { civilite : liste etudids trie par niveau croissant }
|
||||
civilites = set([x["civilite"] for x in identdict.values()])
|
||||
@ -1384,9 +1384,8 @@ def _get_prev_moy(etudid, formsemestre_id):
|
||||
etud = info[0]
|
||||
Se = sco_parcours_dut.SituationEtudParcours(etud, formsemestre_id)
|
||||
if Se.prev:
|
||||
nt = sco_cache.NotesTableCache.get(
|
||||
Se.prev["formsemestre_id"]
|
||||
) # > get_etud_moy_gen
|
||||
prev_sem = FormSemestre.query.get(Se.prev["formsemestre_id"])
|
||||
nt: NotesTableCompat = res_sem.load_formsemestre_results(prev_sem)
|
||||
return nt.get_etud_moy_gen(etudid)
|
||||
else:
|
||||
return 0.0
|
||||
|
@ -31,16 +31,17 @@
|
||||
import flask
|
||||
from flask import url_for, g, request
|
||||
|
||||
from app import models
|
||||
from app.models.evaluations import Evaluation
|
||||
from app.models.moduleimpls import ModuleImpl
|
||||
import app.scodoc.sco_utils as scu
|
||||
import app.scodoc.notesdb as ndb
|
||||
from app import log
|
||||
from app import models
|
||||
from app.comp import res_sem
|
||||
from app.comp import moy_mod
|
||||
from app.comp.moy_mod import ModuleImplResults
|
||||
from app.comp.res_common import NotesTableCompat
|
||||
from app.models import FormSemestre
|
||||
from app.models.evaluations import Evaluation
|
||||
from app.models.moduleimpls import ModuleImpl
|
||||
import app.scodoc.sco_utils as scu
|
||||
import app.scodoc.notesdb as ndb
|
||||
from app.scodoc.TrivialFormulator import TrivialFormulator
|
||||
from app.scodoc import sco_cache
|
||||
from app.scodoc import sco_edit_module
|
||||
@ -788,7 +789,9 @@ def _add_moymod_column(
|
||||
):
|
||||
"""Ajoute la colonne moymod à rows"""
|
||||
col_id = "moymod"
|
||||
nt = sco_cache.NotesTableCache.get(formsemestre_id) # > get_etud_mod_moy
|
||||
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
||||
|
||||
nb_notes = 0
|
||||
sum_notes = 0
|
||||
notes = [] # liste des notes numeriques, pour calcul histogramme uniquement
|
||||
|
@ -29,6 +29,7 @@
|
||||
"""
|
||||
|
||||
from flask_login import current_user
|
||||
import psycopg2
|
||||
|
||||
import app.scodoc.sco_utils as scu
|
||||
import app.scodoc.notesdb as ndb
|
||||
@ -271,7 +272,12 @@ _moduleimpl_inscriptionEditor = ndb.EditableTable(
|
||||
def do_moduleimpl_inscription_create(args, formsemestre_id=None):
|
||||
"create a moduleimpl_inscription"
|
||||
cnx = ndb.GetDBConnexion()
|
||||
try:
|
||||
r = _moduleimpl_inscriptionEditor.create(cnx, args)
|
||||
except psycopg2.errors.UniqueViolation as exc:
|
||||
raise ScoValueError(
|
||||
"Inscription impossible car déjà existante: vérifiez la situation"
|
||||
)
|
||||
sco_cache.invalidate_formsemestre(
|
||||
formsemestre_id=formsemestre_id
|
||||
) # > moduleimpl_inscription
|
||||
|
@ -33,6 +33,10 @@ import flask
|
||||
from flask import url_for, g, request
|
||||
from flask_login import current_user
|
||||
|
||||
from app.comp import res_sem
|
||||
from app.comp.res_common import NotesTableCompat
|
||||
from app.models import FormSemestre
|
||||
|
||||
import app.scodoc.notesdb as ndb
|
||||
import app.scodoc.sco_utils as scu
|
||||
from app import log
|
||||
@ -88,7 +92,11 @@ def moduleimpl_inscriptions_edit(moduleimpl_id, etuds=[], submitted=False):
|
||||
"Appliquer les modifications".
|
||||
</p>
|
||||
"""
|
||||
% (moduleimpl_id, mod["titre"], mod["code"]),
|
||||
% (
|
||||
moduleimpl_id,
|
||||
mod["titre"] or "(module sans titre)",
|
||||
mod["code"] or "(module sans code)",
|
||||
),
|
||||
]
|
||||
# Liste des inscrits à ce semestre
|
||||
inscrits = sco_formsemestre_inscriptions.do_formsemestre_inscription_listinscrits(
|
||||
@ -304,8 +312,8 @@ def moduleimpl_inscriptions_stats(formsemestre_id):
|
||||
H.append(
|
||||
'<tr class="formsemestre_status"><td>%s</td><td class="formsemestre_status_code">%s</td><td class="formsemestre_status_inscrits">%s</td><td>%s</td></tr>'
|
||||
% (
|
||||
mod["ue"]["acronyme"],
|
||||
mod["module"]["code"],
|
||||
mod["ue"]["acronyme"] or "",
|
||||
mod["module"]["code"] or "(module sans code)",
|
||||
mod["nb_inscrits"],
|
||||
c_link,
|
||||
)
|
||||
@ -333,7 +341,11 @@ def moduleimpl_inscriptions_stats(formsemestre_id):
|
||||
c_link = mod["module"]["titre"]
|
||||
H.append(
|
||||
'<tr class="formsemestre_status_green"><td>%s</td><td class="formsemestre_status_code">%s</td><td>%s</td></tr>'
|
||||
% (mod["ue"]["acronyme"], mod["module"]["code"], c_link)
|
||||
% (
|
||||
mod["ue"]["acronyme"],
|
||||
mod["module"]["code"] or "(module sans code)",
|
||||
c_link,
|
||||
)
|
||||
)
|
||||
H.append("</table>")
|
||||
|
||||
@ -479,21 +491,21 @@ def get_etuds_with_capitalized_ue(formsemestre_id):
|
||||
returns { ue_id : [ { infos } ] }
|
||||
"""
|
||||
UECaps = scu.DictDefault(defaultvalue=[])
|
||||
nt = sco_cache.NotesTableCache.get(
|
||||
formsemestre_id
|
||||
) # > get_ues_stat_dict, get_etud_ue_status
|
||||
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
||||
|
||||
inscrits = sco_formsemestre_inscriptions.do_formsemestre_inscription_list(
|
||||
args={"formsemestre_id": formsemestre_id}
|
||||
)
|
||||
ues = nt.get_ues_stat_dict()
|
||||
for ue in ues:
|
||||
for etud in inscrits:
|
||||
status = nt.get_etud_ue_status(etud["etudid"], ue["ue_id"])
|
||||
if status["was_capitalized"]:
|
||||
ue_status = nt.get_etud_ue_status(etud["etudid"], ue["ue_id"])
|
||||
if ue_status and ue_status["was_capitalized"]:
|
||||
UECaps[ue["ue_id"]].append(
|
||||
{
|
||||
"etudid": etud["etudid"],
|
||||
"ue_status": status,
|
||||
"ue_status": ue_status,
|
||||
"is_ins": is_inscrit_ue(
|
||||
etud["etudid"], formsemestre_id, ue["ue_id"]
|
||||
),
|
||||
|
@ -203,7 +203,6 @@ def moduleimpl_status(moduleimpl_id=None, partition_id=None):
|
||||
moduleimpl_id=M["moduleimpl_id"]
|
||||
)
|
||||
|
||||
# nt = sco_cache.NotesTableCache.get(formsemestre_id)
|
||||
nt: NotesTableCompat = res_sem.load_formsemestre_results(modimpl.formsemestre)
|
||||
|
||||
mod_evals = sco_evaluation_db.do_evaluation_list({"moduleimpl_id": moduleimpl_id})
|
||||
|
@ -36,7 +36,7 @@ import app.scodoc.sco_utils as scu
|
||||
import app.scodoc.notesdb as ndb
|
||||
from app import log
|
||||
from app.scodoc.scolog import logdb
|
||||
from app.scodoc import sco_cache
|
||||
from app.scodoc import sco_cache, sco_etud
|
||||
from app.scodoc import sco_formsemestre
|
||||
from app.scodoc import sco_formations
|
||||
from app.scodoc.sco_codes_parcours import (
|
||||
@ -111,9 +111,6 @@ class DecisionSem(object):
|
||||
|
||||
def SituationEtudParcours(etud, formsemestre_id):
|
||||
"""renvoie une instance de SituationEtudParcours (ou sous-classe spécialisée)"""
|
||||
# nt = sco_cache.NotesTableCache.get(
|
||||
# formsemestre_id
|
||||
# ) # > get_etud_decision_sem, get_etud_moy_gen, get_ues_stat_dict, get_etud_ue_status, etud_check_conditions_ues
|
||||
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
||||
parcours = nt.parcours
|
||||
@ -302,9 +299,6 @@ class SituationEtudParcoursGeneric(object):
|
||||
sem["semestre_id"] == n1
|
||||
and sem["formation_code"] == self.formation.formation_code
|
||||
):
|
||||
# nt = sco_cache.NotesTableCache.get(
|
||||
# sem["formsemestre_id"]
|
||||
# ) # > get_etud_decision_sem
|
||||
formsemestre = FormSemestre.query.get_or_404(sem["formsemestre_id"])
|
||||
nt: NotesTableCompat = res_sem.load_formsemestre_results(
|
||||
formsemestre
|
||||
@ -322,9 +316,6 @@ class SituationEtudParcoursGeneric(object):
|
||||
sont validés. En sortie, sem_idx_set contient ceux qui n'ont pas été validés."""
|
||||
for sem in self.get_semestres():
|
||||
if sem["formation_code"] == self.formation.formation_code:
|
||||
# nt = sco_cache.NotesTableCache.get(
|
||||
# sem["formsemestre_id"]
|
||||
# ) # > get_etud_decision_sem
|
||||
formsemestre = FormSemestre.query.get_or_404(sem["formsemestre_id"])
|
||||
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
||||
decision = nt.get_etud_decision_sem(self.etudid)
|
||||
@ -336,15 +327,16 @@ class SituationEtudParcoursGeneric(object):
|
||||
|
||||
def _comp_semestres(self):
|
||||
# etud['sems'] est trie par date decroissante (voir fill_etuds_info)
|
||||
if not "sems" in self.etud:
|
||||
self.etud["sems"] = sco_etud.etud_inscriptions_infos(
|
||||
self.etud["etudid"], self.etud["ne"]
|
||||
)["sems"]
|
||||
sems = self.etud["sems"][:] # copy
|
||||
sems.reverse()
|
||||
# Nb max d'UE et acronymes
|
||||
ue_acros = {} # acronyme ue : 1
|
||||
nb_max_ue = 0
|
||||
for sem in sems:
|
||||
# nt = sco_cache.NotesTableCache.get(
|
||||
# sem["formsemestre_id"]
|
||||
# ) # > get_ues_stat_dict
|
||||
formsemestre = FormSemestre.query.get_or_404(sem["formsemestre_id"])
|
||||
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
||||
ues = nt.get_ues_stat_dict(filter_sport=True)
|
||||
@ -414,9 +406,6 @@ class SituationEtudParcoursGeneric(object):
|
||||
if not sem:
|
||||
code = "" # non inscrit à ce semestre
|
||||
else:
|
||||
# nt = sco_cache.NotesTableCache.get(
|
||||
# sem["formsemestre_id"]
|
||||
# ) # > get_etud_decision_sem
|
||||
formsemestre = FormSemestre.query.get_or_404(sem["formsemestre_id"])
|
||||
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
||||
decision = nt.get_etud_decision_sem(self.etudid)
|
||||
@ -486,7 +475,6 @@ class SituationEtudParcoursGeneric(object):
|
||||
# Verifications basiques:
|
||||
# ?
|
||||
# Code etat du semestre precedent:
|
||||
# nt = sco_cache.NotesTableCache.get(prev["formsemestre_id"])
|
||||
formsemestre = FormSemestre.query.get_or_404(prev["formsemestre_id"])
|
||||
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
||||
self.prev_decision = nt.get_etud_decision_sem(self.etudid)
|
||||
@ -545,8 +533,6 @@ class SituationEtudParcoursGeneric(object):
|
||||
sem["formation_code"] == self.formation.formation_code
|
||||
and sem["semestre_id"] == s
|
||||
):
|
||||
# nt = sco_cache.NotesTableCache.get(sem["formsemestre_id"])
|
||||
# > get_etud_decision_sem
|
||||
formsemestre = FormSemestre.query.get_or_404(sem["formsemestre_id"])
|
||||
nt: NotesTableCompat = res_sem.load_formsemestre_results(
|
||||
formsemestre
|
||||
@ -924,9 +910,6 @@ def formsemestre_validate_ues(formsemestre_id, etudid, code_etat_sem, assiduite)
|
||||
"""
|
||||
valid_semestre = CODES_SEM_VALIDES.get(code_etat_sem, False)
|
||||
cnx = ndb.GetDBConnexion()
|
||||
# nt = sco_cache.NotesTableCache.get(
|
||||
# formsemestre_id
|
||||
# ) # > get_ues_stat_dict, get_etud_ue_status
|
||||
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
||||
ue_ids = [x["ue_id"] for x in nt.get_ues_stat_dict(filter_sport=True)]
|
||||
@ -1005,7 +988,8 @@ def do_formsemestre_validate_ue(
|
||||
if code == ADM:
|
||||
if moy_ue is None:
|
||||
# stocke la moyenne d'UE capitalisée:
|
||||
moy_ue = nt.get_etud_ue_status(etudid, ue_id)["moy"]
|
||||
ue_status = nt.get_etud_ue_status(etudid, ue_id)
|
||||
moy_ue = ue_status["moy"] if ue_status else ""
|
||||
args["moy_ue"] = moy_ue
|
||||
log("formsemestre_validate_ue: create %s" % args)
|
||||
if code != None:
|
||||
|
@ -257,7 +257,7 @@ class PlacementRunner:
|
||||
self.moduleimpl_data["formsemestre_id"]
|
||||
)
|
||||
self.evalname = "%s-%s" % (
|
||||
self.module_data["code"],
|
||||
self.module_data["code"] or "?",
|
||||
ndb.DateDMYtoISO(self.eval_data["jour"]),
|
||||
)
|
||||
if self.eval_data["description"]:
|
||||
@ -266,7 +266,8 @@ class PlacementRunner:
|
||||
self.evaltitre = "évaluation du %s" % self.eval_data["jour"]
|
||||
self.desceval = [ # une liste de chaines: description de l'evaluation
|
||||
"%s" % self.sem["titreannee"],
|
||||
"Module : %s - %s" % (self.module_data["code"], self.module_data["abbrev"]),
|
||||
"Module : %s - %s"
|
||||
% (self.module_data["code"] or "?", self.module_data["abbrev"] or ""),
|
||||
"Surveillants : %s" % self.surveillants,
|
||||
"Batiment : %(batiment)s - Salle : %(salle)s" % self.__dict__,
|
||||
"Controle : %s (coef. %g)"
|
||||
|
@ -33,6 +33,9 @@ import collections
|
||||
|
||||
from flask import url_for, g, request
|
||||
|
||||
from app.comp import res_sem
|
||||
from app.comp.res_common import NotesTableCompat
|
||||
from app.models import FormSemestre
|
||||
import app.scodoc.sco_utils as scu
|
||||
from app.scodoc import sco_abs
|
||||
from app.scodoc import sco_cache
|
||||
@ -58,17 +61,25 @@ def etud_get_poursuite_info(sem, etud):
|
||||
for s in etud["sems"]:
|
||||
if s["semestre_id"] == sem_id:
|
||||
etudid = etud["etudid"]
|
||||
nt = sco_cache.NotesTableCache.get(s["formsemestre_id"])
|
||||
formsemestre = FormSemestre.query.get_or_404(s["formsemestre_id"])
|
||||
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
||||
dec = nt.get_etud_decision_sem(etudid)
|
||||
# Moyennes et rangs des UE
|
||||
ues = nt.get_ues_stat_dict(filter_sport=True)
|
||||
moy_ues = [
|
||||
moy_ues = []
|
||||
for ue in ues:
|
||||
ue_status = nt.get_etud_ue_status(etudid, ue["ue_id"])
|
||||
if ue_status:
|
||||
moy_ues.append(
|
||||
(
|
||||
ue["acronyme"],
|
||||
scu.fmt_note(nt.get_etud_ue_status(etudid, ue["ue_id"])["moy"]),
|
||||
scu.fmt_note(
|
||||
nt.get_etud_ue_status(etudid, ue["ue_id"])["moy"]
|
||||
),
|
||||
)
|
||||
for ue in ues
|
||||
]
|
||||
)
|
||||
else:
|
||||
moy_ues.append((ue["acronyme"], ""))
|
||||
rg_ues = [
|
||||
("rang_" + ue["acronyme"], nt.ue_rangs[ue["ue_id"]][0][etudid])
|
||||
for ue in ues
|
||||
@ -81,14 +92,17 @@ def etud_get_poursuite_info(sem, etud):
|
||||
for ue in ues: # on parcourt chaque UE
|
||||
for modimpl in modimpls: # dans chaque UE les modules
|
||||
if modimpl["module"]["ue_id"] == ue["ue_id"]:
|
||||
codeModule = modimpl["module"]["code"]
|
||||
codeModule = modimpl["module"]["code"] or ""
|
||||
noteModule = scu.fmt_note(
|
||||
nt.get_etud_mod_moy(modimpl["moduleimpl_id"], etudid)
|
||||
)
|
||||
if noteModule != "NI": # si étudiant inscrit au module
|
||||
rangModule = nt.mod_rangs[modimpl["moduleimpl_id"]][0][
|
||||
etudid
|
||||
]
|
||||
if nt.mod_rangs is not None:
|
||||
rangModule = nt.mod_rangs[modimpl["moduleimpl_id"]][
|
||||
0
|
||||
][etudid]
|
||||
else:
|
||||
rangModule = ""
|
||||
modules.append([codeModule, noteModule])
|
||||
rangs.append(["rang_" + codeModule, rangModule])
|
||||
|
||||
|
@ -68,17 +68,17 @@ des tuples (name, value, formsemestre_id).
|
||||
Si formsemestre_id est NULL, la valeur concerne tous les semestres,
|
||||
sinon, elle ne concerne que le semestre indiqué.
|
||||
|
||||
* Utilisation dans ScoDoc8
|
||||
* Utilisation dans ScoDoc 9
|
||||
- lire une valeur:
|
||||
get_preference(name, formsemestre_id)
|
||||
nb: les valeurs sont des chaines, sauf:
|
||||
. si le type est spécfié (float ou int)
|
||||
. si le type est spécifié (float ou int)
|
||||
. les boolcheckbox qui sont des entiers 0 ou 1
|
||||
- avoir un mapping (read only) de toutes les valeurs:
|
||||
sco_preferences.SemPreferences(formsemestre_id)
|
||||
- editer les preferences globales:
|
||||
- éditer les preferences globales:
|
||||
sco_preferences.get_base_preferences(self).edit()
|
||||
- editer les preferences d'un semestre:
|
||||
- éditer les preferences d'un semestre:
|
||||
SemPreferences(formsemestre_id).edit()
|
||||
|
||||
* Implémentation: sco_preferences.py
|
||||
@ -123,22 +123,22 @@ from app.scodoc.TrivialFormulator import TrivialFormulator
|
||||
import app.scodoc.notesdb as ndb
|
||||
import app.scodoc.sco_utils as scu
|
||||
|
||||
_SCO_BASE_PREFERENCES = {} # { dept_acronym: BasePreferences instance }
|
||||
|
||||
|
||||
def clear_base_preferences():
|
||||
"""Clear cached preferences"""
|
||||
# usefull only for tests, where the same process may run
|
||||
# successively on several databases
|
||||
_SCO_BASE_PREFERENCES.clear()
|
||||
g._SCO_BASE_PREFERENCES = {} # { dept_id: BasePreferences instance }
|
||||
|
||||
|
||||
def get_base_preferences():
|
||||
"""Return global preferences for the current department"""
|
||||
dept_acronym = g.scodoc_dept
|
||||
if not dept_acronym in _SCO_BASE_PREFERENCES:
|
||||
_SCO_BASE_PREFERENCES[dept_acronym] = BasePreferences(dept_acronym)
|
||||
return _SCO_BASE_PREFERENCES[dept_acronym]
|
||||
dept_id = g.scodoc_dept_id
|
||||
if not hasattr(g, "_SCO_BASE_PREFERENCES"):
|
||||
g._SCO_BASE_PREFERENCES = {}
|
||||
if not dept_id in g._SCO_BASE_PREFERENCES:
|
||||
g._SCO_BASE_PREFERENCES[dept_id] = BasePreferences(dept_id)
|
||||
return g._SCO_BASE_PREFERENCES[dept_id]
|
||||
|
||||
|
||||
def get_preference(name, formsemestre_id=None):
|
||||
@ -263,10 +263,10 @@ class BasePreferences(object):
|
||||
filter_nulls=False,
|
||||
)
|
||||
|
||||
def __init__(self, dept_acronym: str):
|
||||
dept = Departement.query.filter_by(acronym=dept_acronym).first()
|
||||
def __init__(self, dept_id: int):
|
||||
dept = Departement.query.get(dept_id)
|
||||
if not dept:
|
||||
raise ScoValueError(f"Invalid departement: {dept_acronym}")
|
||||
raise ScoValueError(f"Invalid departement: {dept_id}")
|
||||
self.dept_id = dept.id
|
||||
self.init()
|
||||
self.load()
|
||||
@ -1859,7 +1859,7 @@ class BasePreferences(object):
|
||||
|
||||
def load(self):
|
||||
"""Load all preferences from db"""
|
||||
log(f"loading preferences for dept_id={self.dept_id}")
|
||||
# log(f"loading preferences for dept_id={self.dept_id}")
|
||||
|
||||
cnx = ndb.GetDBConnexion()
|
||||
preflist = self._editor.list(cnx, {"dept_id": self.dept_id})
|
||||
@ -2114,7 +2114,7 @@ class BasePreferences(object):
|
||||
return form
|
||||
|
||||
|
||||
class SemPreferences(object):
|
||||
class SemPreferences:
|
||||
"""Preferences for a formsemestre"""
|
||||
|
||||
def __init__(self, formsemestre_id=None):
|
||||
@ -2270,9 +2270,8 @@ def doc_preferences():
|
||||
return "\n".join([" | ".join(x) for x in L])
|
||||
|
||||
|
||||
def bulletin_option_affichage(formsemestre_id: int) -> dict:
|
||||
def bulletin_option_affichage(formsemestre_id: int, prefs: SemPreferences) -> dict:
|
||||
"dict avec les options d'affichages (préférences) pour ce semestre"
|
||||
prefs = SemPreferences(formsemestre_id)
|
||||
fields = (
|
||||
"bul_show_abs",
|
||||
"bul_show_abs_modules",
|
||||
|
@ -31,29 +31,30 @@ import time
|
||||
|
||||
from openpyxl.styles.numbers import FORMAT_NUMBER_00
|
||||
|
||||
from flask import flash
|
||||
from flask import request
|
||||
from flask_login import current_user
|
||||
|
||||
import app.scodoc.sco_utils as scu
|
||||
from app.comp import res_sem
|
||||
from app.comp.res_common import NotesTableCompat
|
||||
from app.models import FormSemestre, Identite
|
||||
from app.scodoc import sco_abs
|
||||
from app.scodoc import sco_codes_parcours
|
||||
from app.scodoc import sco_groups
|
||||
from app.scodoc import sco_cache
|
||||
from app.scodoc import sco_etud
|
||||
from app.scodoc import sco_excel
|
||||
from app.scodoc import sco_formsemestre
|
||||
from app.scodoc import sco_parcours_dut
|
||||
from app.scodoc import sco_codes_parcours
|
||||
import sco_version
|
||||
from app.scodoc import sco_etud
|
||||
from app.scodoc import sco_preferences
|
||||
from app.scodoc.sco_excel import ScoExcelSheet
|
||||
import app.scodoc.sco_utils as scu
|
||||
import sco_version
|
||||
|
||||
|
||||
def feuille_preparation_jury(formsemestre_id):
|
||||
"Feuille excel pour preparation des jurys"
|
||||
nt = sco_cache.NotesTableCache.get(
|
||||
formsemestre_id
|
||||
) # > get_etudids, get_etud_moy_gen, get_ues_stat_dict, get_etud_ue_status, get_etud_decision_sem, identdict,
|
||||
etudids = nt.get_etudids(sorted=True) # tri par moy gen
|
||||
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
||||
etuds: Identite = nt.get_inscrits(order_by="moy") # tri par moy gen
|
||||
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
||||
|
||||
etud_groups = sco_groups.formsemestre_get_etud_groupnames(formsemestre_id)
|
||||
@ -76,68 +77,65 @@ def feuille_preparation_jury(formsemestre_id):
|
||||
groupestd = {} # etudid : nom groupe principal
|
||||
nbabs = {}
|
||||
nbabsjust = {}
|
||||
for etudid in etudids:
|
||||
info = sco_etud.get_etud_info(etudid=etudid, filled=True)
|
||||
if not info:
|
||||
continue # should not occur...
|
||||
etud = info[0]
|
||||
Se = sco_parcours_dut.SituationEtudParcours(etud, formsemestre_id)
|
||||
for etud in etuds:
|
||||
Se = sco_parcours_dut.SituationEtudParcours(
|
||||
etud.to_dict_scodoc7(), formsemestre_id
|
||||
)
|
||||
if Se.prev:
|
||||
ntp = sco_cache.NotesTableCache.get(
|
||||
formsemestre_prev = FormSemestre.query.get_or_404(
|
||||
Se.prev["formsemestre_id"]
|
||||
) # > get_ues_stat_dict, get_etud_ue_status, get_etud_moy_gen, get_etud_decision_sem
|
||||
)
|
||||
ntp: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre_prev)
|
||||
for ue in ntp.get_ues_stat_dict(filter_sport=True):
|
||||
ue_status = ntp.get_etud_ue_status(etudid, ue["ue_id"])
|
||||
ue_status = ntp.get_etud_ue_status(etud.id, ue["ue_id"])
|
||||
ue_code_s = (
|
||||
ue["ue_code"] + "_%s" % ntp.sem["semestre_id"]
|
||||
) # code indentifiant l'UE
|
||||
prev_moy_ue[ue_code_s][etudid] = ue_status["moy"]
|
||||
# prev_ue_acro[ue_code_s] = (ue['numero'], ue['acronyme'])
|
||||
prev_moy_ue[ue_code_s][etud.id] = ue_status["moy"] if ue_status else ""
|
||||
prev_ue_acro[ue_code_s] = (ue["numero"], ue["acronyme"], ue["titre"])
|
||||
prev_moy[etudid] = ntp.get_etud_moy_gen(etudid)
|
||||
prev_decision = ntp.get_etud_decision_sem(etudid)
|
||||
prev_moy[etud.id] = ntp.get_etud_moy_gen(etud.id)
|
||||
prev_decision = ntp.get_etud_decision_sem(etud.id)
|
||||
if prev_decision:
|
||||
prev_code[etudid] = prev_decision["code"]
|
||||
prev_code[etud.id] = prev_decision["code"]
|
||||
if prev_decision["compense_formsemestre_id"]:
|
||||
prev_code[etudid] += "+" # indique qu'il a servi a compenser
|
||||
prev_code[etud.id] += "+" # indique qu'il a servi a compenser
|
||||
|
||||
moy[etudid] = nt.get_etud_moy_gen(etudid)
|
||||
moy[etud.id] = nt.get_etud_moy_gen(etud.id)
|
||||
for ue in nt.get_ues_stat_dict(filter_sport=True):
|
||||
ue_status = nt.get_etud_ue_status(etudid, ue["ue_id"])
|
||||
ue_status = nt.get_etud_ue_status(etud.id, ue["ue_id"])
|
||||
ue_code_s = ue["ue_code"] + "_%s" % nt.sem["semestre_id"]
|
||||
moy_ue[ue_code_s][etudid] = ue_status["moy"]
|
||||
# ue_acro[ue_code_s] = (ue['numero'], ue['acronyme'])
|
||||
moy_ue[ue_code_s][etud.id] = ue_status["moy"] if ue_status else ""
|
||||
ue_acro[ue_code_s] = (ue["numero"], ue["acronyme"], ue["titre"])
|
||||
|
||||
if Se.prev:
|
||||
try:
|
||||
moy_inter[etudid] = (moy[etudid] + prev_moy[etudid]) / 2.0
|
||||
moy_inter[etud.id] = (moy[etud.id] + prev_moy[etud.id]) / 2.0
|
||||
except:
|
||||
pass
|
||||
|
||||
decision = nt.get_etud_decision_sem(etudid)
|
||||
decision = nt.get_etud_decision_sem(etud.id)
|
||||
if decision:
|
||||
code[etudid] = decision["code"]
|
||||
code[etud.id] = decision["code"]
|
||||
if decision["compense_formsemestre_id"]:
|
||||
code[etudid] += "+" # indique qu'il a servi a compenser
|
||||
assidu[etudid] = {False: "Non", True: "Oui"}.get(decision["assidu"], "")
|
||||
code[etud.id] += "+" # indique qu'il a servi a compenser
|
||||
assidu[etud.id] = {False: "Non", True: "Oui"}.get(decision["assidu"], "")
|
||||
aut_list = sco_parcours_dut.formsemestre_get_autorisation_inscription(
|
||||
etudid, formsemestre_id
|
||||
etud.id, formsemestre_id
|
||||
)
|
||||
autorisations[etudid] = ", ".join(["S%s" % x["semestre_id"] for x in aut_list])
|
||||
autorisations[etud.id] = ", ".join(["S%s" % x["semestre_id"] for x in aut_list])
|
||||
# parcours:
|
||||
parcours[etudid] = Se.get_parcours_descr()
|
||||
parcours[etud.id] = Se.get_parcours_descr()
|
||||
# groupe principal (td)
|
||||
groupestd[etudid] = ""
|
||||
for s in etud["sems"]:
|
||||
groupestd[etud.id] = ""
|
||||
for s in Se.etud["sems"]:
|
||||
if s["formsemestre_id"] == formsemestre_id:
|
||||
groupestd[etudid] = etud_groups.get(etudid, {}).get(
|
||||
groupestd[etud.id] = etud_groups.get(etud.id, {}).get(
|
||||
main_partition_id, ""
|
||||
)
|
||||
# absences:
|
||||
e_nbabs, e_nbabsjust = sco_abs.get_abs_count(etudid, sem)
|
||||
nbabs[etudid] = e_nbabs
|
||||
nbabsjust[etudid] = e_nbabs - e_nbabsjust
|
||||
e_nbabs, e_nbabsjust = sco_abs.get_abs_count(etud.id, sem)
|
||||
nbabs[etud.id] = e_nbabs
|
||||
nbabsjust[etud.id] = e_nbabs - e_nbabsjust
|
||||
|
||||
# Codes des UE "semestre précédent":
|
||||
ue_prev_codes = list(prev_moy_ue.keys())
|
||||
@ -229,26 +227,26 @@ def feuille_preparation_jury(formsemestre_id):
|
||||
return x
|
||||
|
||||
i = 1 # numero etudiant
|
||||
for etudid in etudids:
|
||||
for etud in etuds:
|
||||
cells = []
|
||||
etud = nt.identdict[etudid]
|
||||
cells.append(ws.make_cell(str(i)))
|
||||
if sco_preferences.get_preference("prepa_jury_nip"):
|
||||
cells.append(ws.make_cell(etud["code_nip"]))
|
||||
cells.append(ws.make_cell(etud.code_nip))
|
||||
if sco_preferences.get_preference("prepa_jury_ine"):
|
||||
cells.append(ws.make_cell(etud["code_ine"]))
|
||||
cells.append(ws.make_cell(etud.code_ine))
|
||||
admission = etud.admission.first()
|
||||
cells += ws.make_row(
|
||||
[
|
||||
etudid,
|
||||
etud["civilite_str"],
|
||||
sco_etud.format_nom(etud["nom"]),
|
||||
sco_etud.format_prenom(etud["prenom"]),
|
||||
etud["date_naissance"],
|
||||
etud["bac"],
|
||||
etud["specialite"],
|
||||
etud["classement"],
|
||||
parcours[etudid],
|
||||
groupestd[etudid],
|
||||
etud.id,
|
||||
etud.civilite_str,
|
||||
sco_etud.format_nom(etud.nom),
|
||||
sco_etud.format_prenom(etud.prenom),
|
||||
etud.date_naissance,
|
||||
admission.bac,
|
||||
admission.specialite,
|
||||
admission.classement,
|
||||
parcours[etud.id],
|
||||
groupestd[etud.id],
|
||||
]
|
||||
)
|
||||
co = len(cells)
|
||||
@ -256,33 +254,35 @@ def feuille_preparation_jury(formsemestre_id):
|
||||
for ue_acro in ue_prev_codes:
|
||||
cells.append(
|
||||
ws.make_cell(
|
||||
fmt(prev_moy_ue.get(ue_acro, {}).get(etudid, "")), style_note
|
||||
fmt(prev_moy_ue.get(ue_acro, {}).get(etud.id, "")), style_note
|
||||
)
|
||||
)
|
||||
co += 1
|
||||
cells.append(
|
||||
ws.make_cell(fmt(prev_moy.get(etudid, "")), style_bold)
|
||||
ws.make_cell(fmt(prev_moy.get(etud.id, "")), style_bold)
|
||||
) # moy gen prev
|
||||
cells.append(
|
||||
ws.make_cell(fmt(prev_code.get(etudid, "")), style_moy)
|
||||
ws.make_cell(fmt(prev_code.get(etud.id, "")), style_moy)
|
||||
) # decision prev
|
||||
co += 2
|
||||
|
||||
for ue_acro in ue_codes:
|
||||
cells.append(
|
||||
ws.make_cell(fmt(moy_ue.get(ue_acro, {}).get(etudid, "")), style_note)
|
||||
ws.make_cell(fmt(moy_ue.get(ue_acro, {}).get(etud.id, "")), style_note)
|
||||
)
|
||||
co += 1
|
||||
cells.append(ws.make_cell(fmt(moy.get(etudid, "")), style_note_bold)) # moy gen
|
||||
cells.append(
|
||||
ws.make_cell(fmt(moy.get(etud.id, "")), style_note_bold)
|
||||
) # moy gen
|
||||
co += 1
|
||||
if moy_inter:
|
||||
cells.append(ws.make_cell(fmt(moy_inter.get(etudid, "")), style_note))
|
||||
cells.append(ws.make_cell(str(nbabs.get(etudid, "")), style_center))
|
||||
cells.append(ws.make_cell(str(nbabsjust.get(etudid, "")), style_center))
|
||||
cells.append(ws.make_cell(fmt(moy_inter.get(etud.id, "")), style_note))
|
||||
cells.append(ws.make_cell(str(nbabs.get(etud.id, "")), style_center))
|
||||
cells.append(ws.make_cell(str(nbabsjust.get(etud.id, "")), style_center))
|
||||
if code:
|
||||
cells.append(ws.make_cell(code.get(etudid, ""), style_moy))
|
||||
cells.append(ws.make_cell(autorisations.get(etudid, ""), style_moy))
|
||||
# l.append(assidu.get(etudid, ''))
|
||||
cells.append(ws.make_cell(code.get(etud.id, ""), style_moy))
|
||||
cells.append(ws.make_cell(autorisations.get(etud.id, ""), style_moy))
|
||||
# l.append(assidu.get(etud.id, ''))
|
||||
ws.append_row(cells)
|
||||
i += 1
|
||||
#
|
||||
@ -326,6 +326,7 @@ def feuille_preparation_jury(formsemestre_id):
|
||||
)
|
||||
)
|
||||
xls = ws.generate()
|
||||
flash("Feuille préparation jury générée")
|
||||
return scu.send_file(
|
||||
xls,
|
||||
f"PrepaJury{sn}",
|
||||
|
@ -105,7 +105,7 @@ def _descr_decisions_ues(nt, etudid, decisions_ue, decision_sem):
|
||||
for ue_id in nt.validations.ue_capitalisees.loc[[etudid]]["ue_id"]:
|
||||
try:
|
||||
uelist.append(nt.get_etud_ue_status(etudid, ue_id)["ue"])
|
||||
except KeyError:
|
||||
except (KeyError, TypeError):
|
||||
pass
|
||||
uelist.sort(key=itemgetter("numero"))
|
||||
|
||||
@ -162,13 +162,13 @@ def _comp_ects_by_ue_code(nt, decision_ues):
|
||||
return ects_by_ue_code
|
||||
|
||||
|
||||
def _comp_ects_capitalises_by_ue_code(nt, etudid):
|
||||
def _comp_ects_capitalises_by_ue_code(nt: NotesTableCompat, etudid: int):
|
||||
"""Calcul somme des ECTS des UE capitalisees"""
|
||||
ues = nt.get_ues_stat_dict()
|
||||
ects_by_ue_code = {}
|
||||
for ue in ues:
|
||||
ue_status = nt.get_etud_ue_status(etudid, ue["ue_id"])
|
||||
if ue_status["is_capitalized"]:
|
||||
if ue_status and ue_status["is_capitalized"]:
|
||||
ects_val = float(ue_status["ue"]["ects"] or 0.0)
|
||||
ects_by_ue_code[ue["ue_code"]] = ects_val
|
||||
|
||||
@ -217,9 +217,6 @@ def dict_pvjury(
|
||||
'decisions_dict' : { etudid : decision (comme ci-dessus) },
|
||||
}
|
||||
"""
|
||||
# nt = sco_cache.NotesTableCache.get(
|
||||
# formsemestre_id
|
||||
# ) # > get_etudids, get_etud_etat, get_etud_decision_sem, get_etud_decision_ues
|
||||
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
||||
if etudids is None:
|
||||
|
@ -50,7 +50,6 @@ from app.scodoc import sco_bulletins_json
|
||||
from app.scodoc import sco_bulletins_xml
|
||||
from app.scodoc import sco_bulletins, sco_excel
|
||||
from app.scodoc import sco_codes_parcours
|
||||
from app.scodoc import sco_cache
|
||||
from app.scodoc import sco_evaluations
|
||||
from app.scodoc import sco_evaluation_db
|
||||
from app.scodoc import sco_formations
|
||||
@ -307,8 +306,6 @@ def make_formsemestre_recapcomplet(
|
||||
)[0]
|
||||
parcours = formsemestre.formation.get_parcours()
|
||||
|
||||
# nt = sco_cache.NotesTableCache.get(formsemestre_id) # sco91
|
||||
# sco92 :
|
||||
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
||||
modimpls = formsemestre.modimpls_sorted
|
||||
ues = nt.get_ues_stat_dict() # incluant le(s) UE de sport
|
||||
@ -853,7 +850,13 @@ def _list_notes_evals_titles(codemodule: str, evals: list[Evaluation]) -> list[s
|
||||
L = []
|
||||
eval_index = len(evals) - 1
|
||||
for e in evals:
|
||||
L.append(codemodule + "-" + str(eval_index) + "-" + e.jour.isoformat())
|
||||
L.append(
|
||||
codemodule
|
||||
+ "-"
|
||||
+ str(eval_index)
|
||||
+ "-"
|
||||
+ (e.jour.isoformat() if e.jour else "")
|
||||
)
|
||||
eval_index -= 1
|
||||
return L
|
||||
|
||||
@ -885,8 +888,8 @@ def _formsemestre_recapcomplet_xml(
|
||||
force_publishing=True,
|
||||
):
|
||||
"XML export: liste tous les bulletins XML."
|
||||
|
||||
nt = sco_cache.NotesTableCache.get(formsemestre_id) # > get_table_moyennes_triees
|
||||
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
||||
T = nt.get_table_moyennes_triees()
|
||||
if not T:
|
||||
return "", "", "xml"
|
||||
@ -956,7 +959,8 @@ def _formsemestre_recapcomplet_json(
|
||||
"bulletins": [],
|
||||
}
|
||||
bulletins = J["bulletins"]
|
||||
nt = sco_cache.NotesTableCache.get(formsemestre_id) # > get_table_moyennes_triees
|
||||
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
||||
T = nt.get_table_moyennes_triees()
|
||||
for t in T:
|
||||
etudid = t[-1]
|
||||
|
@ -39,14 +39,16 @@ from operator import itemgetter
|
||||
from flask import url_for, g, request
|
||||
import pydot
|
||||
|
||||
from app.comp import res_sem
|
||||
from app.comp.res_common import NotesTableCompat
|
||||
from app.models import FormSemestre
|
||||
|
||||
import app.scodoc.sco_utils as scu
|
||||
from app.models import FormationModalite
|
||||
from app.scodoc import notesdb as ndb
|
||||
from app.scodoc import html_sco_header
|
||||
from app.scodoc import sco_codes_parcours
|
||||
from app.scodoc import sco_cache
|
||||
from app.scodoc import sco_etud
|
||||
from app.scodoc import sco_excel
|
||||
from app.scodoc import sco_formsemestre
|
||||
from app.scodoc import sco_formsemestre_inscriptions
|
||||
from app.scodoc import sco_parcours_dut
|
||||
@ -61,9 +63,9 @@ MAX_ETUD_IN_DESCR = 20
|
||||
|
||||
def formsemestre_etuds_stats(sem, only_primo=False):
|
||||
"""Récupère liste d'etudiants avec etat et decision."""
|
||||
nt = sco_cache.NotesTableCache.get(
|
||||
sem["formsemestre_id"]
|
||||
) # > get_table_moyennes_triees, identdict, get_etud_decision_sem, get_etud_etat,
|
||||
formsemestre = FormSemestre.query.get_or_404(sem["formsemestre_id"])
|
||||
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
||||
|
||||
T = nt.get_table_moyennes_triees()
|
||||
# Construit liste d'étudiants du semestre avec leur decision
|
||||
etuds = []
|
||||
@ -400,9 +402,8 @@ def table_suivi_cohorte(
|
||||
|
||||
logt("table_suivi_cohorte: start")
|
||||
# 1-- Liste des semestres posterieurs dans lesquels ont été les etudiants de sem
|
||||
nt = sco_cache.NotesTableCache.get(
|
||||
formsemestre_id
|
||||
) # > get_etudids, get_etud_decision_sem
|
||||
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
||||
etudids = nt.get_etudids()
|
||||
|
||||
logt("A: orig etuds set")
|
||||
@ -456,9 +457,8 @@ def table_suivi_cohorte(
|
||||
s["members"] = orig_set.intersection(inset)
|
||||
nb_dipl = 0 # combien de diplomes dans ce semestre ?
|
||||
if s["semestre_id"] == nt.parcours.NB_SEM:
|
||||
nt = sco_cache.NotesTableCache.get(
|
||||
s["formsemestre_id"]
|
||||
) # > get_etud_decision_sem
|
||||
s_formsemestre = FormSemestre.query.get_or_404(s["formsemestre_id"])
|
||||
nt: NotesTableCompat = res_sem.load_formsemestre_results(s_formsemestre)
|
||||
for etudid in s["members"]:
|
||||
dec = nt.get_etud_decision_sem(etudid)
|
||||
if dec and code_semestre_validant(dec["code"]):
|
||||
@ -905,9 +905,9 @@ def _descr_etud_set(etudids):
|
||||
|
||||
def _count_dem_reo(formsemestre_id, etudids):
|
||||
"count nb of demissions and reorientation in this etud set"
|
||||
nt = sco_cache.NotesTableCache.get(
|
||||
formsemestre_id
|
||||
) # > get_etud_etat, get_etud_decision_sem
|
||||
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
||||
|
||||
dems = set()
|
||||
reos = set()
|
||||
for etudid in etudids:
|
||||
@ -971,9 +971,9 @@ def get_codeparcoursetud(etud, prefix="", separator=""):
|
||||
i = len(sems) - 1
|
||||
while i >= 0:
|
||||
s = sems[i] # 'sems' est a l'envers, du plus recent au plus ancien
|
||||
nt = sco_cache.NotesTableCache.get(
|
||||
s["formsemestre_id"]
|
||||
) # > get_etud_etat, get_etud_decision_sem
|
||||
s_formsemestre = FormSemestre.query.get_or_404(s["formsemestre_id"])
|
||||
nt: NotesTableCompat = res_sem.load_formsemestre_results(s_formsemestre)
|
||||
|
||||
p.append(_codesem(s, prefix=prefix))
|
||||
# code decisions jury de chaque semestre:
|
||||
if nt.get_etud_etat(etud["etudid"]) == "D":
|
||||
@ -1017,7 +1017,8 @@ def tsp_etud_list(
|
||||
"""
|
||||
# log('tsp_etud_list(%s, bac="%s")' % (formsemestre_id,bac))
|
||||
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
||||
nt = sco_cache.NotesTableCache.get(formsemestre_id) # > get_etudids,
|
||||
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
||||
etudids = nt.get_etudids()
|
||||
etuds = []
|
||||
bacs = set()
|
||||
@ -1260,9 +1261,8 @@ def graph_parcours(
|
||||
nxt = {}
|
||||
etudid = etud["etudid"]
|
||||
for s in etud["sems"]: # du plus recent au plus ancien
|
||||
nt = sco_cache.NotesTableCache.get(
|
||||
s["formsemestre_id"]
|
||||
) # > get_etud_decision_sem, get_etud_etat
|
||||
s_formsemestre = FormSemestre.query.get_or_404(s["formsemestre_id"])
|
||||
nt: NotesTableCompat = res_sem.load_formsemestre_results(s_formsemestre)
|
||||
dec = nt.get_etud_decision_sem(etudid)
|
||||
if nxt:
|
||||
if (
|
||||
|
@ -29,15 +29,16 @@
|
||||
|
||||
Formulaire revu en juillet 2016
|
||||
"""
|
||||
import sys
|
||||
import time
|
||||
import datetime
|
||||
import psycopg2
|
||||
|
||||
import flask
|
||||
from flask import g, url_for, request
|
||||
from flask_login import current_user
|
||||
|
||||
from app.comp import res_sem
|
||||
from app.comp.res_common import NotesTableCompat
|
||||
from app.models import FormSemestre
|
||||
import app.scodoc.sco_utils as scu
|
||||
from app.scodoc.sco_utils import ModuleType
|
||||
import app.scodoc.notesdb as ndb
|
||||
@ -49,7 +50,6 @@ from app.scodoc.sco_exceptions import (
|
||||
ScoGenError,
|
||||
ScoValueError,
|
||||
)
|
||||
from app.scodoc.sco_permissions import Permission
|
||||
from app.scodoc.TrivialFormulator import TrivialFormulator, TF
|
||||
from app.scodoc import html_sco_header, sco_users
|
||||
from app.scodoc import htmlutils
|
||||
@ -813,8 +813,8 @@ def feuille_saisie_notes(evaluation_id, group_ids=[]):
|
||||
evaltitre = "évaluation du %s" % E["jour"]
|
||||
description = "%s en %s (%s) resp. %s" % (
|
||||
evaltitre,
|
||||
Mod["abbrev"],
|
||||
Mod["code"],
|
||||
Mod["abbrev"] or "",
|
||||
Mod["code"] or "",
|
||||
mod_responsable["prenomnom"],
|
||||
)
|
||||
|
||||
@ -872,9 +872,8 @@ def has_existing_decision(M, E, etudid):
|
||||
Si oui, return True
|
||||
"""
|
||||
formsemestre_id = M["formsemestre_id"]
|
||||
nt = sco_cache.NotesTableCache.get(
|
||||
formsemestre_id
|
||||
) # > get_etud_decision_sem, get_etud_decision_ues
|
||||
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
||||
if nt.get_etud_decision_sem(etudid):
|
||||
return True
|
||||
dec_ues = nt.get_etud_decision_ues(etudid)
|
||||
|
@ -42,6 +42,9 @@ sem_set_list()
|
||||
import flask
|
||||
from flask import g
|
||||
|
||||
from app.comp import res_sem
|
||||
from app.comp.res_common import NotesTableCompat
|
||||
from app.models import FormSemestre
|
||||
from app.scodoc import html_sco_header
|
||||
from app.scodoc import sco_cache
|
||||
from app.scodoc import sco_etape_apogee
|
||||
@ -239,7 +242,8 @@ class SemSet(dict):
|
||||
self["etuds_without_nip"] = set() # etudids
|
||||
self["jury_ok"] = True
|
||||
for sem in self.sems:
|
||||
nt = sco_cache.NotesTableCache.get(sem["formsemestre_id"])
|
||||
formsemestre = FormSemestre.query.get_or_404(sem["formsemestre_id"])
|
||||
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
||||
sem["etuds"] = list(nt.identdict.values())
|
||||
sem["nips"] = {e["code_nip"] for e in sem["etuds"] if e["code_nip"]}
|
||||
sem["etuds_without_nip"] = {
|
||||
|
@ -37,6 +37,9 @@ import http
|
||||
|
||||
from flask import g, url_for
|
||||
|
||||
from app.comp import res_sem
|
||||
from app.comp.res_common import NotesTableCompat
|
||||
from app.models import FormSemestre
|
||||
import app.scodoc.sco_utils as scu
|
||||
import app.scodoc.notesdb as ndb
|
||||
from app import log
|
||||
@ -269,7 +272,8 @@ def get_etud_tagged_modules(etudid, tagname):
|
||||
etud = sco_etud.get_etud_info(etudid=etudid, filled=True)[0]
|
||||
R = []
|
||||
for sem in etud["sems"]:
|
||||
nt = sco_cache.NotesTableCache.get(sem["formsemestre_id"])
|
||||
formsemestre = FormSemestre.query.get_or_404(sem["formsemestre_id"])
|
||||
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
||||
modimpls = nt.get_modimpls_dict()
|
||||
for modimpl in modimpls:
|
||||
tags = module_tag_list(module_id=modimpl["module_id"])
|
||||
|
@ -227,7 +227,10 @@ def get_mention(moy):
|
||||
moy = float(moy)
|
||||
except:
|
||||
return ""
|
||||
if moy > 0.0:
|
||||
return NOTES_MENTIONS_LABS[bisect.bisect_right(NOTES_MENTIONS_TH, moy)]
|
||||
else:
|
||||
return ""
|
||||
|
||||
|
||||
class DictDefault(dict): # obsolete, use collections.defaultdict
|
||||
|
@ -97,7 +97,8 @@ section>div:nth-child(1){
|
||||
.hide_coef .synthese em,
|
||||
.hide_coef .eval>em,
|
||||
.hide_date_inscr .dateInscription,
|
||||
.hide_ects .ects{
|
||||
.hide_ects .ects,
|
||||
.hide_rangs .rang{
|
||||
display: none;
|
||||
}
|
||||
|
||||
@ -158,7 +159,10 @@ section>div:nth-child(1){
|
||||
text-align: right;
|
||||
}
|
||||
.rang{
|
||||
text-decoration: underline var(--couleurIntense);
|
||||
font-weight: bold;
|
||||
}
|
||||
.ue .rang{
|
||||
font-weight: 400;
|
||||
}
|
||||
.decision{
|
||||
margin: 5px 0;
|
||||
@ -186,6 +190,9 @@ section>div:nth-child(1){
|
||||
.synthese h3{
|
||||
background: var(--couleurFondTitresUE);
|
||||
}
|
||||
.synthese .ue>div{
|
||||
text-align: right;
|
||||
}
|
||||
.synthese em,
|
||||
.eval em{
|
||||
opacity: 0.6;
|
||||
@ -308,6 +315,14 @@ h3{
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 700px) {
|
||||
section{
|
||||
padding: 16px;
|
||||
}
|
||||
.syntheseModule, .eval {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
/*.absences{
|
||||
display: grid;
|
||||
grid-template-columns: auto auto;
|
||||
|
@ -254,6 +254,7 @@ class releveBUT extends HTMLElement {
|
||||
</h3>
|
||||
<div>
|
||||
<div class=moyenne>Moyenne : ${dataUE.moyenne?.value || "-"}</div>
|
||||
<div class=rang>Rang : ${dataUE.moyenne?.rang} / ${dataUE.moyenne?.total}</div>
|
||||
<div class=info>
|
||||
Bonus : ${dataUE.bonus || 0} -
|
||||
Malus : ${dataUE.malus || 0}
|
||||
@ -299,7 +300,7 @@ class releveBUT extends HTMLElement {
|
||||
Object.values(module.evaluations).forEach((evaluation) => {
|
||||
output += `
|
||||
<div class=syntheseModule>
|
||||
<div>${module.titre} - ${evaluation.description}</div>
|
||||
<div>${module.titre} - ${evaluation.description || "Note"}</div>
|
||||
<div>
|
||||
${evaluation.note.value ?? "-"}
|
||||
<em>Coef. ${evaluation.coef}</em>
|
||||
|
@ -6,10 +6,23 @@
|
||||
<h1>Charger un référentiel de compétences</h1>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<div class="col-md-5">
|
||||
{{ wtf.quick_form(form) }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-5">
|
||||
<ul>
|
||||
<li>
|
||||
<a href="{{ url_for('notes.refcomp_table', scodoc_dept=g.scodoc_dept, ) }}">
|
||||
Liste des référentiels de compétences chargés</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{{ url_for('notes.refcomp_assoc_formation', scodoc_dept=g.scodoc_dept, formation_id=formation.id) }}">
|
||||
Association à la formation {{ formation.acronyme }}</a>
|
||||
</li>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
@ -35,6 +35,11 @@
|
||||
<b>{{ue.acronyme}}</b> <a class="discretelink" href="{{
|
||||
url_for('notes.ue_infos', scodoc_dept=g.scodoc_dept, ue_id=ue.id)}}"
|
||||
>{{ue.titre}}</a>
|
||||
{% set virg = joiner(", ") %}
|
||||
<span class="ue_code">(
|
||||
{%- if ue.ue_code -%}{{ virg() }}code {{ue.ue_code}} {%- endif -%}
|
||||
{{ virg() }}{{ue.ects or 0}} ECTS)
|
||||
</span>
|
||||
</span>
|
||||
|
||||
{% if editable and not ue.is_locked() %}
|
||||
|
@ -12,7 +12,7 @@
|
||||
<li>{{ue.acronyme}}
|
||||
<ul>
|
||||
<li>Semestre: {{ue.semestre_idx}}</li>
|
||||
<li>Code: {{ue.code}}</li>
|
||||
<li>Code: <tt>{{ue.ue_code}}</tt></li>
|
||||
<li>Type: {{ue.type}}</li>
|
||||
<li>Externe: {{ "oui" if ue.is_external else "non" }}</li>
|
||||
<li>Code Apogée: {{ue.code_apogee}}</li>
|
||||
|
@ -60,22 +60,22 @@ from flask import g, request
|
||||
from flask import url_for
|
||||
from flask_login import current_user
|
||||
|
||||
from app import log
|
||||
from app.comp import res_sem
|
||||
from app.comp.res_common import NotesTableCompat
|
||||
from app.decorators import (
|
||||
scodoc,
|
||||
scodoc7func,
|
||||
permission_required,
|
||||
admin_required,
|
||||
login_required,
|
||||
permission_required_compat_scodoc7,
|
||||
)
|
||||
|
||||
from app.models import FormSemestre
|
||||
from app.models.absences import BilletAbsence
|
||||
from app.views import absences_bp as bp
|
||||
|
||||
# ---------------
|
||||
from app.models.absences import BilletAbsence
|
||||
from app.scodoc import sco_utils as scu
|
||||
from app.scodoc import notesdb as ndb
|
||||
from app import log
|
||||
from app.scodoc.scolog import logdb
|
||||
from app.scodoc.sco_permissions import Permission
|
||||
from app.scodoc.sco_exceptions import ScoValueError, APIInvalidParams
|
||||
@ -83,15 +83,10 @@ from app.scodoc.TrivialFormulator import TrivialFormulator
|
||||
from app.scodoc.gen_tables import GenTable
|
||||
from app.scodoc import html_sco_header
|
||||
from app.scodoc import sco_abs
|
||||
from app.scodoc import sco_abs_notification
|
||||
from app.scodoc import sco_abs_views
|
||||
from app.scodoc import sco_cache
|
||||
from app.scodoc import sco_compute_moy
|
||||
from app.scodoc import sco_etud
|
||||
from app.scodoc import sco_excel
|
||||
from app.scodoc import sco_find_etud
|
||||
from app.scodoc import sco_formsemestre
|
||||
from app.scodoc import sco_groups
|
||||
from app.scodoc import sco_groups_view
|
||||
from app.scodoc import sco_moduleimpl
|
||||
from app.scodoc import sco_preferences
|
||||
@ -372,8 +367,12 @@ def SignaleAbsenceGrHebdo(
|
||||
else:
|
||||
# Si aucun etudiant n'est inscrit au module choisi...
|
||||
moduleimpl_id = None
|
||||
nt = sco_cache.NotesTableCache.get(formsemestre_id)
|
||||
sem = sco_formsemestre.do_formsemestre_list({"formsemestre_id": formsemestre_id})[0]
|
||||
|
||||
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
||||
|
||||
sem = formsemestre.to_dict()
|
||||
# sem = sco_formsemestre.do_formsemestre_list({"formsemestre_id": formsemestre_id})[0]
|
||||
|
||||
# calcule dates jours de cette semaine
|
||||
# liste de dates iso "yyyy-mm-dd"
|
||||
@ -444,8 +443,9 @@ def SignaleAbsenceGrHebdo(
|
||||
% {
|
||||
"modimpl_id": modimpl["moduleimpl_id"],
|
||||
"modname": modimpl["module"]["code"]
|
||||
or ""
|
||||
+ " "
|
||||
+ (modimpl["module"]["abbrev"] or modimpl["module"]["titre"]),
|
||||
+ (modimpl["module"]["abbrev"] or modimpl["module"]["titre"] or ""),
|
||||
"sel": sel,
|
||||
}
|
||||
)
|
||||
@ -493,10 +493,11 @@ def SignaleAbsenceGrSemestre(
|
||||
+ html_sco_header.sco_footer()
|
||||
)
|
||||
formsemestre_id = groups_infos.formsemestre_id
|
||||
formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||
sem = formsemestre.to_dict()
|
||||
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,7 +527,7 @@ def SignaleAbsenceGrSemestre(
|
||||
base_url = base_url_noweeks + "&nbweeks=%s" % nbweeks # sans le moduleimpl_id
|
||||
|
||||
if etuds:
|
||||
nt = sco_cache.NotesTableCache.get(formsemestre_id)
|
||||
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
||||
|
||||
work_saturday = sco_abs.is_work_saturday()
|
||||
jourdebut = sco_abs.ddmmyyyy(datedebut, work_saturday=work_saturday)
|
||||
@ -611,6 +612,7 @@ def SignaleAbsenceGrSemestre(
|
||||
% {
|
||||
"modimpl_id": modimpl["moduleimpl_id"],
|
||||
"modname": modimpl["module"]["code"]
|
||||
or ""
|
||||
+ " "
|
||||
+ (modimpl["module"]["abbrev"] or modimpl["module"]["titre"]),
|
||||
"sel": sel,
|
||||
@ -729,12 +731,13 @@ def _gen_form_saisie_groupe(
|
||||
# UE capitalisee dans semestre courant ?
|
||||
cap = []
|
||||
if etud["cursem"]:
|
||||
nt = sco_cache.NotesTableCache.get(
|
||||
formsemestre = FormSemestre.query.get_or_404(
|
||||
etud["cursem"]["formsemestre_id"]
|
||||
) # > get_ues_stat_dict, get_etud_ue_status
|
||||
)
|
||||
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
||||
for ue in nt.get_ues_stat_dict():
|
||||
status = nt.get_etud_ue_status(etudid, ue["ue_id"])
|
||||
if status["is_capitalized"]:
|
||||
ue_status = nt.get_etud_ue_status(etudid, ue["ue_id"])
|
||||
if ue_status and ue_status["is_capitalized"]:
|
||||
cap.append(ue["acronyme"])
|
||||
if cap:
|
||||
capstr = ' <span class="capstr">(%s cap.)</span>' % ", ".join(cap)
|
||||
|
@ -39,6 +39,9 @@ from flask import flash, jsonify, render_template, url_for
|
||||
from flask import current_app, g, request
|
||||
from flask_login import current_user
|
||||
from werkzeug.utils import redirect
|
||||
|
||||
from app.comp import res_sem
|
||||
from app.comp.res_common import NotesTableCompat
|
||||
from app.models.formsemestre import FormSemestre
|
||||
from app.models.formsemestre import FormSemestreUEComputationExpr
|
||||
from app.models.ues import UniteEns
|
||||
@ -1319,7 +1322,7 @@ def formsemestre_enseignants_list(formsemestre_id, format="html"):
|
||||
# description textuelle des modules
|
||||
for ens in sem_ens:
|
||||
sem_ens[ens]["descr_mods"] = ", ".join(
|
||||
[x["module"]["code"] for x in sem_ens[ens]["mods"]]
|
||||
[x["module"]["code"] or "?" for x in sem_ens[ens]["mods"]]
|
||||
)
|
||||
|
||||
# ajoute infos sur enseignant:
|
||||
@ -1427,13 +1430,14 @@ def formsemestre_desinscription(etudid, formsemestre_id, dialog_confirmed=False)
|
||||
S'il s'agit d'un semestre extérieur et qu'il n'y a plus d'inscrit,
|
||||
le semestre sera supprimé.
|
||||
"""
|
||||
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
||||
formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||
sem = formsemestre.to_dict() # compat
|
||||
# -- check lock
|
||||
if not sem["etat"]:
|
||||
if not formsemestre.etat:
|
||||
raise ScoValueError("desinscription impossible: semestre verrouille")
|
||||
|
||||
# -- Si décisions de jury, désinscription interdite
|
||||
nt = sco_cache.NotesTableCache.get(formsemestre_id)
|
||||
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
||||
if nt.etud_has_decision(etudid):
|
||||
raise ScoValueError(
|
||||
"""Désinscription impossible: l'étudiant a une décision de jury
|
||||
@ -1446,7 +1450,7 @@ def formsemestre_desinscription(etudid, formsemestre_id, dialog_confirmed=False)
|
||||
)
|
||||
if not dialog_confirmed:
|
||||
etud = sco_etud.get_etud_info(etudid=etudid, filled=True)[0]
|
||||
if sem["modalite"] != "EXT":
|
||||
if formsemestre.modalite != "EXT":
|
||||
msg_ext = """
|
||||
<p>%s sera désinscrit de tous les modules du semestre %s (%s - %s).</p>
|
||||
<p>Cette opération ne doit être utilisée que pour corriger une <b>erreur</b> !
|
||||
@ -1898,7 +1902,8 @@ def formsemestre_bulletins_mailetuds(
|
||||
):
|
||||
"envoi a chaque etudiant (inscrit et ayant un mail) son bulletin"
|
||||
prefer_mail_perso = int(prefer_mail_perso)
|
||||
nt = sco_cache.NotesTableCache.get(formsemestre_id) # > get_etudids
|
||||
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
||||
etudids = nt.get_etudids()
|
||||
#
|
||||
if not sco_bulletins.can_send_bulletin_by_mail(formsemestre_id):
|
||||
@ -1920,7 +1925,7 @@ def formsemestre_bulletins_mailetuds(
|
||||
nb_send = 0
|
||||
for etudid in etudids:
|
||||
h, _ = sco_bulletins.do_formsemestre_bulletinetud(
|
||||
formsemestre_id,
|
||||
formsemestre,
|
||||
etudid,
|
||||
version=version,
|
||||
prefer_mail_perso=prefer_mail_perso,
|
||||
@ -2247,9 +2252,10 @@ def formsemestre_validation_suppress_etud(
|
||||
dest_url=scu.ScoURL(),
|
||||
)
|
||||
if not dialog_confirmed:
|
||||
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
||||
etud = sco_etud.get_etud_info(etudid=etudid, filled=True)[0]
|
||||
nt = sco_cache.NotesTableCache.get(formsemestre_id) # > get_etud_decision_sem
|
||||
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||
sem = formsemestre.to_dict()
|
||||
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
||||
decision_jury = nt.get_etud_decision_sem(etudid)
|
||||
if decision_jury:
|
||||
existing = (
|
||||
|
@ -3,9 +3,11 @@ PN / Référentiel de compétences
|
||||
|
||||
Emmanuel Viennet, 2021
|
||||
"""
|
||||
from pathlib import Path
|
||||
import re
|
||||
|
||||
from flask import url_for, flash
|
||||
from flask import jsonify
|
||||
from flask import jsonify, flash, url_for
|
||||
from flask import Markup
|
||||
from flask import current_app, g, request
|
||||
from flask.templating import render_template
|
||||
from flask_login import current_user
|
||||
@ -15,7 +17,7 @@ from werkzeug.utils import secure_filename
|
||||
from config import Config
|
||||
|
||||
from app import db
|
||||
from app import models
|
||||
from app import log
|
||||
|
||||
from app.decorators import scodoc, permission_required
|
||||
from app.models import Formation
|
||||
@ -23,9 +25,8 @@ from app.models.but_refcomp import ApcReferentielCompetences
|
||||
from app.but.import_refcomp import orebut_import_refcomp
|
||||
from app.but.forms.refcomp_forms import FormationRefCompForm, RefCompLoadForm
|
||||
from app.scodoc.gen_tables import GenTable
|
||||
from app.scodoc import html_sidebar
|
||||
from app.scodoc import sco_utils as scu
|
||||
from app.scodoc.sco_exceptions import ScoFormatError
|
||||
from app.scodoc.sco_exceptions import ScoFormatError, ScoValueError
|
||||
from app.scodoc.sco_permissions import Permission
|
||||
from app.views import notes_bp as bp
|
||||
from app.views import ScoData
|
||||
@ -171,14 +172,61 @@ def refcomp_load(formation_id=None):
|
||||
formation = Formation.query.get_or_404(formation_id)
|
||||
else:
|
||||
formation = None
|
||||
refs_distrib_files = sorted(
|
||||
list(
|
||||
(
|
||||
Path(current_app.config["SCODOC_DIR"])
|
||||
/ "ressources/referentiels/but2022/competences"
|
||||
).glob("*.xml")
|
||||
)
|
||||
)
|
||||
refs_distrib_dict = [{"id": 0, "specialite": "Aucun", "created": "", "serial": ""}]
|
||||
i = 1
|
||||
for filename in refs_distrib_files:
|
||||
m = re.match(r".*/but-([A-Za-z_]+)-([0-9]+)-([0-9]+).xml", str(filename))
|
||||
if (
|
||||
m
|
||||
and ApcReferentielCompetences.query.filter_by(
|
||||
scodoc_orig_filename=Path(filename).name, dept_id=g.scodoc_dept_id
|
||||
).count()
|
||||
== 0
|
||||
):
|
||||
refs_distrib_dict.append(
|
||||
{
|
||||
"id": i,
|
||||
"specialite": m.group(1),
|
||||
"created": m.group(2),
|
||||
"serial": m.group(3),
|
||||
"filename": str(filename),
|
||||
}
|
||||
)
|
||||
i += 1
|
||||
else:
|
||||
log(f"refcomp_load: ignoring {filename} (invalid filename)")
|
||||
|
||||
form = RefCompLoadForm()
|
||||
form.referentiel_standard.choices = [
|
||||
(r["id"], f"{r['specialite']} ({r['created']}-{r['serial']})")
|
||||
for r in refs_distrib_dict
|
||||
]
|
||||
if form.validate_on_submit():
|
||||
if form.upload.data:
|
||||
f = form.upload.data
|
||||
filename = secure_filename(f.filename)
|
||||
elif form.referentiel_standard.data:
|
||||
try:
|
||||
filename = refs_distrib_dict[int(form.referentiel_standard.data)][
|
||||
"filename"
|
||||
]
|
||||
except (ValueError, IndexError):
|
||||
raise ScoValueError("choix invalide")
|
||||
f = open(filename)
|
||||
else:
|
||||
raise ScoValueError("choix invalide")
|
||||
try:
|
||||
xml_data = f.read()
|
||||
_ = orebut_import_refcomp(
|
||||
xml_data, dept_id=g.scodoc_dept_id, orig_filename=filename
|
||||
xml_data, dept_id=g.scodoc_dept_id, orig_filename=Path(filename).name
|
||||
)
|
||||
except TypeError as exc:
|
||||
raise ScoFormatError(
|
||||
@ -187,6 +235,11 @@ def refcomp_load(formation_id=None):
|
||||
except ScoFormatError:
|
||||
raise
|
||||
|
||||
flash(
|
||||
Markup(f"Référentiel <tt>{Path(filename).name}</tt> chargé."),
|
||||
category="info",
|
||||
)
|
||||
|
||||
if formation is not None:
|
||||
return redirect(
|
||||
url_for(
|
||||
|
@ -35,7 +35,15 @@ import io
|
||||
import re
|
||||
|
||||
import flask
|
||||
from flask import abort, flash, url_for, redirect, render_template, send_file
|
||||
from flask import (
|
||||
abort,
|
||||
flash,
|
||||
make_response,
|
||||
redirect,
|
||||
render_template,
|
||||
send_file,
|
||||
url_for,
|
||||
)
|
||||
from flask import request
|
||||
import flask_login
|
||||
from flask_login.utils import login_required, current_user
|
||||
@ -54,6 +62,7 @@ from app.models import Departement, Identite
|
||||
from app.models import departements
|
||||
from app.models import FormSemestre, FormSemestreInscription
|
||||
from app.models import ScoDocSiteConfig
|
||||
from app.models import UniteEns
|
||||
from app.scodoc import sco_codes_parcours, sco_logos
|
||||
from app.scodoc import sco_find_etud
|
||||
from app.scodoc import sco_utils as scu
|
||||
@ -264,11 +273,15 @@ def get_bonus_description(bonus_name: str):
|
||||
bonus_name = ""
|
||||
bonus_class = ScoDocSiteConfig.get_bonus_sport_class_from_name(bonus_name)
|
||||
text = bonus_class.__doc__
|
||||
if text:
|
||||
fields = re.split(r"\n\n", text, maxsplit=1)
|
||||
if len(fields) > 1:
|
||||
first_line, text = fields
|
||||
else:
|
||||
first_line, text = "", fields[0]
|
||||
else:
|
||||
text = ""
|
||||
first_line = "pas de fonction bonus configurée."
|
||||
|
||||
return f"""<div class="bonus_description_head">{first_line}</div>
|
||||
<div>{text}</div>
|
||||
@ -350,6 +363,29 @@ def get_logo(name: str, dept_id: int):
|
||||
)
|
||||
|
||||
|
||||
# ---
|
||||
@bp.route("/ScoDoc/ue_colors_css/<int:formation_id>/<int:semestre_idx>")
|
||||
def ue_colors_css(formation_id: int, semestre_idx: int):
|
||||
"""Feuille de style pour les couleurs d'UE"""
|
||||
ues = UniteEns.query.filter_by(
|
||||
formation_id=formation_id, semestre_idx=semestre_idx
|
||||
).order_by(UniteEns.numero)
|
||||
txt = (
|
||||
":root{\n"
|
||||
+ "\n".join(
|
||||
[
|
||||
f"--color-UE{semestre_idx}-{ue_idx+1}: {ue.color};"
|
||||
for ue_idx, ue in enumerate(ues)
|
||||
if ue.color
|
||||
]
|
||||
)
|
||||
+ "\n}\n"
|
||||
)
|
||||
response = make_response(txt)
|
||||
response.headers["Content-Type"] = "text/css"
|
||||
return response
|
||||
|
||||
|
||||
# essais
|
||||
# @bp.route("/testlog")
|
||||
# def testlog():
|
||||
|
@ -35,7 +35,7 @@ import time
|
||||
|
||||
import flask
|
||||
from flask import jsonify, url_for, flash, render_template, make_response
|
||||
from flask import current_app, g, request
|
||||
from flask import g, request
|
||||
from flask_login import current_user
|
||||
from flask_wtf import FlaskForm
|
||||
from flask_wtf.file import FileField, FileAllowed
|
||||
|
94
migrations/versions/bd2c1c3d866e_refcomp_orebut.py
Normal file
94
migrations/versions/bd2c1c3d866e_refcomp_orebut.py
Normal file
@ -0,0 +1,94 @@
|
||||
"""refcomp orebut
|
||||
|
||||
Revision ID: bd2c1c3d866e
|
||||
Revises: c95d5a3bf0de
|
||||
Create Date: 2022-02-12 15:17:42.298699
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.dialects import postgresql
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = "bd2c1c3d866e"
|
||||
down_revision = "c95d5a3bf0de"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
|
||||
op.add_column("apc_competence", sa.Column("id_orebut", sa.Text(), nullable=True))
|
||||
op.drop_constraint(
|
||||
"apc_competence_referentiel_id_titre_key", "apc_competence", type_="unique"
|
||||
)
|
||||
op.create_index(
|
||||
op.f("ix_apc_competence_id_orebut"),
|
||||
"apc_competence",
|
||||
["id_orebut"],
|
||||
)
|
||||
op.add_column(
|
||||
"apc_referentiel_competences", sa.Column("annexe", sa.Text(), nullable=True)
|
||||
)
|
||||
op.add_column(
|
||||
"apc_referentiel_competences",
|
||||
sa.Column("type_structure", sa.Text(), nullable=True),
|
||||
)
|
||||
op.add_column(
|
||||
"apc_referentiel_competences",
|
||||
sa.Column("type_departement", sa.Text(), nullable=True),
|
||||
)
|
||||
op.add_column(
|
||||
"apc_referentiel_competences",
|
||||
sa.Column("version_orebut", sa.Text(), nullable=True),
|
||||
)
|
||||
|
||||
op.create_index(
|
||||
op.f("ix_notes_formsemestre_uecoef_formsemestre_id"),
|
||||
"notes_formsemestre_uecoef",
|
||||
["formsemestre_id"],
|
||||
unique=False,
|
||||
)
|
||||
op.create_index(
|
||||
op.f("ix_notes_formsemestre_uecoef_ue_id"),
|
||||
"notes_formsemestre_uecoef",
|
||||
["ue_id"],
|
||||
unique=False,
|
||||
)
|
||||
op.create_index(
|
||||
op.f("ix_scolar_formsemestre_validation_is_external"),
|
||||
"scolar_formsemestre_validation",
|
||||
["is_external"],
|
||||
unique=False,
|
||||
)
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_index(
|
||||
op.f("ix_scolar_formsemestre_validation_is_external"),
|
||||
table_name="scolar_formsemestre_validation",
|
||||
)
|
||||
op.drop_index(
|
||||
op.f("ix_notes_formsemestre_uecoef_ue_id"),
|
||||
table_name="notes_formsemestre_uecoef",
|
||||
)
|
||||
op.drop_index(
|
||||
op.f("ix_notes_formsemestre_uecoef_formsemestre_id"),
|
||||
table_name="notes_formsemestre_uecoef",
|
||||
)
|
||||
|
||||
op.drop_column("apc_referentiel_competences", "version_orebut")
|
||||
op.drop_column("apc_referentiel_competences", "type_departement")
|
||||
op.drop_column("apc_referentiel_competences", "type_structure")
|
||||
op.drop_column("apc_referentiel_competences", "annexe")
|
||||
op.drop_index(op.f("ix_apc_competence_id_orebut"), table_name="apc_competence")
|
||||
op.create_unique_constraint(
|
||||
"apc_competence_referentiel_id_titre_key",
|
||||
"apc_competence",
|
||||
["referentiel_id", "titre"],
|
||||
)
|
||||
op.drop_column("apc_competence", "id_orebut")
|
||||
# ### end Alembic commands ###
|
@ -1,13 +1,14 @@
|
||||
# -*- mode: python -*-
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
SCOVERSION = "9.1.50a"
|
||||
SCOVERSION = "9.2a-57"
|
||||
|
||||
SCONAME = "ScoDoc"
|
||||
|
||||
SCONEWS = """
|
||||
<h4>Année 2021</h4>
|
||||
<ul>
|
||||
<li>ScoDoc 9.1.50: nombreuses amélioration gestion BUT</li>
|
||||
<li>ScoDoc 9.1: gestion des formations par compétences, type BUT.</li>
|
||||
<li>ScoDoc 9.0: nouvelle architecture logicielle (Flask/Python3/Debian 11)</li>
|
||||
<li>Version mobile (en test)</li>
|
||||
|
31
scodoc.py
31
scodoc.py
@ -14,6 +14,8 @@ import click
|
||||
import flask
|
||||
from flask.cli import with_appcontext
|
||||
from flask.templating import render_template
|
||||
import psycopg2
|
||||
import sqlalchemy
|
||||
|
||||
from app import create_app, cli, db
|
||||
from app import initialize_scodoc_database
|
||||
@ -133,11 +135,11 @@ def user_create(username, role, dept, nom=None, prenom=None): # user-create
|
||||
"Create a new user"
|
||||
r = Role.get_named_role(role)
|
||||
if not r:
|
||||
sys.stderr.write("user_create: role {r} does not exist\n".format(r=role))
|
||||
sys.stderr.write(f"user_create: role {role} does not exist\n")
|
||||
return 1
|
||||
u = User.query.filter_by(user_name=username).first()
|
||||
if u:
|
||||
sys.stderr.write("user_create: user {u} already exists\n".format(u=u))
|
||||
sys.stderr.write(f"user_create: user {u} already exists\n")
|
||||
return 2
|
||||
if dept == "@all":
|
||||
dept = None
|
||||
@ -145,11 +147,26 @@ def user_create(username, role, dept, nom=None, prenom=None): # user-create
|
||||
u.add_role(r, dept)
|
||||
db.session.add(u)
|
||||
db.session.commit()
|
||||
click.echo(
|
||||
"created user, login: {u.user_name}, with role {r} in dept. {dept}".format(
|
||||
u=u, r=r, dept=dept
|
||||
)
|
||||
click.echo(f"created user, login: {u.user_name}, with role {r} in dept. {dept}")
|
||||
|
||||
|
||||
@app.cli.command()
|
||||
@click.argument("username")
|
||||
def user_delete(username): # user-delete
|
||||
"Try to delete this user. Fails if it's associated to some scodoc objects."
|
||||
u = User.query.filter_by(user_name=username).first()
|
||||
if not u:
|
||||
sys.stderr.write(f"user_delete: user {username} not found\n")
|
||||
return 2
|
||||
db.session.delete(u)
|
||||
try:
|
||||
db.session.commit()
|
||||
except (sqlalchemy.exc.IntegrityError, psycopg2.errors.ForeignKeyViolation):
|
||||
sys.stderr.write(
|
||||
f"""\nuser_delete: ne peux pas supprimer l'utilisateur {username}\ncar il est associé à des objets dans ScoDoc (modules, notes, ...).\n"""
|
||||
)
|
||||
return 1
|
||||
click.echo(f"deleted user, login: {username}")
|
||||
|
||||
|
||||
@app.cli.command()
|
||||
@ -417,6 +434,7 @@ def localize_logo(logo: str = None, dept: str = None): # migrate-scodoc7-dept-l
|
||||
@click.argument("xlsfile", type=click.File("rb"))
|
||||
@click.argument("zipfile", type=click.File("rb"))
|
||||
def photos_import_files(formsemestre_id: int, xlsfile: str, zipfile: str):
|
||||
"""Import des photos d'étudiants à partir d'une liste excel et d'un zip avec les images."""
|
||||
import app as mapp
|
||||
from app.scodoc import sco_trombino, sco_photos
|
||||
from app.scodoc import notesdb as ndb
|
||||
@ -485,6 +503,7 @@ def recursive_help(cmd, parent=None):
|
||||
|
||||
@app.cli.command()
|
||||
def dumphelp():
|
||||
"""Génère la page d'aide complète pour la doc."""
|
||||
recursive_help(app.cli)
|
||||
|
||||
|
||||
|
@ -13,6 +13,9 @@ from flask import current_app, g
|
||||
|
||||
import app
|
||||
from app import db
|
||||
from app.comp import res_sem
|
||||
from app.comp.res_common import NotesTableCompat
|
||||
from app.models import FormSemestre
|
||||
from app.scodoc import sco_cache
|
||||
from app.scodoc import sco_evaluations
|
||||
from app.scodoc import sco_evaluation_db
|
||||
@ -24,7 +27,7 @@ from tests.unit.test_sco_basic import run_sco_basic
|
||||
DEPT = TestConfig.DEPT_TEST
|
||||
|
||||
|
||||
def test_notes_table(test_client):
|
||||
def test_notes_table(test_client): # XXX A REVOIR POUR TESTER RES TODO
|
||||
"""Test construction et cache de NotesTable."""
|
||||
app.set_sco_dept(DEPT)
|
||||
assert g.scodoc_dept == DEPT
|
||||
@ -35,7 +38,8 @@ def test_notes_table(test_client):
|
||||
assert len(sems)
|
||||
sem = sems[0]
|
||||
formsemestre_id = sem["formsemestre_id"]
|
||||
nt = sco_cache.NotesTableCache.get(formsemestre_id)
|
||||
formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
||||
assert nt
|
||||
assert sco_cache.NotesTableCache.get(formsemestre_id, compute=False)
|
||||
sco_cache.invalidate_formsemestre(formsemestre_id)
|
||||
|
@ -10,6 +10,9 @@ from tests.unit import sco_fake_gen
|
||||
from flask import g
|
||||
|
||||
import app
|
||||
from app.comp import res_sem
|
||||
from app.comp.res_common import NotesTableCompat
|
||||
from app.models import FormSemestre
|
||||
from app.scodoc import sco_bulletins, sco_formsemestre
|
||||
from app.scodoc import sco_cache
|
||||
from app.scodoc import sco_formsemestre_inscriptions
|
||||
@ -33,7 +36,8 @@ def check_nt(
|
||||
(peut changer dans le futur, ne pas utiliser hors ScoDoc !)
|
||||
ne vérifie que les valeurs expected non False
|
||||
"""
|
||||
nt = sco_cache.NotesTableCache.get(formsemestre_id)
|
||||
formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
||||
mod_moy = nt.get_etud_mod_moy(moduleimpl_id, etudid)
|
||||
if expected_moy_ue is not False:
|
||||
ue_status = nt.get_etud_ue_status(etudid, ue_id)
|
||||
@ -262,7 +266,8 @@ def test_notes_modules(test_client):
|
||||
formsemestre_id=formsemestre_id,
|
||||
)
|
||||
_, _, _ = G.create_note(evaluation=e1, etud=etud, note=12.5)
|
||||
nt = sco_cache.NotesTableCache.get(formsemestre_id)
|
||||
formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
||||
ue_status = nt.get_etud_ue_status(etudid, ue_id)
|
||||
assert ue_status["nb_missing"] == 1 # 1 même si etud non inscrit à l'autre module
|
||||
assert ue_status["nb_notes"] == 1
|
||||
@ -276,7 +281,8 @@ def test_notes_modules(test_client):
|
||||
{"etudid": etuds[1]["etudid"], "moduleimpl_id": moduleimpl_id2},
|
||||
formsemestre_id=formsemestre_id,
|
||||
)
|
||||
nt = sco_cache.NotesTableCache.get(formsemestre_id)
|
||||
formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
||||
ue_status = nt.get_etud_ue_status(etudid, ue_id)
|
||||
assert ue_status["nb_missing"] == 1 # mi2 n'a pas encore de note
|
||||
assert ue_status["nb_notes"] == 1
|
||||
@ -288,7 +294,8 @@ def test_notes_modules(test_client):
|
||||
coefficient=1.0,
|
||||
)
|
||||
_, _, _ = G.create_note(evaluation=e_m2, etud=etud, note=19.5)
|
||||
nt = sco_cache.NotesTableCache.get(formsemestre_id)
|
||||
formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
||||
ue_status = nt.get_etud_ue_status(etudid, ue_id)
|
||||
assert ue_status["nb_missing"] == 1 # manque une note
|
||||
assert ue_status["nb_notes"] == 1
|
||||
|
@ -19,7 +19,10 @@ from config import TestConfig
|
||||
from tests.unit import sco_fake_gen
|
||||
|
||||
import app
|
||||
from app.scodoc import notesdb as ndb, sco_formsemestre
|
||||
from app.comp import res_sem
|
||||
from app.comp.res_common import NotesTableCompat
|
||||
from app.models import FormSemestre
|
||||
from app.scodoc import sco_formsemestre
|
||||
from app.scodoc import sco_abs
|
||||
from app.scodoc import sco_abs_views
|
||||
from app.scodoc import sco_bulletins
|
||||
@ -208,7 +211,8 @@ def run_sco_basic(verbose=False):
|
||||
redirect=False,
|
||||
)
|
||||
# Vérifie que toutes les UE des étudiants notés ont été acquises:
|
||||
nt = sco_cache.NotesTableCache.get(formsemestre_id)
|
||||
formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
||||
for etud in etuds[:5]:
|
||||
dec_ues = nt.get_etud_decision_ues(etud["etudid"])
|
||||
for ue_id in dec_ues:
|
||||
|
@ -483,7 +483,7 @@ SCO7_TABLES_ORDONNEES = [
|
||||
), # (relation) avait un id modules_enseignants_id
|
||||
("partition", "partition_id"),
|
||||
("identite", "etudid"),
|
||||
("entreprises", "entreprise_id"),
|
||||
# ("entreprises", "entreprise_id"),
|
||||
("notes_evaluation", "evaluation_id"),
|
||||
("group_descr", "group_id"),
|
||||
("group_membership", "group_membership_id"), # (relation, qui avait un id)
|
||||
@ -498,8 +498,8 @@ SCO7_TABLES_ORDONNEES = [
|
||||
("scolog", ""),
|
||||
("etud_annotations", "id"),
|
||||
("billet_absence", "billet_id"),
|
||||
("entreprise_correspondant", "entreprise_corresp_id"),
|
||||
("entreprise_contact", "entreprise_contact_id"),
|
||||
# ("entreprise_correspondant", "entreprise_corresp_id"),
|
||||
# ("entreprise_contact", "entreprise_contact_id"),
|
||||
("absences_notifications", ""),
|
||||
# ("notes_form_modalites", "form_modalite_id"), : déjà initialisées
|
||||
("notes_appreciations", "id"),
|
||||
|
Loading…
Reference in New Issue
Block a user