1
0
forked from ScoDoc/ScoDoc

Merge branch 'dev92' of https://scodoc.org/git/ScoDoc/ScoDoc into entreprises

This commit is contained in:
Arthur ZHU 2022-02-17 16:05:55 +01:00
commit 15f313fedd
89 changed files with 1505 additions and 871 deletions

View File

@ -20,10 +20,10 @@ Flask, SQLAlchemy, au lien de Python2/Zope dans les versions précédentes).
### État actuel (26 jan 22) ### É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. - 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 ### Lignes de commandes

View File

@ -76,7 +76,9 @@ class User(UserMixin, db.Model):
"Departement", "Departement",
foreign_keys=[Departement.acronym], foreign_keys=[Departement.acronym],
primaryjoin=(dept == Departement.acronym), primaryjoin=(dept == Departement.acronym),
lazy="dynamic", lazy="select",
passive_deletes="all",
uselist=False,
) )
def __init__(self, **kwargs): def __init__(self, **kwargs):
@ -236,7 +238,7 @@ class User(UserMixin, db.Model):
def get_dept_id(self) -> int: def get_dept_id(self) -> int:
"returns user's department id, or None" "returns user's department id, or None"
if self.dept: if self.dept:
return self._departement.first().id return self._departement.id
return None return None
# Permissions management: # Permissions management:

View File

@ -9,14 +9,15 @@
import datetime import datetime
from flask import url_for, g 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_utils as scu
from app.scodoc import sco_bulletins_json from app.scodoc import sco_bulletins_json
from app.scodoc import sco_bulletins_pdf
from app.scodoc import sco_preferences from app.scodoc import sco_preferences
from app.scodoc.sco_codes_parcours import UE_SPORT from app.scodoc.sco_codes_parcours import UE_SPORT
from app.scodoc.sco_utils import fmt_note from app.scodoc.sco_utils import fmt_note
from app.comp.res_but import ResultatsSemestreBUT
class BulletinBUT: class BulletinBUT:
@ -28,6 +29,7 @@ class BulletinBUT:
def __init__(self, formsemestre: FormSemestre): def __init__(self, formsemestre: FormSemestre):
""" """ """ """
self.res = ResultatsSemestreBUT(formsemestre) self.res = ResultatsSemestreBUT(formsemestre)
self.prefs = sco_preferences.SemPreferences(formsemestre.id)
def etud_ue_mod_results(self, etud, ue, modimpls) -> dict: def etud_ue_mod_results(self, etud, ue, modimpls) -> dict:
"dict synthèse résultats dans l'UE pour les modules indiqués" "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), "saes": self.etud_ue_mod_results(etud, ue, res.saes),
} }
if ue.type != UE_SPORT: 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] rangs, effectif = res.ue_rangs[ue.id]
rang = rangs[etud.id] rang = rangs[etud.id]
else: else:
@ -155,9 +157,7 @@ class BulletinBUT:
if e.visibulletin if e.visibulletin
and ( and (
modimpl_results.evaluations_etat[e.id].is_complete modimpl_results.evaluations_etat[e.id].is_complete
or sco_preferences.get_preference( or self.prefs["bul_show_all_evals"]
"bul_show_all_evals", res.formsemestre.id
)
) )
], ],
} }
@ -216,9 +216,11 @@ class BulletinBUT:
else: else:
return f"Bonus de {fmt_note(bonus_vect.iloc[0])}" return f"Bonus de {fmt_note(bonus_vect.iloc[0])}"
def bulletin_etud(self, etud, formsemestre, force_publishing=False) -> dict: def bulletin_etud(
"""Le bulletin de l'étudiant dans ce semestre. self, etud: Identite, formsemestre, force_publishing=False
Si force_publishing, rempli le bulletin même si bul_hide_xml est vrai ) -> 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). (bulletins non publiés).
""" """
res = self.res res = self.res
@ -239,7 +241,9 @@ class BulletinBUT:
}, },
"formsemestre_id": formsemestre.id, "formsemestre_id": formsemestre.id,
"etat_inscription": etat_inscription, "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: if not published:
return d return d
@ -254,7 +258,7 @@ class BulletinBUT:
"inscription": "", # inutilisé mais nécessaire pour le js de Seb. "inscription": "", # inutilisé mais nécessaire pour le js de Seb.
"groupes": [], # XXX TODO "groupes": [], # XXX TODO
"absences": { "absences": {
"injustifie": nbabsjust, "injustifie": nbabs - nbabsjust,
"total": nbabs, "total": nbabs,
}, },
} }
@ -312,3 +316,12 @@ class BulletinBUT:
) )
return d 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()

View File

@ -145,7 +145,7 @@ def bulletin_but_xml_compat(
doc.append(Element("note_max", value="20")) # notes toujours sur 20 doc.append(Element("note_max", value="20")) # notes toujours sur 20
doc.append(Element("bonus_sport_culture", value=str(bonus))) doc.append(Element("bonus_sport_culture", value=str(bonus)))
# Liste les UE / modules /evals # 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 rang_ue = 0 # XXX TODO rang de l'étudiant dans cette UE
nb_inscrits_ue = ( nb_inscrits_ue = (
nb_inscrits # approx: compliqué de définir le "nb d'inscrit à une 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) doc.append(x_ue)
if ue.type != sco_codes_parcours.UE_SPORT: if ue.type != sco_codes_parcours.UE_SPORT:
v = results.etud_moy_ue[ue.id][etud.id] 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: else:
v = 0 # XXX TODO valeur bonus sport pour cet étudiant v = results.bonus or 0.0
vmin = vmax = 0.0
x_ue.append( x_ue.append(
Element( Element(
"note", "note",
value=scu.fmt_note(v), value=scu.fmt_note(v),
min=scu.fmt_note(results.etud_moy_ue[ue.id].min()), min=scu.fmt_note(vmin),
max=scu.fmt_note(results.etud_moy_ue[ue.id].max()), max=scu.fmt_note(vmax),
) )
) )
x_ue.append(Element("ects", value=str(ue.ects if ue.ects else 0))) 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("rang", value=str(rang_ue)))
x_ue.append(Element("effectif", value=str(nb_inscrits_ue))) x_ue.append(Element("effectif", value=str(nb_inscrits_ue)))
# Liste les modules rattachés à cette 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 # Liste ici uniquement les modules rattachés à cette UE
if modimpl.module.ue.id == ue.id: if modimpl.module.ue.id == ue.id:
# mod_moy = scu.fmt_note(results.etud_moy_ue[ue.id][etud.id]) # mod_moy = scu.fmt_note(results.etud_moy_ue[ue.id][etud.id])
try:
coef = results.modimpl_coefs_df[modimpl.id][ue.id] coef = results.modimpl_coefs_df[modimpl.id][ue.id]
except KeyError:
coef = 0.0
x_mod = Element( x_mod = Element(
"module", "module",
id=str(modimpl.id), id=str(modimpl.id),
@ -214,6 +220,7 @@ def bulletin_but_xml_compat(
note_max_origin=str(e.note_max), note_max_origin=str(e.note_max),
) )
x_mod.append(x_eval) x_mod.append(x_eval)
try:
x_eval.append( x_eval.append(
Element( Element(
"note", "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: Evaluations incomplètes ou futures: XXX
# XXX TODO UE capitalisee (listee seulement si meilleure que l'UE courante) # XXX TODO UE capitalisee (listee seulement si meilleure que l'UE courante)

View File

@ -19,10 +19,12 @@ class FormationRefCompForm(FlaskForm):
class RefCompLoadForm(FlaskForm): class RefCompLoadForm(FlaskForm):
referentiel_standard = SelectField(
"Choisir un référentiel de compétences officiel BUT"
)
upload = FileField( upload = FileField(
label="Sélectionner un fichier XML Orébut", label="Ou bien sélectionner un fichier XML au format Orébut",
validators=[ validators=[
FileRequired(),
FileAllowed( FileAllowed(
[ [
"xml", "xml",
@ -33,3 +35,13 @@ class RefCompLoadForm(FlaskForm):
) )
submit = SubmitField("Valider") submit = SubmitField("Valider")
cancel = SubmitField("Annuler") 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

View File

@ -6,6 +6,8 @@
from xml.etree import ElementTree from xml.etree import ElementTree
from typing import TextIO from typing import TextIO
import sqlalchemy
from app import db from app import db
from app.models.but_refcomp import ( from app.models.but_refcomp import (
@ -19,7 +21,7 @@ from app.models.but_refcomp import (
ApcAnneeParcours, ApcAnneeParcours,
ApcParcoursNiveauCompetence, 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): 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 peut lever TypeError ou ScoFormatError
Résultat: instance de ApcReferentielCompetences 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: try:
root = ElementTree.XML(xml_data) root = ElementTree.XML(xml_data)
except ElementTree.ParseError as exc: 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: if not competences:
raise ScoFormatError("élément 'competences' manquant") raise ScoFormatError("élément 'competences' manquant")
for competence in competences.findall("competence"): for competence in competences.findall("competence"):
try:
c = ApcCompetence(**ApcCompetence.attr_from_xml(competence.attrib)) 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) ref.competences.append(c)
# --- SITUATIONS # --- SITUATIONS
situations = competence.find("situations") situations = competence.find("situations")

View File

@ -21,7 +21,8 @@ class StatsMoyenne:
Les valeurs NAN ou non numériques sont toujours enlevées. Les valeurs NAN ou non numériques sont toujours enlevées.
Si vals is None, renvoie des zéros (utilisé pour UE bonus) Si vals is None, renvoie des zéros (utilisé pour UE bonus)
""" """
if vals is None 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 self.moy = self.min = self.max = self.size = self.nb_vals = 0
else: else:
self.moy = np.nanmean(vals) self.moy = np.nanmean(vals)
@ -29,6 +30,8 @@ class StatsMoyenne:
self.max = np.nanmax(vals) self.max = np.nanmax(vals)
self.size = len(vals) self.size = len(vals)
self.nb_vals = self.size - np.count_nonzero(np.isnan(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): def to_dict(self):
"Tous les attributs dans un dict" "Tous les attributs dans un dict"

View File

@ -87,6 +87,8 @@ class BonusSport:
for m in formsemestre.modimpls_sorted 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 = [ self.modimpls_spo = [
modimpl modimpl
for i, modimpl in enumerate(formsemestre.modimpls_sorted) 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_inscr_spo, sem_modimpl_moys_no_nan, 0.0
) )
modimpl_coefs_spo = modimpl_coefs_spo.T 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_coefs_etuds = np.where(
modimpl_inscr_spo, np.stack([modimpl_coefs_spo] * nb_etuds), 0.0 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) En APC: ndarray (nb_etuds, nb_mod_sport, nb_ues_non_bonus)
modimpl_coefs_etuds_no_nan: 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( bonus_moy_arr = np.sum(
np.where( np.where(
sem_modimpl_moys_inscrits > self.seuil_moy_gen, sem_modimpl_moys_inscrits > self.seuil_moy_gen,
@ -249,6 +257,9 @@ class BonusSportMultiplicatif(BonusSport):
# bonus = m_0 (a - 1) # bonus = m_0 (a - 1)
def compute_bonus(self, sem_modimpl_moys_inscrits, modimpl_coefs_etuds_no_nan): def compute_bonus(self, sem_modimpl_moys_inscrits, modimpl_coefs_etuds_no_nan):
"""calcul du bonus""" """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: # Calcule moyenne pondérée des notes de sport:
notes = np.sum( notes = np.sum(
sem_modimpl_moys_inscrits * modimpl_coefs_etuds_no_nan, axis=1 sem_modimpl_moys_inscrits * modimpl_coefs_etuds_no_nan, axis=1

View File

@ -48,14 +48,15 @@ def compute_sem_moys_apc(
return moy_gen 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 """Calcul rangs à partir d'une séries ("vecteur") de notes (index etudid, valeur
numérique) en tenant compte des ex-aequos. numérique) en tenant compte des ex-aequos.
Result: { etudid : rang:str } rang est une chaine decrivant le rang. Result: Series { etudid : rang:str } rang est une chaine decrivant le rang.
""" """
notes = notes.sort_values(ascending=False) # Serie, tri par ordre décroissant 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) N = len(notes)
nb_ex = 0 # nb d'ex-aequo consécutifs en cours nb_ex = 0 # nb d'ex-aequo consécutifs en cours
notes_i = notes.iat notes_i = notes.iat
@ -67,6 +68,7 @@ def comp_ranks_series(notes: pd.Series) -> dict[int, str]:
next = None next = None
val = notes_i[i] val = notes_i[i]
if nb_ex: if nb_ex:
rangs_int[etudid] = i + 1 - nb_ex
srang = "%d ex" % (i + 1 - nb_ex) srang = "%d ex" % (i + 1 - nb_ex)
if val == next: if val == next:
nb_ex += 1 nb_ex += 1
@ -74,9 +76,11 @@ def comp_ranks_series(notes: pd.Series) -> dict[int, str]:
nb_ex = 0 nb_ex = 0
else: else:
if val == next: if val == next:
rangs_int[etudid] = i + 1 - nb_ex
srang = "%d ex" % (i + 1 - nb_ex) srang = "%d ex" % (i + 1 - nb_ex)
nb_ex = 1 nb_ex = 1
else: else:
rangs_int[etudid] = i + 1
srang = "%d" % (i + 1) srang = "%d" % (i + 1)
rangs[etudid] = srang rangs_str[etudid] = srang
return rangs return rangs_str, rangs_int

View File

@ -136,8 +136,13 @@ def df_load_modimpl_coefs(
) )
for mod_coef in mod_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: # Initialisation des poids non fixés:
# 0 pour modules normaux, 1. pour bonus (car par défaut, on veut qu'un bonus agisse # 0 pour modules normaux, 1. pour bonus (car par défaut, on veut qu'un bonus agisse
# sur toutes les UE) # 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_inscr_df: matrice d'inscription du semestre (etud x modimpl)
modimpl_coefs_df: matrice coefficients (UE x modimpl), sans UEs bonus sport 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_etuds, nb_modules, nb_ues_no_bonus = sem_cube.shape
nb_ues_tot = len(ues) nb_ues_tot = len(ues)
assert len(modimpls) == nb_modules 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( return pd.DataFrame(
index=modimpl_inscr_df.index, columns=modimpl_coefs_df.index 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 les coefficients effectifs de chaque UE pour chaque étudiant
(sommes de coefs de modules pris en compte) (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: # Restreint aux modules sélectionnés:
sem_matrix = sem_matrix[:, modimpl_mask] sem_matrix = sem_matrix[:, modimpl_mask]
modimpl_inscr = modimpl_inscr_df.values[:, modimpl_mask] modimpl_inscr = modimpl_inscr_df.values[:, modimpl_mask]
@ -391,8 +407,9 @@ def compute_malus(
) -> pd.DataFrame: ) -> pd.DataFrame:
"""Calcul le malus sur les UE """Calcul le malus sur les UE
Dans chaque UE, on peut avoir un ou plusieurs modules de MALUS. 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 Leurs notes sont positives ou négatives.
de chaque UE. La somme des notes de malus somme est _soustraite_ à la moyenne de chaque UE.
Arguments: Arguments:
- sem_modimpl_moys : - sem_modimpl_moys :
notes moyennes aux modules (tous les étuds x tous les modimpls) notes moyennes aux modules (tous les étuds x tous les modimpls)
@ -415,6 +432,7 @@ def compute_malus(
for m in formsemestre.modimpls_sorted for m in formsemestre.modimpls_sorted
] ]
) )
if len(modimpl_mask):
malus_moys = sem_modimpl_moys[:, modimpl_mask].sum(axis=1) malus_moys = sem_modimpl_moys[:, modimpl_mask].sum(axis=1)
malus[ue.id] = malus_moys malus[ue.id] = malus_moys

View File

@ -6,9 +6,11 @@
"""Résultats semestres BUT """Résultats semestres BUT
""" """
import time
import numpy as np import numpy as np
import pandas as pd import pandas as pd
from app import log
from app.comp import moy_ue, moy_sem, inscr_mod from app.comp import moy_ue, moy_sem, inscr_mod
from app.comp.res_common import NotesTableCompat from app.comp.res_common import NotesTableCompat
from app.comp.bonus_spo import BonusSport from app.comp.bonus_spo import BonusSport
@ -30,8 +32,14 @@ class ResultatsSemestreBUT(NotesTableCompat):
super().__init__(formsemestre) super().__init__(formsemestre)
if not self.load_cached(): if not self.load_cached():
t0 = time.time()
self.compute() self.compute()
t1 = time.time()
self.store() self.store()
t2 = time.time()
log(
f"ResultatsSemestreBUT: cached formsemestre_id={formsemestre.id} ({(t1-t0):g}s +{(t2-t1):g}s)"
)
def compute(self): def compute(self):
"Charge les notes et inscriptions et calcule les moyennes d'UE et gen." "Charge les notes et inscriptions et calcule les moyennes d'UE et gen."

View File

@ -6,7 +6,7 @@
"""Résultats semestres classiques (non APC) """Résultats semestres classiques (non APC)
""" """
import time
import numpy as np import numpy as np
import pandas as pd import pandas as pd
from sqlalchemy.sql import text from sqlalchemy.sql import text
@ -40,8 +40,14 @@ class ResultatsSemestreClassic(NotesTableCompat):
super().__init__(formsemestre) super().__init__(formsemestre)
if not self.load_cached(): if not self.load_cached():
t0 = time.time()
self.compute() self.compute()
t1 = time.time()
self.store() 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) # recalculé (aussi rapide que de les cacher)
self.moy_min = self.etud_moy_gen.min() self.moy_min = self.etud_moy_gen.min()
self.moy_max = self.etud_moy_gen.max() self.moy_max = self.etud_moy_gen.max()
@ -110,9 +116,8 @@ class ResultatsSemestreClassic(NotesTableCompat):
if bonus_mg is not None: if bonus_mg is not None:
self.etud_moy_gen += bonus_mg self.etud_moy_gen += bonus_mg
self.etud_moy_gen.clip(lower=0.0, upper=20.0, inplace=True) self.etud_moy_gen.clip(lower=0.0, upper=20.0, inplace=True)
self.bonus = ( # compat nt, utilisé pour l'afficher sur les bulletins:
bonus_mg # compat nt, utilisé pour l'afficher sur les bulletins self.bonus = bonus_mg
)
# --- UE capitalisées # --- UE capitalisées
self.apply_capitalisation() 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) (Series rendus par compute_module_moy, index: etud)
Resultat: ndarray (etud x module) 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_arr = [s.values for s in modimpls_notes]
modimpls_notes = np.stack(modimpls_notes_arr) modimpls_notes = np.stack(modimpls_notes_arr)
# passe de (mod x etud) à (etud x mod) # passe de (mod x etud) à (etud x mod)

View File

@ -4,11 +4,12 @@
# See LICENSE # See LICENSE
############################################################################## ##############################################################################
from collections import defaultdict, Counter from collections import Counter
from functools import cached_property from functools import cached_property
import numpy as np import numpy as np
import pandas as pd import pandas as pd
from app import log
from app.comp.aux_stats import StatsMoyenne from app.comp.aux_stats import StatsMoyenne
from app.comp import moy_sem from app.comp import moy_sem
from app.comp.res_cache import ResultatsCache from app.comp.res_cache import ResultatsCache
@ -19,8 +20,7 @@ from app.models import FormSemestreUECoef
from app.models.ues import UniteEns from app.models.ues import UniteEns
from app.scodoc import sco_utils as scu from app.scodoc import sco_utils as scu
from app.scodoc.sco_cache import ResultatsSemestreCache from app.scodoc.sco_cache import ResultatsSemestreCache
from app.scodoc.sco_codes_parcours import UE_SPORT, ATT, DEF from app.scodoc.sco_codes_parcours import UE_SPORT, DEF
from app.scodoc.sco_exceptions import ScoValueError
# Il faut bien distinguer # Il faut bien distinguer
# - ce qui est caché de façon persistente (via redis): # - ce qui est caché de façon persistente (via redis):
@ -51,6 +51,7 @@ class ResultatsSemestre(ResultatsCache):
"etud_moy_ue: DataFrame columns UE, rows etudid" "etud_moy_ue: DataFrame columns UE, rows etudid"
self.etud_moy_gen = {} self.etud_moy_gen = {}
self.etud_moy_gen_ranks = {} self.etud_moy_gen_ranks = {}
self.etud_moy_gen_ranks_int = {}
self.modimpls_results: ModuleImplResults = None self.modimpls_results: ModuleImplResults = None
"Résultats de chaque modimpl: dict { modimpl.id : ModuleImplResults(Classique ou BUT) }" "Résultats de chaque modimpl: dict { modimpl.id : ModuleImplResults(Classique ou BUT) }"
self.etud_coef_ue_df = None self.etud_coef_ue_df = None
@ -183,7 +184,7 @@ class ResultatsSemestre(ResultatsCache):
sum_coefs_ue = 0.0 sum_coefs_ue = 0.0
for ue in self.formsemestre.query_ues(): for ue in self.formsemestre.query_ues():
ue_cap = self.get_etud_ue_status(etudid, ue.id) 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 recompute_mg = True
coef = ue_cap["coef_ue"] coef = ue_cap["coef_ue"]
if not np.isnan(ue_cap["moy"]): 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: def get_etud_ue_status(self, etudid: int, ue_id: int) -> dict:
"""L'état de l'UE pour cet étudiant. """L'état de l'UE pour cet étudiant.
L'UE doit être du semestre. Result: dict, ou None si l'UE n'est pas dans ce semestre.
Result: dict.
""" """
if not self.validations:
self.validations = res_sem.load_formsemestre_validations(self.formsemestre)
ue = UniteEns.query.get(ue_id) # TODO cacher nos UEs ? ue = UniteEns.query.get(ue_id) # TODO cacher nos UEs ?
if ue.type == UE_SPORT: if ue.type == UE_SPORT:
return { return {
"is_capitalized": False, "is_capitalized": False,
"was_capitalized": False,
"is_external": False, "is_external": False,
"coef_ue": 0.0, "coef_ue": 0.0,
"cur_moy_ue": 0.0, "cur_moy_ue": 0.0,
@ -232,9 +231,16 @@ class ResultatsSemestre(ResultatsCache):
"capitalized_ue_id": None, "capitalized_ue_id": None,
"ects_pot": 0.0, "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] cur_moy_ue = self.etud_moy_ue[ue_id][etudid]
moy_ue = cur_moy_ue 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: if etudid in self.validations.ue_capitalisees.index:
ue_cap = self._get_etud_ue_cap(etudid, ue) ue_cap = self._get_etud_ue_cap(etudid, ue)
if ( if (
@ -242,6 +248,7 @@ class ResultatsSemestre(ResultatsCache):
and not ue_cap.empty and not ue_cap.empty
and not np.isnan(ue_cap["moy_ue"]) 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): if ue_cap["moy_ue"] > cur_moy_ue or np.isnan(cur_moy_ue):
moy_ue = ue_cap["moy_ue"] moy_ue = ue_cap["moy_ue"]
is_capitalized = True is_capitalized = True
@ -250,6 +257,7 @@ class ResultatsSemestre(ResultatsCache):
return { return {
"is_capitalized": is_capitalized, "is_capitalized": is_capitalized,
"was_capitalized": was_capitalized,
"is_external": ue_cap["is_external"] if is_capitalized else ue.is_external, "is_external": ue_cap["is_external"] if is_capitalized else ue.is_external,
"coef_ue": coef_ue, "coef_ue": coef_ue,
"ects_pot": ue.ects or 0.0, "ects_pot": ue.ects or 0.0,
@ -301,6 +309,7 @@ class NotesTableCompat(ResultatsSemestre):
"bonus_ues", "bonus_ues",
"malus", "malus",
"etud_moy_gen_ranks", "etud_moy_gen_ranks",
"etud_moy_gen_ranks_int",
"ue_rangs", "ue_rangs",
) )
@ -311,41 +320,54 @@ class NotesTableCompat(ResultatsSemestre):
self.bonus = None # virtuel self.bonus = None # virtuel
self.bonus_ues = None # virtuel self.bonus_ues = None # virtuel
self.ue_rangs = {u.id: (None, nb_etuds) for u in self.ues} self.ue_rangs = {u.id: (None, nb_etuds) for u in self.ues}
self.mod_rangs = { self.mod_rangs = None # sera surchargé en Classic, mais pas en APC
m.id: (None, nb_etuds) for m in self.formsemestre.modimpls_sorted
}
self.moy_min = "NA" self.moy_min = "NA"
self.moy_max = "NA" self.moy_max = "NA"
self.moy_moy = "NA" self.moy_moy = "NA"
self.expr_diagnostics = "" self.expr_diagnostics = ""
self.parcours = self.formsemestre.formation.get_parcours() self.parcours = self.formsemestre.formation.get_parcours()
def get_etudids(self, sorted=False) -> list[int]: def get_inscrits(self, include_demdef=True, order_by=False) -> list[Identite]:
"""Liste des etudids inscrits, incluant les démissionnaires. """Liste des étudiants inscrits
Si sorted, triée par moy. générale décroissante order_by = False|'nom'|'moy' tri sur nom ou sur moyenne générale (indicative)
Sinon, triée par ordre alphabetique de NOM
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, # Note: pour avoir les inscrits non triés,
# utiliser [ ins.etudid for ins in self.formsemestre.inscriptions ] # 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] return [x["etudid"] for x in self.inscrlist]
@cached_property @cached_property
def sem(self) -> dict: def sem(self) -> dict:
"""le formsemestre, comme un dict (nt.sem)""" """le formsemestre, comme un gros et gras dict (nt.sem)"""
return self.formsemestre.to_dict() return self.formsemestre.get_infos_dict()
@cached_property @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), """Liste des inscrits au semestre (avec DEM et DEF),
sous forme de dict etud, sous forme de dict etud,
classée dans l'ordre alphabétique de noms. classée dans l'ordre alphabétique de noms.
""" """
etuds = self.formsemestre.get_inscrits(include_demdef=True) etuds = self.formsemestre.get_inscrits(include_demdef=True, order=True)
etuds.sort(key=lambda e: e.sort_key)
return [e.to_dict_scodoc7() for e in etuds] return [e.to_dict_scodoc7() for e in etuds]
@cached_property @cached_property
@ -387,11 +409,14 @@ class NotesTableCompat(ResultatsSemestre):
Moyenne générale: etud_moy_gen_ranks Moyenne générale: etud_moy_gen_ranks
Par UE (sauf ue bonus) 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(): for ue in self.formsemestre.query_ues():
moy_ue = self.etud_moy_ue[ue.id] moy_ue = self.etud_moy_ue[ue.id]
self.ue_rangs[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()), int(moy_ue.count()),
) )
# .count() -> nb of non NaN values # .count() -> nb of non NaN values
@ -420,12 +445,27 @@ class NotesTableCompat(ResultatsSemestre):
Return: True|False, message explicatif Return: True|False, message explicatif
""" """
return self.parcours.check_barre_ues( ue_status_list = []
[ for ue in self.formsemestre.query_ues():
self.get_etud_ue_status(etudid, ue.id) ue_status = self.get_etud_ue_status(etudid, ue.id)
for ue in self.formsemestre.query_ues() 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: 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. """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. Évaluation "complete" ssi toutes notes saisies ou en attente.
""" """
modimpl = ModuleImpl.query.get(moduleimpl_id) modimpl = ModuleImpl.query.get(moduleimpl_id)
modimpl_results = self.modimpls_results.get(moduleimpl_id)
if not modimpl_results:
return [] # safeguard
evals_results = [] evals_results = []
for e in modimpl.evaluations: 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() d = e.to_dict()
moduleimpl_results = self.modimpls_results[e.moduleimpl_id]
d["heure_debut"] = e.heure_debut # datetime.time d["heure_debut"] = e.heure_debut # datetime.time
d["heure_fin"] = e.heure_fin d["heure_fin"] = e.heure_fin
d["jour"] = e.jour # datetime d["jour"] = e.jour # datetime
d["notes"] = { d["notes"] = {
etud.id: { etud.id: {
"etudid": 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 for etud in self.etuds
} }
d["etat"] = { d["etat"] = {
"evalattente": moduleimpl_results.evaluations_etat[e.id].nb_attente, "evalattente": modimpl_results.evaluations_etat[e.id].nb_attente,
} }
evals_results.append(d) 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 return evals_results
def get_evaluations_etats(self): def get_evaluations_etats(self):
@ -608,7 +655,7 @@ class NotesTableCompat(ResultatsSemestre):
""" """
table_moyennes = [] table_moyennes = []
etuds_inscriptions = self.formsemestre.etuds_inscriptions 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: for etudid in etuds_inscriptions:
moy_gen = self.etud_moy_gen.get(etudid, False) moy_gen = self.etud_moy_gen.get(etudid, False)
if moy_gen is False: if moy_gen is False:
@ -623,8 +670,11 @@ class NotesTableCompat(ResultatsSemestre):
ue_is_cap = {} ue_is_cap = {}
for ue in ues: for ue in ues:
ue_status = self.get_etud_ue_status(etudid, ue.id) ue_status = self.get_etud_ue_status(etudid, ue.id)
if ue_status:
moy_ues.append(ue_status["moy"]) moy_ues.append(ue_status["moy"])
ue_is_cap[ue.id] = ue_status["is_capitalized"] ue_is_cap[ue.id] = ue_status["is_capitalized"]
else:
moy_ues.append("?")
t = [moy_gen] + list(moy_ues) t = [moy_gen] + list(moy_ues)
# Moyennes modules: # Moyennes modules:
for modimpl in self.formsemestre.modimpls_sorted: for modimpl in self.formsemestre.modimpls_sorted:

View File

@ -25,7 +25,7 @@ def load_formsemestre_results(formsemestre: FormSemestre) -> ResultatsSemestre:
""" """
# --- Try local cache (within the same request context) # --- Try local cache (within the same request context)
if not hasattr(g, "formsemestre_results_cache"): if not hasattr(g, "formsemestre_results_cache"):
g.formsemestre_results_cache = {} # pylint: disable=C0237 g.formsemestre_results_cache = {}
else: else:
if formsemestre.id in g.formsemestre_results_cache: if formsemestre.id in g.formsemestre_results_cache:
return g.formsemestre_results_cache[formsemestre.id] return g.formsemestre_results_cache[formsemestre.id]

View File

@ -81,6 +81,9 @@ class ApcReferentielCompetences(db.Model, XMLModel):
) )
formations = db.relationship("Formation", backref="referentiel_competence") formations = db.relationship("Formation", backref="referentiel_competence")
def __repr__(self):
return f"<ApcReferentielCompetences {self.id} {self.specialite}>"
def to_dict(self): def to_dict(self):
"""Représentation complète du ref. de comp. """Représentation complète du ref. de comp.
comme un dict. comme un dict.
@ -110,7 +113,8 @@ class ApcCompetence(db.Model, XMLModel):
db.Integer, db.ForeignKey("apc_referentiel_competences.id"), nullable=False db.Integer, db.ForeignKey("apc_referentiel_competences.id"), nullable=False
) )
# les compétences dans Orébut sont identifiées par leur id unique # 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 = db.Column(db.Text(), nullable=False, index=True)
titre_long = db.Column(db.Text()) titre_long = db.Column(db.Text())
couleur = db.Column(db.Text()) couleur = db.Column(db.Text())
@ -139,6 +143,9 @@ class ApcCompetence(db.Model, XMLModel):
cascade="all, delete-orphan", cascade="all, delete-orphan",
) )
def __repr__(self):
return f"<ApcCompetence {self.id} {self.titre}>"
def to_dict(self): def to_dict(self):
return { return {
"id_orebut": self.id_orebut, "id_orebut": self.id_orebut,

View File

@ -7,12 +7,14 @@
from functools import cached_property from functools import cached_property
from flask import abort, url_for from flask import abort, url_for
from flask import g, request from flask import g, request
import sqlalchemy
from app import db from app import db
from app import models from app import models
from app.scodoc import notesdb as ndb from app.scodoc import notesdb as ndb
from app.scodoc.sco_bac import Baccalaureat from app.scodoc.sco_bac import Baccalaureat
import app.scodoc.sco_utils as scu
class Identite(db.Model): class Identite(db.Model):
@ -73,6 +75,13 @@ class Identite(db.Model):
""" """
return {"M": "M.", "F": "Mme", "X": ""}[self.civilite] 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: def nom_disp(self) -> str:
"Nom à afficher" "Nom à afficher"
if self.nom_usuel: if self.nom_usuel:
@ -125,6 +134,7 @@ class Identite(db.Model):
# ScoDoc7 output_formators: (backward compat) # ScoDoc7 output_formators: (backward compat)
e["etudid"] = self.id e["etudid"] = self.id
e["date_naissance"] = ndb.DateISOtoDMY(e["date_naissance"]) 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 return {k: e[k] or "" for k in e} # convert_null_outputs_to_empty
def to_dict_bul(self, include_urls=True): 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." "Le bac. utiliser bac.abbrev() pour avoir une chaine de caractères."
return Baccalaureat(self.bac, specialite=self.specialite) 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 # Suivi scolarité / débouchés
class ItemSuivi(db.Model): class ItemSuivi(db.Model):

View File

@ -2,12 +2,16 @@
"""ScoDoc models: evaluations """ScoDoc models: evaluations
""" """
import datetime
from app import db 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 import app.scodoc.notesdb as ndb
from app.scodoc import sco_evaluation_db
class Evaluation(db.Model): class Evaluation(db.Model):
@ -51,11 +55,11 @@ class Evaluation(db.Model):
e["evaluation_id"] = self.id e["evaluation_id"] = self.id
e["jour"] = ndb.DateISOtoDMY(e["jour"]) e["jour"] = ndb.DateISOtoDMY(e["jour"])
e["numero"] = ndb.int_null_is_zero(e["numero"]) 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): def from_dict(self, data):
"""Set evaluation attributes from given dict values.""" """Set evaluation attributes from given dict values."""
sco_evaluation_db._check_evaluation_args(data) check_evaluation_args(data)
for k in self.__dict__.keys(): for k in self.__dict__.keys():
if k != "_sa_instance_state" and k != "id" and k in data: if k != "_sa_instance_state" and k != "id" and k in data:
setattr(self, k, data[k]) setattr(self, k, data[k])
@ -145,3 +149,89 @@ class EvaluationUEPoids(db.Model):
def __repr__(self): def __repr__(self):
return f"<EvaluationUEPoids {self.evaluation} {self.ue} poids={self.poids}>" 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 !")

View File

@ -117,10 +117,12 @@ class FormSemestre(db.Model):
return f"<{self.__class__.__name__} {self.id} {self.titre_num()}>" return f"<{self.__class__.__name__} {self.id} {self.titre_num()}>"
def to_dict(self): def to_dict(self):
"dict (compatible ScoDoc7)"
d = dict(self.__dict__) d = dict(self.__dict__)
d.pop("_sa_instance_state", None) d.pop("_sa_instance_state", None)
# ScoDoc7 output_formators: (backward compat) # ScoDoc7 output_formators: (backward compat)
d["formsemestre_id"] = self.id d["formsemestre_id"] = self.id
d["titre_num"] = self.titre_num()
if self.date_debut: if self.date_debut:
d["date_debut"] = self.date_debut.strftime("%d/%m/%Y") d["date_debut"] = self.date_debut.strftime("%d/%m/%Y")
d["date_debut_iso"] = self.date_debut.isoformat() 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_debut"] = str(self.date_debut.year)
d["annee"] = d["annee_debut"] d["annee"] = d["annee_debut"]
d["annee_fin"] = str(self.date_fin.year) 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_debut_ord"] = self.date_debut.month
d["mois_fin_ord"] = self.date_fin.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 # 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... d["periode"] = 1 # typiquement, début en septembre: S1, S3...
else: else:
d["periode"] = 2 # typiquement, début en février: S2, S4... d["periode"] = 2 # typiquement, début en février: S2, S4...
d["titre_num"] = self.titre_num d["titre_num"] = self.titre_num()
d["titreannee"] = "%s %s %s" % ( d["titreannee"] = self.titre_annee()
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["mois_debut"] = f"{self.date_debut.month} {self.date_debut.year}" 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["mois_fin"] = f"{self.date_fin.month} {self.date_fin.year}"
d["titremois"] = "%s %s (%s - %s)" % ( d["titremois"] = "%s %s (%s - %s)" % (
@ -332,6 +329,15 @@ class FormSemestre(db.Model):
"-".join((imputation_dept, parcours_name, modalite, semestre_id, annee_sco)) "-".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: def titre_mois(self) -> str:
"""Le titre et les dates du semestre, pour affichage dans des listes """Le titre et les dates du semestre, pour affichage dans des listes
Ex: "BUT QLIO (PN 2022) semestre 1 FI (Sept 2022 - Jan 2023)" 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() 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 """Liste des étudiants inscrits à ce semestre
Si include_demdef, tous les étudiants, avec les démissionnaires Si include_demdef, tous les étudiants, avec les démissionnaires
et défaillants. et défaillants.
Si order, tri par clé sort_key
""" """
if include_demdef: if include_demdef:
return [ins.etud for ins in self.inscriptions] etuds = [ins.etud for ins in self.inscriptions]
else: 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 @cached_property
def etudids_actifs(self) -> set: def etudids_actifs(self) -> set:

View File

@ -79,7 +79,7 @@ class ModuleImpl(db.Model):
) )
def to_dict(self): 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 = dict(self.__dict__)
e.pop("_sa_instance_state", None) e.pop("_sa_instance_state", None)
# ScoDoc7 output_formators: (backward compat) # ScoDoc7 output_formators: (backward compat)

View File

@ -4,8 +4,9 @@
""" """
from app import db 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): class BulAppreciations(db.Model):
@ -67,3 +68,32 @@ class NotesNotesLog(db.Model):
comment = db.Column(db.Text) # texte libre comment = db.Column(db.Text) # texte libre
date = db.Column(db.DateTime(timezone=True), server_default=db.func.now()) date = db.Column(db.DateTime(timezone=True), server_default=db.func.now())
uid = db.Column(db.Integer, db.ForeignKey("user.id")) 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

View File

@ -87,7 +87,7 @@ def get_tags_latex(code_latex):
""" """
if code_latex: if code_latex:
# changé par EV: était r"([\*]{2}[a-zA-Z0-9:éèàâêëïôöù]+[\*]{2})" # changé par EV: était r"([\*]{2}[a-zA-Z0-9:éèàâêëïôöù]+[\*]{2})"
res = re.findall(r"([\*]{2}[^ \t\n\r\f\v\*]+[\*]{2})", code_latex) res = re.findall(r"([\*]{2}[^\t\n\r\f\v\*]+[\*]{2})", code_latex)
return [tag[2:-2] for tag in res] return [tag[2:-2] for tag in res]
else: else:
return [] return []

View File

@ -46,9 +46,12 @@ import io
import os import os
from zipfile import ZipFile 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 from app.scodoc.gen_tables import GenTable, SeqGenTable
import app.scodoc.sco_utils as scu 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_codes_parcours # sco_codes_parcours.NEXT -> sem suivant
from app.scodoc import sco_etud from app.scodoc import sco_etud
from app.scodoc import sco_formsemestre from app.scodoc import sco_formsemestre
@ -174,6 +177,8 @@ class JuryPE(object):
self.PARCOURSINFO_DICT = {} # Les parcours des étudiants self.PARCOURSINFO_DICT = {} # Les parcours des étudiants
self.syntheseJury = {} # Le jury de synthèse self.syntheseJury = {} # Le jury de synthèse
self.semestresDeScoDoc = sco_formsemestre.do_formsemestre_list()
# Calcul du jury PE # Calcul du jury PE
self.exe_calculs_juryPE(semBase) self.exe_calculs_juryPE(semBase)
self.synthetise_juryPE() self.synthetise_juryPE()
@ -317,12 +322,10 @@ class JuryPE(object):
etudiants = [] etudiants = []
for sem in semsListe: # pour chacun des semestres de la liste 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"]) nt = self.get_cache_notes_d_un_semestre(sem["formsemestre_id"])
# sco_cache.NotesTableCache.get( sem['formsemestre_id'])
etudiantsDuSemestre = ( etudiantsDuSemestre = (
nt.get_etudids() nt.get_etudids()
) # nt.identdict.keys() # identification des etudiants du semestre ) # identification des etudiants du semestre
if pe_tools.PE_DEBUG: if pe_tools.PE_DEBUG:
pe_tools.pe_print( pe_tools.pe_print(
@ -486,14 +489,14 @@ class JuryPE(object):
lastdate = max(sesdates) # date de fin de l'inscription la plus récente 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) # if PETable.AFFICHAGE_DEBUG_PE == True : pe_tools.pe_print(" derniere inscription = ", lastDateSem)
semestresDeScoDoc = sco_formsemestre.do_formsemestre_list()
if sonDernierSidValide is None: if sonDernierSidValide is None:
# si l'étudiant n'a validé aucun semestre, les prend tous ? (à vérifier) # si l'étudiant n'a validé aucun semestre, les prend tous ? (à vérifier)
semestresSuperieurs = semestresDeScoDoc semestresSuperieurs = self.semestresDeScoDoc
else: else:
semestresSuperieurs = [ semestresSuperieurs = [
sem sem
for sem in semestresDeScoDoc for sem in self.semestresDeScoDoc
if sem["semestre_id"] > sonDernierSidValide if sem["semestre_id"] > sonDernierSidValide
] # Semestre de rang plus élevé que son dernier sem valide ] # Semestre de rang plus élevé que son dernier sem valide
datesDesSemestresSuperieurs = [ 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""" """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)
# ------------------------------------------------------------------------------------------------------------------ # ------------------------------------------------------------------------------------------------------------------

View File

@ -37,9 +37,13 @@ Created on Fri Sep 9 09:15:05 2016
""" """
from app import log 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.models.ues import UniteEns
from app.scodoc import sco_codes_parcours from app.scodoc import sco_codes_parcours
from app.scodoc import sco_cache
from app.scodoc import sco_tag_module from app.scodoc import sco_tag_module
from app.pe import pe_tagtable 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: le tableau de notes du semestre considéré
- nt.inscrlist: étudiants inscrits à ce semestre, par ordre alphabétique (avec demissions) - nt.inscrlist: étudiants inscrits à ce semestre, par ordre alphabétique (avec demissions)
- nt.identdict: { etudid : ident } - nt.identdict: { etudid : ident }
- nt._modimpls : liste des moduleimpl { ... 'module_id', ...} - liste des moduleimpl { ... 'module_id', ...}
Attributs supplémentaires : Attributs supplémentaires :
- inscrlist/identdict: étudiants inscrits hors démissionnaires ou défaillants - inscrlist/identdict: étudiants inscrits hors démissionnaires ou défaillants
@ -97,7 +101,11 @@ class SemestreTag(pe_tagtable.TableTag):
self.nt = notetable self.nt = notetable
# Les attributs hérités : la liste des étudiants # 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 = { self.identdict = {
etudid: ident etudid: ident
for (etudid, ident) in self.nt.identdict.items() 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 # Les modules pris en compte dans le calcul des moyennes par tag => ceux des UE standards
self.modimpls = [ self.modimpls = [
modimpl modimpl
for modimpl in self.nt._modimpls for modimpl in self.nt.formsemestre.modimpls_sorted
if modimpl["ue"]["type"] == sco_codes_parcours.UE_STANDARD if modimpl.module.ue.type == sco_codes_parcours.UE_STANDARD
] # la liste des modules (objet modimpl) ] # 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( 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 = {} tagdict = {}
for modimpl in self.modimpls: for modimpl in self.modimpls:
modimpl_id = modimpl["moduleimpl_id"] modimpl_id = modimpl.id
# liste des tags pour le modimpl concerné: # 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 ( for (
tag tag
@ -172,17 +183,13 @@ class SemestreTag(pe_tagtable.TableTag):
# Ajout du modimpl au tagname considéré # Ajout du modimpl au tagname considéré
tagdict[tagname][modimpl_id] = { tagdict[tagname][modimpl_id] = {
"module_id": modimpl["module_id"], # les données sur le module "module_id": modimpl.module.id, # les données sur le module
"coeff": modimpl["module"][ "coeff": modimpl.module.coefficient, # le coeff du module dans le semestre
"coefficient"
], # le coeff du module dans le semestre
"ponderation": ponderation, # la pondération demandée pour le tag sur le module "ponderation": ponderation, # la pondération demandée pour le tag sur le module
"module_code": modimpl["module"][ "module_code": modimpl.module.code, # le code qui doit se retrouver à l'identique dans des ue capitalisee
"code" "ue_id": modimpl.module.ue.id, # les données sur l'ue
], # le code qui doit se retrouver à l'identique dans des ue capitalisee "ue_code": modimpl.module.ue.ue_code,
"ue_id": modimpl["ue"]["ue_id"], # les données sur l'ue "ue_acronyme": modimpl.module.ue.acronyme,
"ue_code": modimpl["ue"]["ue_code"],
"ue_acronyme": modimpl["ue"]["acronyme"],
} }
return tagdict return tagdict
@ -218,7 +225,9 @@ class SemestreTag(pe_tagtable.TableTag):
def get_moyennes_DUT(self): def get_moyennes_DUT(self):
"""Lit les moyennes DUT du semestre pour tous les étudiants """Lit les moyennes DUT du semestre pour tous les étudiants
et les renvoie au même format que comp_MoyennesTag""" 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): def get_noteEtCoeff_modimpl(self, modimpl_id, etudid, profondeur=2):
@ -230,7 +239,7 @@ class SemestreTag(pe_tagtable.TableTag):
""" """
(note, coeff_norm) = (None, None) (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: if modimpl == None or profondeur < 0:
return (None, None) return (None, None)
@ -238,14 +247,14 @@ class SemestreTag(pe_tagtable.TableTag):
ue_capitalisees = self.get_ue_capitalisees( ue_capitalisees = self.get_ue_capitalisees(
etudid etudid
) # les ue capitalisées des étudiants ) # les ue capitalisées des étudiants
ue_capitalisees_id = [ ue_capitalisees_id = {
ue.id for ue in ue_capitalisees ue_cap["ue_id"] for ue_cap in ue_capitalisees
] # les id des ue capitalisées } # les id des ue capitalisées
# Si le module ne fait pas partie 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 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_norm = (
coeff / self.somme_coeffs if self.somme_coeffs != 0 else 0 coeff / self.somme_coeffs if self.somme_coeffs != 0 else 0
) # le coeff normalisé ) # le coeff normalisé
@ -256,29 +265,30 @@ class SemestreTag(pe_tagtable.TableTag):
self.nt, etudid, modimpl_id self.nt, etudid, modimpl_id
) # la moyenne actuelle ) # la moyenne actuelle
# A quel semestre correspond l'ue capitalisée et quelles sont ses notes ? # 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 = [ fids_prec = [
ue["formsemestre_id"] ue_cap["formsemestre_id"]
for ue in ue_capitalisees for ue_cap in ue_capitalisees
if ue.ue_code == modimpl["ue"]["ue_code"] if ue_cap["ue_code"] == modimpl.module.ue.ue_code
] # and ue['semestre_id'] == semestre_id] ] # and ue['semestre_id'] == semestre_id]
if len(fids_prec) > 0: if len(fids_prec) > 0:
# => le formsemestre_id du semestre dont vient la capitalisation # => le formsemestre_id du semestre dont vient la capitalisation
fid_prec = fids_prec[0] fid_prec = fids_prec[0]
# Lecture des notes de ce semestre # Lecture des notes de ce semestre
nt_prec = sco_cache.NotesTableCache.get( # le tableau de note du semestre considéré:
fid_prec formsemestre_prec = FormSemestre.query.get_or_404(fid_prec)
) # le tableau de note du semestre considéré 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) # 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 = [ modimpl_prec = [
module modi
for module in nt_prec._modimpls for modi in nt_prec.formsemestre.modimpls_sorted
if module["module"]["code"] == modimpl["module"]["code"] if modi.module.code == modimpl.module.code
] ]
if len(modimpl_prec) > 0: # si une correspondance est trouvée 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) moy_ue_capitalisee = get_moy_ue_from_nt(nt_prec, etudid, modprec_id)
if ( if (
moy_ue_capitalisee is None moy_ue_capitalisee is None
@ -286,7 +296,7 @@ class SemestreTag(pe_tagtable.TableTag):
note = self.nt.get_etud_mod_moy( note = self.nt.get_etud_mod_moy(
modimpl_id, etudid modimpl_id, etudid
) # lecture de la note ) # lecture de la note
coeff = modimpl["module"]["coefficient"] # le coeff coeff = modimpl.module.coefficient # le coeff
coeff_norm = ( coeff_norm = (
coeff / self.somme_coeffs if self.somme_coeffs != 0 else 0 coeff / self.somme_coeffs if self.somme_coeffs != 0 else 0
) # le coeff normalisé ) # le coeff normalisé
@ -300,13 +310,11 @@ class SemestreTag(pe_tagtable.TableTag):
return (note, coeff_norm) return (note, coeff_norm)
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
def get_ue_capitalisees(self, etudid) -> list[UniteEns]: def get_ue_capitalisees(self, etudid) -> list[dict]:
"""Renvoie la liste des ue_id effectivement capitalisées par un étudiant""" """Renvoie la liste des capitalisation effectivement capitalisées par un étudiant"""
ue_ids = [ if etudid in self.nt.validations.ue_capitalisees.index:
ue_id return self.nt.validations.ue_capitalisees.loc[[etudid]].to_dict("records")
for ue_id in self.nt.validations.ue_capitalisees.loc[[etudid]]["ue_id"] return []
]
return [UniteEns.query.get(ue_id) for ue_id in ue_ids]
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
def get_listesNotesEtCoeffsTagEtudiant(self, tag, etudid): def get_listesNotesEtCoeffsTagEtudiant(self, tag, etudid):
@ -472,37 +480,27 @@ def comp_coeff_pond(coeffs, ponderations):
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
def get_moduleimpl(nt, modimpl_id): def get_moduleimpl(modimpl_id) -> dict:
"""Renvoie l'objet modimpl dont l'id est modimpl_id fourni dans la note table nt, """Renvoie l'objet modimpl dont l'id est modimpl_id"""
en utilisant l'attribut nt._modimpls""" modimpl = ModuleImpl.query.get(modimpl_id)
modimplids = [ if modimpl:
modimpl["moduleimpl_id"] for modimpl in nt._modimpls return modimpl
] # la liste de id des modules (modimpl_id)
if modimpl_id not in modimplids:
if SemestreTag.DEBUG: if SemestreTag.DEBUG:
log( log(
"SemestreTag.get_moduleimpl( %s ) : le modimpl recherche n'existe pas" "SemestreTag.get_moduleimpl( %s ) : le modimpl recherche n'existe pas"
% (modimpl_id) % (modimpl_id)
) )
return None return None
return nt._modimpls[modimplids.index(modimpl_id)]
# ********************************************** # **********************************************
def get_moy_ue_from_nt(nt, etudid, modimpl_id): 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 """Renvoie la moyenne de l'UE d'un etudid dans laquelle se trouve
en partant du note table nt""" le module de modimpl_id
mod = get_moduleimpl(nt, modimpl_id) # le module """
indice = 0 # ré-écrit
while indice < len(nt._ues): modimpl = get_moduleimpl(modimpl_id) # le module
if ( ue_status = nt.get_etud_ue_status(etudid, modimpl.module.ue.id)
nt._ues[indice]["ue_id"] == mod["module"]["ue_id"] if ue_status is None:
): # si les ue_id correspond return None
data = [ return ue_status["moy"]
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é

View File

@ -68,7 +68,7 @@ class TableTag(object):
self.taglist = [] self.taglist = []
self.resultats = {} self.resultats = {}
self.etud_moy_gen_ranks = {} self.rangs = {}
self.statistiques = {} self.statistiques = {}
# ***************************************************************************************************************** # *****************************************************************************************************************

View File

@ -254,13 +254,13 @@ class TF(object):
continue # allowed empty field, skip continue # allowed empty field, skip
# type # type
typ = descr.get("type", "string") typ = descr.get("type", "string")
if val != "" and val != None: if val != "" and val is not None:
# check only non-null values # check only non-null values
if typ[:3] == "int": if typ[:3] == "int":
try: try:
val = int(val) val = int(val)
self.values[field] = val self.values[field] = val
except: except ValueError:
msg.append( msg.append(
"La valeur du champ '%s' doit être un nombre entier" % field "La valeur du champ '%s' doit être un nombre entier" % field
) )
@ -270,30 +270,24 @@ class TF(object):
try: try:
val = float(val.replace(",", ".")) # allow , val = float(val.replace(",", ".")) # allow ,
self.values[field] = val self.values[field] = val
except: except ValueError:
msg.append( msg.append(
"La valeur du champ '%s' doit être un nombre" % field "La valeur du champ '%s' doit être un nombre" % field
) )
ok = 0 ok = 0
if typ[:3] == "int" or typ == "float" or typ == "real":
if ( if (
val != "" ok
and (typ[:3] == "int" or typ == "float" or typ == "real")
and val != ""
and val != None 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( msg.append(
"La valeur (%d) du champ '%s' est trop petite (min=%s)" "La valeur (%d) du champ '%s' est trop petite (min=%s)"
% (val, field, descr["min_value"]) % (val, field, descr["min_value"])
) )
ok = 0 ok = 0
if "max_value" in descr and self.values[field] > descr["max_value"]:
if (
val != ""
and val != None
and "max_value" in descr
and val > descr["max_value"]
):
msg.append( msg.append(
"La valeur (%s) du champ '%s' est trop grande (max=%s)" "La valeur (%s) du champ '%s' est trop grande (max=%s)"
% (val, field, descr["max_value"]) % (val, field, descr["max_value"])

View File

@ -171,7 +171,7 @@ class NotesTable:
def __init__(self, formsemestre_id): def __init__(self, formsemestre_id):
# log(f"NotesTable( formsemestre_id={formsemestre_id} )") # log(f"NotesTable( formsemestre_id={formsemestre_id} )")
# raise NotImplementedError() # XXX raise NotImplementedError() # XXX
if not formsemestre_id: if not formsemestre_id:
raise ValueError("invalid formsemestre_id (%s)" % formsemestre_id) raise ValueError("invalid formsemestre_id (%s)" % formsemestre_id)
self.formsemestre_id = formsemestre_id self.formsemestre_id = formsemestre_id
@ -954,9 +954,12 @@ class NotesTable:
Return: True|False, message explicatif Return: True|False, message explicatif
""" """
return self.parcours.check_barre_ues( ue_status_list = []
[self.get_etud_ue_status(etudid, ue["ue_id"]) for ue in self._ues] 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): def get_table_moyennes_triees(self):
return self.T return self.T
@ -1160,9 +1163,11 @@ class NotesTable:
nt_cap = sco_cache.NotesTableCache.get( nt_cap = sco_cache.NotesTableCache.get(
ue_cap["formsemestre_id"] ue_cap["formsemestre_id"]
) # > UE capitalisees par un etud ) # > UE capitalisees par un etud
moy_ue_cap = nt_cap.get_etud_ue_status(etudid, ue_cap["ue_id"])[ ue_cap_status = nt_cap.get_etud_ue_status(etudid, ue_cap["ue_id"])
"moy" if ue_cap_status:
] moy_ue_cap = ue_cap_status["moy"]
else:
moy_ue_cap = ""
ue_cap["moy_ue"] = moy_ue_cap ue_cap["moy_ue"] = moy_ue_cap
if ( if (
isinstance(moy_ue_cap, float) isinstance(moy_ue_cap, float)

View File

@ -479,7 +479,7 @@ def _get_abs_description(a, cursor=None):
) )
if Mlist: if Mlist:
M = Mlist[0] M = Mlist[0]
module += "%s " % M["module"]["code"] module += "%s " % (M["module"]["code"] or "(module sans code)")
if desc: if desc:
return "(%s) %s" % (desc, module) return "(%s) %s" % (desc, module)

View File

@ -33,7 +33,9 @@ import datetime
from flask import url_for, g, request, abort from flask import url_for, g, request, abort
from app import log 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 import app.scodoc.sco_utils as scu
from app.scodoc import notesdb as ndb from app.scodoc import notesdb as ndb
from app.scodoc.scolog import logdb from app.scodoc.scolog import logdb
@ -118,13 +120,16 @@ def doSignaleAbsence(
if moduleimpl_id and moduleimpl_id != "NULL": if moduleimpl_id and moduleimpl_id != "NULL":
mod = sco_moduleimpl.moduleimpl_list(moduleimpl_id=moduleimpl_id)[0] mod = sco_moduleimpl.moduleimpl_list(moduleimpl_id=moduleimpl_id)[0]
formsemestre_id = mod["formsemestre_id"] 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() ues = nt.get_ues_stat_dict()
for ue in ues: for ue in ues:
modimpls = nt.get_modimpls_dict(ue_id=ue["ue_id"]) modimpls = nt.get_modimpls_dict(ue_id=ue["ue_id"])
for modimpl in modimpls: for modimpl in modimpls:
if modimpl["moduleimpl_id"] == moduleimpl_id: 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 = [ H = [
html_sco_header.sco_header( html_sco_header.sco_header(
page_title=f"Signalement d'une absence pour {etud.nomprenom}", page_title=f"Signalement d'une absence pour {etud.nomprenom}",
@ -179,11 +184,12 @@ def SignaleAbsenceEtud(): # etudid implied
menu_module = "" menu_module = ""
else: else:
formsemestre_id = etud["cursem"]["formsemestre_id"] 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( require_module = sco_preferences.get_preference(
"abs_require_module", formsemestre_id "abs_require_module", formsemestre_id
) )
nt = sco_cache.NotesTableCache.get(formsemestre_id)
ues = nt.get_ues_stat_dict()
if require_module: if require_module:
menu_module = """ menu_module = """
<script type="text/javascript"> <script type="text/javascript">
@ -214,7 +220,7 @@ def SignaleAbsenceEtud(): # etudid implied
"""<option value="%(modimpl_id)s">%(modname)s</option>\n""" """<option value="%(modimpl_id)s">%(modname)s</option>\n"""
% { % {
"modimpl_id": modimpl["moduleimpl_id"], "modimpl_id": modimpl["moduleimpl_id"],
"modname": modimpl["module"]["code"], "modname": modimpl["module"]["code"] or "",
} }
) )
menu_module += """</select></p>""" menu_module += """</select></p>"""
@ -960,10 +966,10 @@ def _tables_abs_etud(
ex.append( ex.append(
f"""<a href="{url_for('notes.moduleimpl_status', f"""<a href="{url_for('notes.moduleimpl_status',
scodoc_dept=g.scodoc_dept, moduleimpl_id=mod["moduleimpl_id"])} scodoc_dept=g.scodoc_dept, moduleimpl_id=mod["moduleimpl_id"])}
">{mod["module"]["code"]}</a>""" ">{mod["module"]["code"] or "(module sans code)"}</a>"""
) )
else: else:
ex.append(mod["module"]["code"]) ex.append(mod["module"]["code"] or "(module sans code)")
if ex: if ex:
return ", ".join(ex) return ", ".join(ex)
return "" return ""
@ -978,10 +984,10 @@ def _tables_abs_etud(
ex.append( ex.append(
f"""<a href="{url_for('notes.moduleimpl_status', f"""<a href="{url_for('notes.moduleimpl_status',
scodoc_dept=g.scodoc_dept, moduleimpl_id=mod["moduleimpl_id"])} scodoc_dept=g.scodoc_dept, moduleimpl_id=mod["moduleimpl_id"])}
">{mod["module"]["code"]}</a>""" ">{mod["module"]["code"] or '(module sans code)'}</a>"""
) )
else: else:
ex.append(mod["module"]["code"]) ex.append(mod["module"]["code"] or "(module sans code)")
if ex: if ex:
return ", ".join(ex) return ", ".join(ex)
return "" return ""

View File

@ -95,9 +95,12 @@ from flask import send_file
# Pour la détection auto de l'encodage des fichiers Apogée: # Pour la détection auto de l'encodage des fichiers Apogée:
from chardet import detect as chardet_detect 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 from app.models.config import ScoDocSiteConfig
import app.scodoc.sco_utils as scu import app.scodoc.sco_utils as scu
from app import log
from app.scodoc.sco_exceptions import ScoValueError, ScoFormatError from app.scodoc.sco_exceptions import ScoValueError, ScoFormatError
from app.scodoc.gen_tables import GenTable from app.scodoc.gen_tables import GenTable
from app.scodoc.sco_vdi import ApoEtapeVDI 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é dict: with N, B, J, R keys, ou None si elt non trouvé
""" """
etudid = self.etud["etudid"] 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: if etudid not in nt.identdict:
return None # etudiant non inscrit dans ce semestre 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"]) ue_status = nt.get_etud_ue_status(etudid, ue["ue_id"])
code_decision_ue = decisions_ue[ue["ue_id"]]["code"] code_decision_ue = decisions_ue[ue["ue_id"]]["code"]
return dict( return dict(
N=_apo_fmt_note(ue_status["moy"]), N=_apo_fmt_note(ue_status["moy"] if ue_status else ""),
B=20, B=20,
J="", J="",
R=ScoDocSiteConfig.get_code_apo(code_decision_ue), R=ScoDocSiteConfig.get_code_apo(code_decision_ue),
@ -476,7 +481,8 @@ class ApoEtud(dict):
# l'étudiant n'a pas de semestre courant ?! # l'étudiant n'a pas de semestre courant ?!
log("comp_elt_annuel: etudid %s has no cur_sem" % etudid) log("comp_elt_annuel: etudid %s has no cur_sem" % etudid)
return VOID_APO_RES 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) cur_decision = cur_nt.get_etud_decision_sem(etudid)
if not cur_decision: if not cur_decision:
# pas de decision => pas de résultat annuel # pas de decision => pas de résultat annuel
@ -493,7 +499,10 @@ class ApoEtud(dict):
decision_apo = ScoDocSiteConfig.get_code_apo(cur_decision["code"]) 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) autre_decision = autre_nt.get_etud_decision_sem(etudid)
if not autre_decision: if not autre_decision:
# pas de decision dans l'autre => pas de résultat annuel # pas de decision dans l'autre => pas de résultat annuel
@ -554,7 +563,8 @@ class ApoEtud(dict):
# prend le plus recent avec decision # prend le plus recent avec decision
cur_sem = None cur_sem = None
for sem in cur_sems: 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"]) decision = nt.get_etud_decision_sem(self.etud["etudid"])
if decision: if decision:
cur_sem = sem cur_sem = sem
@ -614,7 +624,8 @@ class ApoEtud(dict):
else: else:
autre_sem = None autre_sem = None
for sem in autres_sems: 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"]) decision = nt.get_etud_decision_sem(self.etud["etudid"])
if decision: if decision:
autre_sem = sem autre_sem = sem
@ -947,7 +958,8 @@ class ApoData(object):
s.add(code) s.add(code)
continue continue
# associé à une UE: # 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(): for ue in nt.get_ues_stat_dict():
if ue["code_apogee"] and code in ue["code_apogee"].split(","): if ue["code_apogee"] and code in ue["code_apogee"].split(","):
s.add(code) s.add(code)

View File

@ -132,19 +132,29 @@ BACS_S = {t[0]: t[2:] for t in _BACS}
class Baccalaureat: class Baccalaureat:
def __init__(self, bac, specialite=""): def __init__(self, bac, specialite=""):
self.bac = bac self.bac = bac or ""
self.specialite = specialite self.specialite = specialite or ""
self._abbrev, self._type = BACS_SSP.get((bac, specialite), (None, None)) self._abbrev, self._type = BACS_SSP.get(
(self.bac, self.specialite), (None, None)
)
# Parfois, la specialite commence par la serie: essaye # Parfois, la specialite commence par la serie: essaye
if self._type is None and specialite and specialite.startswith(bac): if (
specialite = specialite[len(bac) :].strip(" -") self._type is None
self._abbrev, self._type = BACS_SSP.get((bac, specialite), (None, 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 # Cherche la forme serie specialite
if self._type is None and 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 # Cherche avec juste le bac, sans specialite
if self._type is None: 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): def abbrev(self):
"abbreviation for this bac" "abbreviation for this bac"

View File

@ -28,30 +28,21 @@
"""Génération des bulletins de notes """Génération des bulletins de notes
""" """
from app.models import formsemestre
import time
import pprint
import email import email
from email.mime.multipart import MIMEMultipart import pprint
from email.mime.text import MIMEText import time
from email.mime.base import MIMEBase
from email.header import Header
from reportlab.lib.colors import Color
import urllib
from flask import g, request from flask import g, request
from flask import url_for from flask import url_for
from flask_login import current_user from flask_login import current_user
from flask_mail import Message from flask_mail import Message
from app.models.moduleimpls import ModuleImplInscription
import app.scodoc.sco_utils as scu from app import email
from app.scodoc.sco_utils import ModuleType
import app.scodoc.notesdb as ndb
from app import log from app import log
from app.but import bulletin_but
from app.comp import res_sem from app.comp import res_sem
from app.comp.res_common import NotesTableCompat from app.comp.res_common import NotesTableCompat
from app.models import FormSemestre from app.models import FormSemestre, Identite, ModuleImplInscription
from app.scodoc.sco_permissions import Permission from app.scodoc.sco_permissions import Permission
from app.scodoc.sco_exceptions import AccessDenied, ScoValueError from app.scodoc.sco_exceptions import AccessDenied, ScoValueError
from app.scodoc import html_sco_header 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_abs_views
from app.scodoc import sco_bulletins_generator from app.scodoc import sco_bulletins_generator
from app.scodoc import sco_bulletins_json 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_bulletins_xml
from app.scodoc import sco_codes_parcours from app.scodoc import sco_codes_parcours
from app.scodoc import sco_cache
from app.scodoc import sco_etud from app.scodoc import sco_etud
from app.scodoc import sco_evaluation_db from app.scodoc import sco_evaluation_db
from app.scodoc import sco_formations 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_preferences
from app.scodoc import sco_pvjury from app.scodoc import sco_pvjury
from app.scodoc import sco_users 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 # ----- CLASSES DE BULLETINS DE NOTES
from app.scodoc import sco_bulletins_standard 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 !") raise ValueError("invalid version code !")
prefs = sco_preferences.SemPreferences(formsemestre_id) prefs = sco_preferences.SemPreferences(formsemestre_id)
# nt = sco_cache.NotesTableCache.get(formsemestre_id) # > toutes notes
formsemestre = FormSemestre.query.get_or_404(formsemestre_id) formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre) nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
if not nt.get_etud_etat(etudid): 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"], show_mention=prefs["bul_show_mention"],
) )
if dpv:
I["decision_sem"] = dpv["decisions"][0]["decision_sem"]
else:
I["decision_sem"] = ""
I.update(infos) I.update(infos)
I["etud_etat_html"] = _get_etud_etat_html( I["etud_etat_html"] = _get_etud_etat_html(
formsemestre.etuds_inscriptions[etudid].etat formsemestre.etuds_inscriptions[etudid].etat
) )
I["etud_etat"] = nt.get_etud_etat(etudid) I["etud_etat"] = nt.get_etud_etat(etudid)
I["filigranne"] = "" I["filigranne"] = sco_bulletins_pdf.get_filigranne(I["etud_etat"], prefs)
I["demission"] = "" I["demission"] = ""
if I["etud_etat"] == "D": if I["etud_etat"] == scu.DEMISSION:
I["demission"] = "(Démission)" I["demission"] = "(Démission)"
I["filigranne"] = "Démission"
elif I["etud_etat"] == sco_codes_parcours.DEF: elif I["etud_etat"] == sco_codes_parcours.DEF:
I["demission"] = "(Défaillant)" 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 # --- Appreciations
cnx = ndb.GetDBConnexion() cnx = ndb.GetDBConnexion()
@ -363,10 +345,7 @@ def formsemestre_bulletinetud_dict(formsemestre_id, etudid, version="long"):
>{u["ue_descr_txt"]} pouet</a> >{u["ue_descr_txt"]} pouet</a>
""" """
if ue_status["moy"] != "NA" and ue_status["formsemestre_id"]: if ue_status["moy"] != "NA" and ue_status["formsemestre_id"]:
# detail des modules de l'UE capitalisee # détail des modules de l'UE capitalisée
# nt_cap = sco_cache.NotesTableCache.get(
# ue_status["formsemestre_id"]
# ) # > toutes notes
formsemestre_cap = FormSemestre.query.get_or_404( formsemestre_cap = FormSemestre.query.get_or_404(
ue_status["formsemestre_id"] 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): if sco_preferences.get_preference("bul_show_codemodules", formsemestre_id):
mod["code"] = modimpl["module"]["code"] mod["code"] = modimpl["module"]["code"]
mod["code_html"] = link_mod + mod["code"] + "</a>" mod["code_html"] = link_mod + (mod["code"] or "") + "</a>"
else: else:
mod["code"] = mod["code_html"] = "" mod["code"] = mod["code_html"] = ""
mod["name"] = ( mod["name"] = (
@ -536,7 +515,7 @@ def _ue_mod_bulletin(etudid, formsemestre_id, ue_id, modimpls, nt, version):
% (modimpl["moduleimpl_id"], mod_descr) % (modimpl["moduleimpl_id"], mod_descr)
) )
if sco_preferences.get_preference("bul_show_codemodules", formsemestre_id): 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>" mod["code_html"] = link_mod + mod["code_txt"] + "</a>"
else: else:
mod["code_txt"] = "" 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["note_txt"] = e["note_html"] = ""
e["coef_txt"] = scu.fmt_coef(e["coefficient"]) e["coef_txt"] = scu.fmt_coef(e["coefficient"])
# Classement # 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"]] rg = nt.mod_rangs[modimpl["moduleimpl_id"]]
if rg[0] is None: if rg[0] is None:
mod["mod_rang_txt"] = "" mod["mod_rang_txt"] = ""
@ -686,6 +670,7 @@ def etud_descr_situation_semestre(
descr_defaillance : "Défaillant" ou vide si non défaillant. descr_defaillance : "Défaillant" ou vide si non défaillant.
decision_jury : "Validé", "Ajourné", ... (code semestre) decision_jury : "Validé", "Ajourné", ... (code semestre)
descr_decision_jury : "Décision jury: Validé" (une phrase) 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. 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_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 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 # --- Situation et décisions jury
# demission/inscription ? # démission/inscription ?
events = sco_etud.scolar_events_list( events = sco_etud.scolar_events_list(
cnx, args={"etudid": etudid, "formsemestre_id": formsemestre_id} cnx, args={"etudid": etudid, "formsemestre_id": formsemestre_id}
) )
@ -762,11 +747,15 @@ def etud_descr_situation_semestre(
infos["situation"] += " " + infos["descr_defaillance"] infos["situation"] += " " + infos["descr_defaillance"]
dpv = sco_pvjury.dict_pvjury(formsemestre_id, etudids=[etudid]) 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: if not show_decisions:
return infos, dpv return infos, dpv
# Decisions de jury: # Décisions de jury:
pv = dpv["decisions"][0] pv = dpv["decisions"][0]
dec = "" dec = ""
if pv["decision_sem_descr"]: if pv["decision_sem_descr"]:
@ -818,11 +807,15 @@ def formsemestre_bulletinetud(
except: except:
sco_etud.log_unknown_etud() sco_etud.log_unknown_etud()
raise ScoValueError("étudiant inconnu") 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( bulletin = do_formsemestre_bulletinetud(
formsemestre_id, formsemestre,
etudid, etudid,
format=format, format=format,
version=version, version=version,
@ -834,7 +827,6 @@ def formsemestre_bulletinetud(
filename = scu.bul_filename(sem, etud, format) filename = scu.bul_filename(sem, etud, format)
return scu.send_file(bulletin, filename, mime=scu.get_mime_suffix(format)[0]) return scu.send_file(bulletin, filename, mime=scu.get_mime_suffix(format)[0])
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
H = [ H = [
_formsemestre_bulletinetud_header_html( _formsemestre_bulletinetud_header_html(
etud, etudid, sem, formsemestre_id, format, version etud, etudid, sem, formsemestre_id, format, version
@ -891,14 +883,14 @@ def can_send_bulletin_by_mail(formsemestre_id):
def do_formsemestre_bulletinetud( def do_formsemestre_bulletinetud(
formsemestre_id, formsemestre: FormSemestre,
etudid, etudid: int,
version="long", # short, long, selectedevals version="long", # short, long, selectedevals
format="html", format="html",
nohtml=False, nohtml=False,
xml_with_decisions=False, # force decisions dans XML xml_with_decisions=False, # force décisions dans XML
force_publishing=False, # force publication meme si semestre non publie sur "portail" force_publishing=False, # force publication meme si semestre non publié sur "portail"
prefer_mail_perso=False, # mails envoyes sur adresse perso si non vide prefer_mail_perso=False, # mails envoyés sur adresse perso si non vide
): ):
"""Génère le bulletin au format demandé. """Génère le bulletin au format demandé.
Retourne: (bul, filigranne) Retourne: (bul, filigranne)
@ -907,7 +899,7 @@ def do_formsemestre_bulletinetud(
""" """
if format == "xml": if format == "xml":
bul = sco_bulletins_xml.make_xml_formsemestre_bulletinetud( bul = sco_bulletins_xml.make_xml_formsemestre_bulletinetud(
formsemestre_id, formsemestre.id,
etudid, etudid,
xml_with_decisions=xml_with_decisions, xml_with_decisions=xml_with_decisions,
force_publishing=force_publishing, force_publishing=force_publishing,
@ -918,7 +910,7 @@ def do_formsemestre_bulletinetud(
elif format == "json": elif format == "json":
bul = sco_bulletins_json.make_json_formsemestre_bulletinetud( bul = sco_bulletins_json.make_json_formsemestre_bulletinetud(
formsemestre_id, formsemestre.id,
etudid, etudid,
xml_with_decisions=xml_with_decisions, xml_with_decisions=xml_with_decisions,
force_publishing=force_publishing, force_publishing=force_publishing,
@ -926,7 +918,12 @@ def do_formsemestre_bulletinetud(
) )
return bul, "" 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"] etud = I["etud"]
if format == "html": if format == "html":
@ -953,7 +950,7 @@ def do_formsemestre_bulletinetud(
elif format == "pdfmail": elif format == "pdfmail":
# format pdfmail: envoie le pdf par mail a l'etud, et affiche le html # format pdfmail: envoie le pdf par mail a l'etud, et affiche le html
# check permission # 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 !") raise AccessDenied("Vous n'avez pas le droit d'effectuer cette opération !")
if nohtml: if nohtml:
@ -982,7 +979,7 @@ def do_formsemestre_bulletinetud(
) + htm ) + htm
return h, I["filigranne"] 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>' % ( emaillink = '<a class="stdlink" href="mailto:%s">%s</a>' % (
recipient_addr, recipient_addr,
recipient_addr, recipient_addr,

View File

@ -99,7 +99,7 @@ def bulletin_get_class_name_displayed(formsemestre_id):
return "invalide ! (voir paramètres)" return "invalide ! (voir paramètres)"
class BulletinGenerator(object): class BulletinGenerator:
"Virtual superclass for PDF bulletin generators" "" "Virtual superclass for PDF bulletin generators" ""
# Here some helper methods # Here some helper methods
# see sco_bulletins_standard.BulletinGeneratorStandard subclass for real methods # see sco_bulletins_standard.BulletinGeneratorStandard subclass for real methods

View File

@ -209,7 +209,7 @@ def formsemestre_bulletinetud_published_dict(
acronyme=scu.quote_xml_attr(ue["acronyme"]), acronyme=scu.quote_xml_attr(ue["acronyme"]),
titre=scu.quote_xml_attr(ue["titre"]), titre=scu.quote_xml_attr(ue["titre"]),
note=dict( 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"]), min=scu.fmt_note(ue["min"]),
max=scu.fmt_note(ue["max"]), max=scu.fmt_note(ue["max"]),
moy=scu.fmt_note( moy=scu.fmt_note(
@ -254,7 +254,10 @@ def formsemestre_bulletinetud_published_dict(
m["note"][k] = scu.fmt_note(m["note"][k]) m["note"][k] = scu.fmt_note(m["note"][k])
u["module"].append(m) 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( m["rang"] = dict(
value=nt.mod_rangs[modimpl["moduleimpl_id"]][0][etudid] value=nt.mod_rangs[modimpl["moduleimpl_id"]][0][etudid]
) )

View File

@ -476,8 +476,8 @@ def _bulletin_pdf_table_legacy(I, version="long"):
else: else:
rang_minmax = mod["mod_rang_txt"] # vide si pas option rang rang_minmax = mod["mod_rang_txt"] # vide si pas option rang
t = [ t = [
mod["code"], mod["code"] or "",
mod["name"], mod["name"] or "",
rang_minmax, rang_minmax,
mod["mod_moy_txt"], mod["mod_moy_txt"],
mod["mod_coef_txt"], mod["mod_coef_txt"],

View File

@ -56,19 +56,22 @@ import time
import traceback import traceback
from pydoc import html from pydoc import html
from reportlab.platypus.doctemplate import PageTemplate, BaseDocTemplate from reportlab.platypus.doctemplate import BaseDocTemplate
from flask import g, request from flask import g, request
import app.scodoc.sco_utils as scu
from app import log, ScoValueError from app import log, ScoValueError
from app.models import FormSemestre
from app.scodoc import sco_cache 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_pdf
from app.scodoc import sco_preferences from app.scodoc import sco_preferences
from app.scodoc import sco_etud from app.scodoc import sco_etud
import sco_version
from app.scodoc.sco_logos import find_logo from app.scodoc.sco_logos import find_logo
import app.scodoc.sco_utils as scu
import sco_version
def pdfassemblebulletins( def pdfassemblebulletins(
@ -178,22 +181,21 @@ def get_formsemestre_bulletins_pdf(formsemestre_id, version="selectedevals"):
if cached: if cached:
return cached[1], cached[0] return cached[1], cached[0]
fragments = [] fragments = []
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
# Make each bulletin # Make each bulletin
nt = sco_cache.NotesTableCache.get(formsemestre_id) # > get_etudids, get_sexnom formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
bookmarks = {} bookmarks = {}
filigrannes = {} filigrannes = {}
i = 1 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( frag, filigranne = sco_bulletins.do_formsemestre_bulletinetud(
formsemestre_id, formsemestre,
etudid, etud.id,
format="pdfpart", format="pdfpart",
version=version, version=version,
) )
fragments += frag fragments += frag
filigrannes[i] = filigranne filigrannes[i] = filigranne
bookmarks[i] = scu.suppress_accents(nt.get_sexnom(etudid)) bookmarks[i] = etud.sex_nom(no_accents=True)
i = i + 1 i = i + 1
# #
infos = {"DeptName": sco_preferences.get_preference("DeptName", formsemestre_id)} infos = {"DeptName": sco_preferences.get_preference("DeptName", formsemestre_id)}
@ -206,7 +208,7 @@ def get_formsemestre_bulletins_pdf(formsemestre_id, version="selectedevals"):
pdfdoc = pdfassemblebulletins( pdfdoc = pdfassemblebulletins(
formsemestre_id, formsemestre_id,
fragments, fragments,
sem["titremois"], formsemestre.titre_mois(),
infos, infos,
bookmarks, bookmarks,
filigranne=filigrannes, filigranne=filigrannes,
@ -216,7 +218,7 @@ def get_formsemestre_bulletins_pdf(formsemestre_id, version="selectedevals"):
sco_pdf.PDFLOCK.release() sco_pdf.PDFLOCK.release()
# #
dt = time.strftime("%Y-%m-%d") 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("&", "") filename = scu.unescape_html(filename).replace(" ", "_").replace("&", "")
# fill cache # fill cache
sco_cache.SemBulletinsPDFCache.set( sco_cache.SemBulletinsPDFCache.set(
@ -235,8 +237,9 @@ def get_etud_bulletins_pdf(etudid, version="selectedevals"):
filigrannes = {} filigrannes = {}
i = 1 i = 1
for sem in etud["sems"]: for sem in etud["sems"]:
formsemestre = FormSemestre.query.get(sem["formsemestre_id"])
frag, filigranne = sco_bulletins.do_formsemestre_bulletinetud( frag, filigranne = sco_bulletins.do_formsemestre_bulletinetud(
sem["formsemestre_id"], formsemestre,
etudid, etudid,
format="pdfpart", format="pdfpart",
version=version, version=version,
@ -271,3 +274,16 @@ def get_etud_bulletins_pdf(etudid, version="selectedevals"):
) )
return pdfdoc, filename 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 ""

View File

@ -471,9 +471,10 @@ class BulletinGeneratorStandard(sco_bulletins_generator.BulletinGenerator):
ects_txt = str(int(ue["ects"])) ects_txt = str(int(ue["ects"]))
except: except:
ects_txt = "-" ects_txt = "-"
titre = f"{ue['acronyme'] or ''} {ue['titre'] or ''}"
t = { t = {
"titre": ue["acronyme"] + " " + ue["titre"], "titre": titre,
"_titre_html": minuslink + ue["acronyme"] + " " + ue["titre"], "_titre_html": minuslink + titre,
"_titre_colspan": 2, "_titre_colspan": 2,
"module": ue["titre"], "module": ue["titre"],
"rang": ue_descr, "rang": ue_descr,

View File

@ -217,7 +217,7 @@ def make_xml_formsemestre_bulletinetud(
) )
doc.append(x_ue) doc.append(x_ue)
if ue["type"] != sco_codes_parcours.UE_SPORT: 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: else:
v = nt.bonus[etudid] if nt.bonus is not None else 0.0 v = nt.bonus[etudid] if nt.bonus is not None else 0.0
x_ue.append( x_ue.append(
@ -252,7 +252,7 @@ def make_xml_formsemestre_bulletinetud(
x_mod = Element( x_mod = Element(
"module", "module",
id=str(modimpl["moduleimpl_id"]), id=str(modimpl["moduleimpl_id"]),
code=str(mod["code"]), code=str(mod["code"] or ""),
coefficient=str(mod["coefficient"]), coefficient=str(mod["coefficient"]),
numero=str(mod["numero"]), numero=str(mod["numero"]),
titre=scu.quote_xml_attr(mod["titre"]), titre=scu.quote_xml_attr(mod["titre"]),
@ -271,7 +271,10 @@ def make_xml_formsemestre_bulletinetud(
moy=scu.fmt_note(modstat["moy"]), 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( x_mod.append(
Element( Element(
"rang", "rang",

View File

@ -65,7 +65,7 @@ def formsemestre_table_estim_cost(
Mod = M["module"] Mod = M["module"]
T.append( T.append(
{ {
"code": Mod["code"], "code": Mod["code"] or "",
"titre": Mod["titre"], "titre": Mod["titre"],
"heures_cours": Mod["heures_cours"], "heures_cours": Mod["heures_cours"],
"heures_td": Mod["heures_td"] * n_group_td, "heures_td": Mod["heures_td"] * n_group_td,

View File

@ -31,15 +31,17 @@ Rapport (table) avec dernier semestre fréquenté et débouché de chaque étudi
import http import http
from flask import url_for, g, request 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.sco_utils as scu
import app.scodoc.notesdb as ndb import app.scodoc.notesdb as ndb
from app import log
from app.scodoc.sco_exceptions import AccessDenied from app.scodoc.sco_exceptions import AccessDenied
from app.scodoc.scolog import logdb from app.scodoc.scolog import logdb
from app.scodoc.gen_tables import GenTable from app.scodoc.gen_tables import GenTable
from app.scodoc import safehtml from app.scodoc import safehtml
from app.scodoc import html_sco_header 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_permissions_check
from app.scodoc import sco_preferences from app.scodoc import sco_preferences
from app.scodoc import sco_tag_module 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): def table_debouche_etudids(etudids, keep_numeric=True):
"""Rapport pour ces etudiants""" """Rapport pour ces étudiants"""
L = [] L = []
for etudid in etudids: for etudid in etudids:
etud = sco_etud.get_etud_info(filled=True, etudid=etudid)[0] 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)] es = [(s["date_fin_iso"], i) for i, s in enumerate(sems)]
imax = max(es)[1] imax = max(es)[1]
last_sem = sems[imax] 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 = { row = {
"etudid": etudid, "etudid": etudid,
"civilite": etud["civilite"], "civilite": etud["civilite"],

View File

@ -365,6 +365,7 @@ def ue_move(ue_id, after=0, redirect=1):
if len({o.numero for o in others}) != len(others): if len({o.numero for o in others}) != len(others):
# il y a des numeros identiques ! # il y a des numeros identiques !
scu.objects_renumber(db, others) scu.objects_renumber(db, others)
ue.formation.invalidate_cached_sems()
if len(others) > 1: if len(others) > 1:
idx = [u.id for u in others].index(ue.id) idx = [u.id for u in others].index(ue.id)
neigh = None # object to swap with neigh = None # object to swap with

View File

@ -88,13 +88,14 @@ def do_matiere_create(args):
r = _matiereEditor.create(cnx, args) r = _matiereEditor.create(cnx, args)
# news # news
F = sco_formations.formation_list(args={"formation_id": ue["formation_id"]})[0] formation = Formation.query.get(ue["formation_id"])
sco_news.add( sco_news.add(
typ=sco_news.NEWS_FORM, typ=sco_news.NEWS_FORM,
object=ue["formation_id"], object=ue["formation_id"],
text="Modification de la formation %(acronyme)s" % F, text="Modification de la formation {formation.acronyme}",
max_frequency=3, max_frequency=3,
) )
formation.invalidate_cached_sems()
return r return r
@ -195,13 +196,14 @@ def do_matiere_delete(oid):
_matiereEditor.delete(cnx, oid) _matiereEditor.delete(cnx, oid)
# news # news
F = sco_formations.formation_list(args={"formation_id": ue["formation_id"]})[0] formation = Formation.query.get(ue["formation_id"])
sco_news.add( sco_news.add(
typ=sco_news.NEWS_FORM, typ=sco_news.NEWS_FORM,
object=ue["formation_id"], object=ue["formation_id"],
text="Modification de la formation %(acronyme)s" % F, text="Modification de la formation {formation.acronyme}",
max_frequency=3, max_frequency=3,
) )
formation.invalidate_cached_sems()
def matiere_delete(matiere_id=None): def matiere_delete(matiere_id=None):

View File

@ -41,7 +41,6 @@ from app.models import FormSemestre, ModuleImpl
import app.scodoc.notesdb as ndb import app.scodoc.notesdb as ndb
import app.scodoc.sco_utils as scu import app.scodoc.sco_utils as scu
from app.scodoc.sco_utils import ModuleType
from app.scodoc.TrivialFormulator import TrivialFormulator from app.scodoc.TrivialFormulator import TrivialFormulator
from app.scodoc.sco_permissions import Permission from app.scodoc.sco_permissions import Permission
from app.scodoc.sco_exceptions import ( from app.scodoc.sco_exceptions import (
@ -105,13 +104,14 @@ def do_module_create(args) -> int:
r = _moduleEditor.create(cnx, args) r = _moduleEditor.create(cnx, args)
# news # news
F = sco_formations.formation_list(args={"formation_id": args["formation_id"]})[0] formation = Formation.query.get(args["formation_id"])
sco_news.add( sco_news.add(
typ=sco_news.NEWS_FORM, typ=sco_news.NEWS_FORM,
object=args["formation_id"], object=formation.id,
text="Modification de la formation %(acronyme)s" % F, text=f"Modification de la formation {formation.acronyme}",
max_frequency=3, max_frequency=3,
) )
formation.invalidate_cached_sems()
return r return r
@ -196,7 +196,6 @@ def module_create(
}, },
), ),
] ]
semestres_indices = list(range(1, parcours.NB_SEM + 1))
if is_apc: if is_apc:
module_types = scu.ModuleType # tous les types module_types = scu.ModuleType # tous les types
@ -396,13 +395,14 @@ def do_module_delete(oid):
_moduleEditor.delete(cnx, oid) _moduleEditor.delete(cnx, oid)
# news # news
F = sco_formations.formation_list(args={"formation_id": mod["formation_id"]})[0] formation = module.formation
sco_news.add( sco_news.add(
typ=sco_news.NEWS_FORM, typ=sco_news.NEWS_FORM,
object=mod["formation_id"], object=mod["formation_id"],
text="Modification de la formation %(acronyme)s" % F, text=f"Modification de la formation {formation.acronyme}",
max_frequency=3, max_frequency=3,
) )
formation.invalidate_cached_sems()
def module_delete(module_id=None): def module_delete(module_id=None):
@ -451,8 +451,6 @@ def module_delete(module_id=None):
def do_module_edit(vals: dict) -> None: def do_module_edit(vals: dict) -> None:
"edit a module" "edit a module"
from app.scodoc import sco_edit_formation
# check # check
mod = module_list({"module_id": vals["module_id"]})[0] mod = module_list({"module_id": vals["module_id"]})[0]
if module_is_locked(mod["module_id"]): 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... # ne propose pas SAE et Ressources, sauf si déjà de ce type...
module_types = ( module_types = (
set(scu.ModuleType) - {scu.ModuleType.RESSOURCE, scu.ModuleType.SAE} set(scu.ModuleType) - {scu.ModuleType.RESSOURCE, scu.ModuleType.SAE}
) | {a_module.module_type} ) | {a_module.module_type or scu.ModuleType.STANDARD}
descr = [ descr = [
( (
@ -845,7 +843,7 @@ def formation_add_malus_modules(formation_id, titre=None, redirect=True):
[ [
mod mod
for mod in module_list(args={"ue_id": ue["ue_id"]}) 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: if nb_mod_malus == 0:
@ -897,7 +895,7 @@ def ue_add_malus_module(ue_id, titre=None, code=None):
"matiere_id": matiere_id, "matiere_id": matiere_id,
"formation_id": ue["formation_id"], "formation_id": ue["formation_id"],
"semestre_id": semestre_id, "semestre_id": semestre_id,
"module_type": ModuleType.MALUS, "module_type": scu.ModuleType.MALUS,
}, },
) )

View File

@ -128,13 +128,14 @@ def do_ue_create(args):
formation = Formation.query.get(args["formation_id"]) formation = Formation.query.get(args["formation_id"])
formation.invalidate_module_coefs() formation.invalidate_module_coefs()
# news # news
F = sco_formations.formation_list(args={"formation_id": args["formation_id"]})[0] formation = Formation.query.get(args["formation_id"])
sco_news.add( sco_news.add(
typ=sco_news.NEWS_FORM, typ=sco_news.NEWS_FORM,
object=args["formation_id"], object=args["formation_id"],
text="Modification de la formation %(acronyme)s" % F, text="Modification de la formation {formation.acronyme}",
max_frequency=3, max_frequency=3,
) )
formation.invalidate_cached_sems()
return ue_id 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}" title = f"Modification de l'UE {ue.acronyme} {ue.titre}"
initvalues = ue_dict initvalues = ue_dict
submitlabel = "Modifier les valeurs" 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: else:
ue = None ue = None
title = "Création d'une UE" 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, "size": 12,
"title": "Code UE", "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, submitlabel=submitlabel,
) )
if tf[0] == 0: 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"> modules_div = f"""<div id="ue_list_modules">
<div><b>{ue.modules.count()} modules sont rattachés <div><b>{ue.modules.count()} modules sont rattachés
à cette UE</b> du semestre S{ue.semestre_idx}, à 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) semestre_ids = range(1, parcours.NB_SEM + 1)
# transition: on requete ici via l'ORM mais on utilise les fonctions ScoDoc7 # transition: on requete ici via l'ORM mais on utilise les fonctions ScoDoc7
# basées sur des dicts # 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( ues_externes_obj = UniteEns.query.filter_by(
formation_id=formation_id, is_external=True formation_id=formation_id, is_external=True
) )
@ -1415,15 +1418,15 @@ def ue_list_semestre_ids(ue: dict):
UE_PALETTE = [ UE_PALETTE = [
"#EFA00B", "#B80004", # rouge
"#99C24D", "#F97B3D", # Orange Crayola
"#EC9192", "#FEB40B", # Honey Yellow
"#0075C4", "#80CB3F", # Yellow Green
"#D65108", "#05162E", # Oxford Blue
"#DEC0F1", "#548687", # Steel Teal
"#B02E0C", "#444054", # Independence
"#151E3F", "#889696", # Spanish Gray
"#FB3640", "#0CA4A5", # Viridian Green
] ]
@ -1436,8 +1439,8 @@ def colorie_anciennes_ues(ues: list[UniteEns]) -> None:
last_sem_idx = 0 last_sem_idx = 0
for ue in ues: for ue in ues:
if ue.semestre_idx != last_sem_idx: if ue.semestre_idx != last_sem_idx:
last_sem_idx = ue.semestre_idx
index = 0 index = 0
last_sem_idx = ue.semestre_idx
if ue.color is None: if ue.color is None:
ue.color = UE_PALETTE[index % nb_colors] ue.color = UE_PALETTE[index % nb_colors]
index += 1 index += 1

View File

@ -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) 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') 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) 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)

View File

@ -38,6 +38,7 @@ from flask_mail import Message
from app import email from app import email
from app import log from app import log
from app.models import Admission
from app.models.etudiants import make_etud_args from app.models.etudiants import make_etud_args
import app.scodoc.sco_utils as scu import app.scodoc.sco_utils as scu
import app.scodoc.notesdb as ndb import app.scodoc.notesdb as ndb
@ -859,19 +860,24 @@ def list_scolog(etudid):
return cursor.dictfetchall() return cursor.dictfetchall()
def fill_etuds_info(etuds): def fill_etuds_info(etuds, add_admission=True):
"""etuds est une liste d'etudiants (mappings) """etuds est une liste d'etudiants (mappings)
Pour chaque etudiant, ajoute ou formatte les champs Pour chaque etudiant, ajoute ou formatte les champs
-> informations pour fiche etudiant ou listes diverses -> 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() cnx = ndb.GetDBConnexion()
# open('/tmp/t','w').write( str(etuds) )
for etud in etuds: for etud in etuds:
etudid = etud["etudid"] etudid = etud["etudid"]
etud["dept"] = g.scodoc_dept 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}) adrs = adresse_list(cnx, {"etudid": etudid})
if not adrs: if not adrs:
# certains "vieux" etudiants n'ont pas d'adresse # certains "vieux" etudiants n'ont pas d'adresse
@ -884,6 +890,50 @@ def fill_etuds_info(etuds):
etud.update(adr) etud.update(adr)
format_etud_ident(etud) 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 # Semestres dans lesquel il est inscrit
ins = sco_formsemestre_inscriptions.do_formsemestre_inscription_list( ins = sco_formsemestre_inscriptions.do_formsemestre_inscription_list(
{"etudid": etudid} {"etudid": etudid}
@ -908,13 +958,10 @@ def fill_etuds_info(etuds):
etud["inscriptionstr"] = "Inscrit en " + cursem["titremois"] etud["inscriptionstr"] = "Inscrit en " + cursem["titremois"]
etud["inscription_formsemestre_id"] = cursem["formsemestre_id"] etud["inscription_formsemestre_id"] = cursem["formsemestre_id"]
etud["etatincursem"] = curi["etat"] etud["etatincursem"] = curi["etat"]
etud["situation"] = descr_situation_etud(etudid, etud["ne"]) etud["situation"] = descr_situation_etud(etudid, ne)
# XXX est-ce utile ? sco_groups.etud_add_group_infos( etud, cursem)
else: else:
if etud["sems"]: if etud["sems"]:
if etud["sems"][0]["dateord"] > time.strftime( if etud["sems"][0]["dateord"] > time.strftime("%Y-%m-%d", time.localtime()):
"%Y-%m-%d", time.localtime()
):
etud["inscription"] = "futur" etud["inscription"] = "futur"
etud["situation"] = "futur élève" etud["situation"] = "futur élève"
else: else:
@ -925,47 +972,12 @@ def fill_etuds_info(etuds):
etud["situation"] = etud["inscription"] etud["situation"] = etud["inscription"]
etud["inscriptionstr"] = etud["inscription"] etud["inscriptionstr"] = etud["inscription"]
etud["inscription_formsemestre_id"] = None etud["inscription_formsemestre_id"] = None
# XXXetud['partitions'] = {} # ne va pas chercher les groupes des anciens semestres
etud["etatincursem"] = "?" etud["etatincursem"] = "?"
return etud
# 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"] = ""
def descr_situation_etud(etudid, ne=""): def descr_situation_etud(etudid: int, ne="") -> str:
"""chaine decrivant la situation actuelle de l'etudiant""" """chaîne décrivant la situation actuelle de l'étudiant"""
from app.scodoc import sco_formsemestre from app.scodoc import sco_formsemestre
cnx = ndb.GetDBConnexion() cnx = ndb.GetDBConnexion()
@ -982,7 +994,7 @@ def descr_situation_etud(etudid, ne=""):
) )
r = cursor.dictfetchone() r = cursor.dictfetchone()
if not r: if not r:
situation = "non inscrit" situation = "non inscrit" + ne
else: else:
sem = sco_formsemestre.get_formsemestre(r["formsemestre_id"]) sem = sco_formsemestre.get_formsemestre(r["formsemestre_id"])
if r["etat"] == "I": if r["etat"] == "I":

View File

@ -237,7 +237,11 @@ def formsemestre_check_absences_html(formsemestre_id):
if evals: if evals:
H.append( H.append(
'<div class="module_check_absences"><h2><a href="moduleimpl_status?moduleimpl_id=%s">%s: %s</a></h2>' '<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: for E in evals:
H.append( H.append(

View File

@ -36,6 +36,8 @@ from flask import url_for, g
from flask_login import current_user from flask_login import current_user
from app import log 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.sco_utils as scu
import app.scodoc.notesdb as ndb import app.scodoc.notesdb as ndb
from app.scodoc.sco_exceptions import AccessDenied, ScoValueError 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): def do_evaluation_list(args, sortkey=None):
"""List evaluations, sorted by numero (or most recent date first). """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 'apresmidi' : 1 (termine après 12:00) ou 0
'descrheure' : ' de 15h00 à 16h30' 'descrheure' : ' de 15h00 à 16h30'
""" """
# Attention: transformation fonction ScoDc7 en SQLAlchemy # Attention: transformation fonction ScoDoc7 en SQLAlchemy
cnx = ndb.GetDBConnexion() cnx = ndb.GetDBConnexion()
evals = _evaluationEditor.list(cnx, args, sortkey=sortkey) evals = _evaluationEditor.list(cnx, args, sortkey=sortkey)
# calcule duree (chaine de car.) de chaque evaluation et ajoute jouriso, matin, apresmidi # 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 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( def do_evaluation_create(
moduleimpl_id=None, moduleimpl_id=None,
jour=None, jour=None,
@ -220,7 +132,7 @@ def do_evaluation_create(
) )
args = locals() args = locals()
log("do_evaluation_create: args=" + str(args)) log("do_evaluation_create: args=" + str(args))
_check_evaluation_args(args) check_evaluation_args(args)
# Check numeros # Check numeros
module_evaluation_renumber(moduleimpl_id, only_if_unumbered=True) module_evaluation_renumber(moduleimpl_id, only_if_unumbered=True)
if not "numero" in args or args["numero"] is None: if not "numero" in args or args["numero"] is None:
@ -263,6 +175,7 @@ def do_evaluation_create(
# news # news
M = sco_moduleimpl.moduleimpl_list(moduleimpl_id=moduleimpl_id)[0] 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 = sco_edit_module.module_list(args={"module_id": M["module_id"]})[0]
mod["moduleimpl_id"] = M["moduleimpl_id"] mod["moduleimpl_id"] = M["moduleimpl_id"]
mod["url"] = "Notes/moduleimpl_status?moduleimpl_id=%(moduleimpl_id)s" % mod 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() "Modification évaluation impossible pour %s" % current_user.get_nomplogin()
) )
args["moduleimpl_id"] = moduleimpl_id args["moduleimpl_id"] = moduleimpl_id
_check_evaluation_args(args) check_evaluation_args(args)
cnx = ndb.GetDBConnexion() cnx = ndb.GetDBConnexion()
_evaluationEditor.edit(cnx, args) _evaluationEditor.edit(cnx, args)

View File

@ -122,7 +122,7 @@ def evaluation_create_form(
# #
mod_descr = '<a href="moduleimpl_status?moduleimpl_id=%s">%s %s</a> %s' % ( mod_descr = '<a href="moduleimpl_status?moduleimpl_id=%s">%s %s</a> %s' % (
moduleimpl_id, moduleimpl_id,
mod["code"], mod["code"] or "module sans code",
mod["titre"], mod["titre"],
link, link,
) )

View File

@ -403,8 +403,9 @@ def do_evaluation_etat_in_mod(nt, moduleimpl_id):
def formsemestre_evaluations_cal(formsemestre_id): def formsemestre_evaluations_cal(formsemestre_id):
"""Page avec calendrier de toutes les evaluations de ce semestre""" """Page avec calendrier de toutes les evaluations de ce semestre"""
sem = sco_formsemestre.get_formsemestre(formsemestre_id) formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
nt = sco_cache.NotesTableCache.get(formsemestre_id) # > liste evaluations nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
sem = formsemestre.to_dict()
evals = nt.get_evaluations_etats() evals = nt.get_evaluations_etats()
nb_evals = len(evals) 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. N'indique pas les évaluations de ratrapage ni celles des modules de bonus/malus.
""" """
sem = sco_formsemestre.get_formsemestre(formsemestre_id) formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
nt = sco_cache.NotesTableCache.get(formsemestre_id) # > liste evaluations nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
sem = formsemestre.to_dict()
evals = nt.get_evaluations_etats() evals = nt.get_evaluations_etats()
T = [] T = []
@ -635,7 +637,14 @@ def evaluation_describe(evaluation_id="", edit_in_place=True):
) )
mod_descr = ( mod_descr = (
'<a href="moduleimpl_status?moduleimpl_id=%s">%s %s</a> <span class="resp">(resp. <a title="%s">%s</a>)</span> %s' '<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 "" etit = E["description"] or ""

View File

@ -29,6 +29,9 @@
""" """
from flask import url_for, g, request 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.notesdb as ndb
import app.scodoc.sco_utils as scu import app.scodoc.sco_utils as scu
from app import log from app import log
@ -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 } ) # etudid : { formsemestre_id d'inscription le plus recent dans les dates considérées, etud }
for formsemestre_id in formsemestre_ids_parcours: 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() etudids = nt.get_etudids()
for etudid in etudids: for etudid in etudids:
if etudid not in etuds_infos: # pas encore traité ? if etudid not in etuds_infos: # pas encore traité ?

View File

@ -601,7 +601,7 @@ def do_formsemestre_createwithmodules(edit=False):
"input_type": "text_suggest", "input_type": "text_suggest",
"size": 50, "size": 50,
"withcheckbox": True, "withcheckbox": True,
"title": "%s %s" % (mod["code"], mod["titre"]), "title": "%s %s" % (mod["code"] or "", mod["titre"] or ""),
"allowed_values": allowed_user_names, "allowed_values": allowed_user_names,
"template": itemtemplate, "template": itemtemplate,
"text_suggest_options": { "text_suggest_options": {
@ -802,7 +802,9 @@ def do_formsemestre_createwithmodules(edit=False):
} }
moduleimpl_id = sco_moduleimpl.do_moduleimpl_create(modargs) moduleimpl_id = sco_moduleimpl.do_moduleimpl_create(modargs)
mod = sco_edit_module.module_list({"module_id": module_id})[0] 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 # INSCRIPTIONS DES ETUDIANTS
log( log(
'inscription module: %s = "%s"' 'inscription module: %s = "%s"'
@ -824,7 +826,7 @@ def do_formsemestre_createwithmodules(edit=False):
) )
msg += [ msg += [
"inscription de %d étudiants au module %s" "inscription de %d étudiants au module %s"
% (len(etudids), mod["code"]) % (len(etudids), mod["code"] or "(module sans code)")
] ]
else: else:
log( log(
@ -919,11 +921,19 @@ def formsemestre_delete_moduleimpls(formsemestre_id, module_ids_to_del):
if evals: if evals:
msg += [ 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>' '<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 ok = False
else: 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( sco_moduleimpl.do_moduleimpl_delete(
moduleimpl_id, formsemestre_id=formsemestre_id moduleimpl_id, formsemestre_id=formsemestre_id
) )

View File

@ -37,6 +37,9 @@ import flask
from flask import url_for, g, request from flask import url_for, g, request
from flask_login import current_user 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.sco_utils as scu
import app.scodoc.notesdb as ndb import app.scodoc.notesdb as ndb
from app import log from app import log
@ -260,7 +263,8 @@ def formsemestre_ext_edit_ue_validations(formsemestre_id, etudid):
def _make_page(etud, sem, tf, message=""): 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"]) moy_gen = nt.get_etud_moy_gen(etud["etudid"])
H = [ H = [
html_sco_header.sco_header( html_sco_header.sco_header(

View File

@ -32,14 +32,16 @@ import time
import flask import flask
from flask import url_for, g, request 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 import app.scodoc.sco_utils as scu
from app import log from app import log
from app.scodoc.scolog import logdb from app.scodoc.scolog import logdb
from app.scodoc.sco_exceptions import ScoException, ScoValueError 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 from app.scodoc.sco_codes_parcours import UE_STANDARD, UE_SPORT, UE_TYPE_NAME
import app.scodoc.notesdb as ndb 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_find_etud
from app.scodoc import sco_formsemestre from app.scodoc import sco_formsemestre
from app.scodoc import sco_moduleimpl from app.scodoc import sco_moduleimpl
@ -186,7 +188,9 @@ def do_formsemestre_desinscription(etudid, formsemestre_id):
raise ScoValueError("desinscription impossible: semestre verrouille") raise ScoValueError("desinscription impossible: semestre verrouille")
# -- Si decisions de jury, desinscription interdite # -- 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): if nt.etud_has_decision(etudid):
raise ScoValueError( raise ScoValueError(
"desinscription impossible: l'étudiant a une décision de jury (la supprimer avant si nécessaire)" "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") raise ScoValueError("Modification impossible: semestre verrouille")
etud = sco_etud.get_etud_info(etudid=etudid, filled=True)[0] 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() F = html_sco_header.sco_footer()
H = [ 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_ids[ue_id].append(mod["moduleimpl_id"])
modimpls_by_ue_names[ue_id].append( 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() vals = scu.get_request_args()
if not vals.get("tf_submitted", False): if not vals.get("tf_submitted", False):
@ -527,7 +532,7 @@ def formsemestre_inscription_option(etudid, formsemestre_id):
if ue["type"] != UE_STANDARD: if ue["type"] != UE_STANDARD:
ue_descr += " <em>%s</em>" % UE_TYPE_NAME[ue["type"]] ue_descr += " <em>%s</em>" % UE_TYPE_NAME[ue["type"]]
ue_status = nt.get_etud_ue_status(etudid, ue_id) 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"]) 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)' % ( ue_descr += ' <a class="discretelink" href="formsemestre_bulletinetud?formsemestre_id=%s&etudid=%s" title="%s">(capitalisée le %s)' % (
sem_origin["formsemestre_id"], sem_origin["formsemestre_id"],
@ -648,7 +653,7 @@ function chkbx_select(field_id, state) {
"%s (%s)" "%s (%s)"
% ( % (
modsdict[x]["module"]["titre"], modsdict[x]["module"]["titre"],
modsdict[x]["module"]["code"], modsdict[x]["module"]["code"] or "(module sans code)",
) )
for x in a_desinscrire for x in a_desinscrire
] ]
@ -667,7 +672,7 @@ function chkbx_select(field_id, state) {
"%s (%s)" "%s (%s)"
% ( % (
modsdict[x]["module"]["titre"], modsdict[x]["module"]["titre"],
modsdict[x]["module"]["code"], modsdict[x]["module"]["code"] or "(module sans code)",
) )
for x in a_inscrire for x in a_inscrire
] ]
@ -785,7 +790,9 @@ def list_inscrits_ailleurs(formsemestre_id):
Pour chacun, donne la liste des semestres. Pour chacun, donne la liste des semestres.
{ etudid : [ liste de sems ] } { 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() etudids = nt.get_etudids()
d = {} d = {}
for etudid in etudids: for etudid in etudids:

View File

@ -36,8 +36,8 @@ from flask_login import current_user
from app import log from app import log
from app.comp import res_sem from app.comp import res_sem
from app.comp.res_common import NotesTableCompat
from app.models import Module from app.models import Module
from app.models import formsemestre
from app.models.formsemestre import FormSemestre from app.models.formsemestre import FormSemestre
import app.scodoc.sco_utils as scu import app.scodoc.sco_utils as scu
from app.scodoc.sco_utils import ModuleType 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_bulletins
from app.scodoc import sco_codes_parcours from app.scodoc import sco_codes_parcours
from app.scodoc import sco_compute_moy 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_edit_ue
from app.scodoc import sco_evaluations from app.scodoc import sco_evaluations
from app.scodoc import sco_evaluation_db 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 Liste des modules et de leurs coefficients
""" """
sem = sco_formsemestre.get_formsemestre(formsemestre_id) 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) use_ue_coefs = sco_preferences.get_preference("use_ue_coefs", formsemestre_id)
F = sco_formations.formation_list(args={"formation_id": sem["formation_id"]})[0] F = sco_formations.formation_list(args={"formation_id": sem["formation_id"]})[0]
parcours = sco_codes_parcours.get_parcours_from_code(F["type_parcours"]) 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 = { l = {
"UE": M["ue"]["acronyme"], "UE": M["ue"]["acronyme"],
"Code": M["module"]["code"], "Code": M["module"]["code"] or "",
"Module": M["module"]["abbrev"] or M["module"]["titre"], "Module": M["module"]["abbrev"] or M["module"]["titre"],
"_Module_class": "scotext", "_Module_class": "scotext",
"Inscrits": len(ModInscrits), "Inscrits": len(ModInscrits),
@ -991,7 +991,6 @@ def formsemestre_status(formsemestre_id=None):
modimpls = sco_moduleimpl.moduleimpl_withmodule_list( modimpls = sco_moduleimpl.moduleimpl_withmodule_list(
formsemestre_id=formsemestre_id formsemestre_id=formsemestre_id
) )
# nt = sco_cache.NotesTableCache.get(formsemestre_id)
formsemestre = FormSemestre.query.get_or_404(formsemestre_id) formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
nt = res_sem.load_formsemestre_results(formsemestre) nt = res_sem.load_formsemestre_results(formsemestre)

View File

@ -31,7 +31,6 @@ import time
import flask import flask
from flask import url_for, g, request from flask import url_for, g, request
from app.api.sco_api import formsemestre
import app.scodoc.notesdb as ndb import app.scodoc.notesdb as ndb
import app.scodoc.sco_utils as scu import app.scodoc.sco_utils as scu
@ -40,6 +39,7 @@ from app import log
from app.comp import res_sem from app.comp import res_sem
from app.comp.res_common import NotesTableCompat from app.comp.res_common import NotesTableCompat
from app.models import FormSemestre from app.models import FormSemestre
from app.models.notes import etud_has_notes_attente
from app.scodoc.sco_exceptions import ScoValueError from app.scodoc.sco_exceptions import ScoValueError
from app.scodoc.scolog import logdb 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_edit_ue
from app.scodoc import sco_etud from app.scodoc import sco_etud
from app.scodoc import sco_formsemestre 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_inscriptions
from app.scodoc import sco_formsemestre_status
from app.scodoc import sco_parcours_dut from app.scodoc import sco_parcours_dut
from app.scodoc.sco_parcours_dut import etud_est_inscrit_ue from app.scodoc.sco_parcours_dut import etud_est_inscrit_ue
from app.scodoc import sco_photos from app.scodoc import sco_photos
@ -72,9 +70,8 @@ def formsemestre_validation_etud_form(
sortcol=None, sortcol=None,
readonly=True, readonly=True,
): ):
nt = sco_cache.NotesTableCache.get( formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
formsemestre_id nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
) # > get_table_moyennes_triees, get_etud_decision_sem
T = nt.get_table_moyennes_triees() T = nt.get_table_moyennes_triees()
if not etudid and etud_index is None: if not etudid and etud_index is None:
raise ValueError("formsemestre_validation_etud_form: missing argument etudid") 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) decision_jury = Se.nt.get_etud_decision_sem(etudid)
# Bloque si note en attente # Bloque si note en attente
if nt.etud_has_notes_attente(etudid): if etud_has_notes_attente(etudid, formsemestre_id):
H.append( H.append(
tf_error_message( tf_error_message(
f"""Impossible de statuer sur cet étudiant: il a des notes en 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"]) formsemestre = FormSemestre.query.get(sem["formsemestre_id"])
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre) nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
# nt = sco_cache.NotesTableCache.get(sem["formsemestre_id"])
if is_cur: if is_cur:
type_sem = "*" # now unused type_sem = "*" # now unused
class_sem = "sem_courant" class_sem = "sem_courant"
@ -649,7 +645,7 @@ def formsemestre_recap_parcours_table(
else: else:
code = "" code = ""
ue_status = nt.get_etud_ue_status(etudid, ue["ue_id"]) 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 explanation_ue = [] # list of strings
if code == ADM: if code == ADM:
class_ue = "ue_adm" class_ue = "ue_adm"
@ -657,12 +653,12 @@ def formsemestre_recap_parcours_table(
class_ue = "ue_cmp" class_ue = "ue_cmp"
else: else:
class_ue = "ue" 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.") explanation_ue.append("UE externe.")
# log('x'*12+' EXTERNAL %s' % notes_table.fmt_note(moy_ue)) XXXXXXX # log('x'*12+' EXTERNAL %s' % notes_table.fmt_note(moy_ue)) XXXXXXX
# log('UE=%s' % pprint.pformat(ue)) # log('UE=%s' % pprint.pformat(ue))
# log('explanation_ue=%s\n'%explanation_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" class_ue += " ue_capitalized"
explanation_ue.append( explanation_ue.append(
"Capitalisée le %s." % (ue_status["event_date"] or "?") "Capitalisée le %s." % (ue_status["event_date"] or "?")
@ -709,7 +705,10 @@ def formsemestre_recap_parcours_table(
# ECTS validables dans chaque UE # ECTS validables dans chaque UE
for ue in ues: for ue in ues:
ue_status = nt.get_etud_ue_status(etudid, ue["ue_id"]) ue_status = nt.get_etud_ue_status(etudid, ue["ue_id"])
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("<td></td></tr>")
H.append("</table>") H.append("</table>")
@ -878,9 +877,8 @@ def do_formsemestre_validation_auto(formsemestre_id):
"Saisie automatisee des decisions d'un semestre" "Saisie automatisee des decisions d'un semestre"
sem = sco_formsemestre.get_formsemestre(formsemestre_id) sem = sco_formsemestre.get_formsemestre(formsemestre_id)
next_semestre_id = sem["semestre_id"] + 1 next_semestre_id = sem["semestre_id"] + 1
nt = sco_cache.NotesTableCache.get( formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
formsemestre_id nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
) # > get_etudids, get_etud_decision_sem,
etudids = nt.get_etudids() etudids = nt.get_etudids()
nb_valid = 0 nb_valid = 0
conflicts = [] # liste des etudiants avec decision differente déjà saisie 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.barre_moy_ok
and Se.barres_ue_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, # check: s'il existe une decision ou autorisation et qu'elles sont differentes,
# warning (et ne fait rien) # 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 Si le coefficient est spécifié, modifie le coefficient de
cette UE (utile seulement pour les semestres extérieurs). 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) sem = sco_formsemestre.get_formsemestre(formsemestre_id)
cnx = ndb.GetDBConnexion() cnx = ndb.GetDBConnexion()
nt = sco_cache.NotesTableCache.get(formsemestre_id) # > get_etud_ue_status
if ue_coefficient != None: if ue_coefficient != None:
sco_formsemestre.do_formsemestre_uecoef_edit_or_create( sco_formsemestre.do_formsemestre_uecoef_edit_or_create(
cnx, formsemestre_id, ue_id, ue_coefficient cnx, formsemestre_id, ue_id, ue_coefficient

View File

@ -45,6 +45,9 @@ from flask import g, request
from flask import url_for, make_response from flask import url_for, make_response
from app import db 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 import GROUPNAME_STR_LEN, SHORT_STR_LEN
from app.models.groups import Partition from app.models.groups import Partition
import app.scodoc.sco_utils as scu import app.scodoc.sco_utils as scu
@ -488,17 +491,14 @@ def XMLgetGroupsInPartition(partition_id): # was XMLgetGroupesTD
<group ...> <group ...>
... ...
""" """
from app.scodoc import sco_formsemestre
cnx = ndb.GetDBConnexion()
t0 = time.time() t0 = time.time()
partition = get_partition(partition_id) partition = get_partition(partition_id)
formsemestre_id = partition["formsemestre_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) groups = get_partition_groups(partition)
nt = sco_cache.NotesTableCache.get(formsemestre_id) # > inscrdict
etuds_set = set(nt.inscrdict)
# Build XML: # Build XML:
t1 = time.time() t1 = time.time()
doc = Element("ajax-response") doc = Element("ajax-response")
@ -1277,13 +1277,13 @@ def groups_auto_repartition(partition_id=None):
partition = get_partition(partition_id) partition = get_partition(partition_id)
formsemestre_id = partition["formsemestre_id"] formsemestre_id = partition["formsemestre_id"]
formsemestre = FormSemestre.query.get(formsemestre_id)
# renvoie sur page édition groupes # renvoie sur page édition groupes
dest_url = url_for( dest_url = url_for(
"scolar.affect_groups", scodoc_dept=g.scodoc_dept, partition_id=partition_id "scolar.affect_groups", scodoc_dept=g.scodoc_dept, partition_id=partition_id
) )
if not sco_permissions_check.can_change_groups(formsemestre_id): if not sco_permissions_check.can_change_groups(formsemestre_id):
raise AccessDenied("Vous n'avez pas le droit d'effectuer cette opération !") raise AccessDenied("Vous n'avez pas le droit d'effectuer cette opération !")
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
descr = [ descr = [
("partition_id", {"input_type": "hidden"}), ("partition_id", {"input_type": "hidden"}),
@ -1301,7 +1301,7 @@ def groups_auto_repartition(partition_id=None):
H = [ H = [
html_sco_header.sco_header(page_title="Répartition des groupes"), html_sco_header.sco_header(page_title="Répartition des groupes"),
"<h2>Répartition des groupes de %s</h2>" % partition["partition_name"], "<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 """<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 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 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() # return '\n'.join(H) + tf[1] + html_sco_header.sco_footer()
group_ids.append(create_group(partition_id, group_name)) 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 identdict = nt.identdict
# build: { civilite : liste etudids trie par niveau croissant } # build: { civilite : liste etudids trie par niveau croissant }
civilites = set([x["civilite"] for x in identdict.values()]) civilites = set([x["civilite"] for x in identdict.values()])
@ -1384,9 +1384,8 @@ def _get_prev_moy(etudid, formsemestre_id):
etud = info[0] etud = info[0]
Se = sco_parcours_dut.SituationEtudParcours(etud, formsemestre_id) Se = sco_parcours_dut.SituationEtudParcours(etud, formsemestre_id)
if Se.prev: if Se.prev:
nt = sco_cache.NotesTableCache.get( prev_sem = FormSemestre.query.get(Se.prev["formsemestre_id"])
Se.prev["formsemestre_id"] nt: NotesTableCompat = res_sem.load_formsemestre_results(prev_sem)
) # > get_etud_moy_gen
return nt.get_etud_moy_gen(etudid) return nt.get_etud_moy_gen(etudid)
else: else:
return 0.0 return 0.0

View File

@ -31,16 +31,17 @@
import flask import flask
from flask import url_for, g, request 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 log
from app import models
from app.comp import res_sem from app.comp import res_sem
from app.comp import moy_mod from app.comp import moy_mod
from app.comp.moy_mod import ModuleImplResults from app.comp.moy_mod import ModuleImplResults
from app.comp.res_common import NotesTableCompat 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.TrivialFormulator import TrivialFormulator
from app.scodoc import sco_cache from app.scodoc import sco_cache
from app.scodoc import sco_edit_module from app.scodoc import sco_edit_module
@ -788,7 +789,9 @@ def _add_moymod_column(
): ):
"""Ajoute la colonne moymod à rows""" """Ajoute la colonne moymod à rows"""
col_id = "moymod" 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 nb_notes = 0
sum_notes = 0 sum_notes = 0
notes = [] # liste des notes numeriques, pour calcul histogramme uniquement notes = [] # liste des notes numeriques, pour calcul histogramme uniquement

View File

@ -29,6 +29,7 @@
""" """
from flask_login import current_user from flask_login import current_user
import psycopg2
import app.scodoc.sco_utils as scu import app.scodoc.sco_utils as scu
import app.scodoc.notesdb as ndb import app.scodoc.notesdb as ndb
@ -271,7 +272,12 @@ _moduleimpl_inscriptionEditor = ndb.EditableTable(
def do_moduleimpl_inscription_create(args, formsemestre_id=None): def do_moduleimpl_inscription_create(args, formsemestre_id=None):
"create a moduleimpl_inscription" "create a moduleimpl_inscription"
cnx = ndb.GetDBConnexion() cnx = ndb.GetDBConnexion()
try:
r = _moduleimpl_inscriptionEditor.create(cnx, args) 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( sco_cache.invalidate_formsemestre(
formsemestre_id=formsemestre_id formsemestre_id=formsemestre_id
) # > moduleimpl_inscription ) # > moduleimpl_inscription

View File

@ -33,6 +33,10 @@ import flask
from flask import url_for, g, request from flask import url_for, g, request
from flask_login import current_user 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.notesdb as ndb
import app.scodoc.sco_utils as scu import app.scodoc.sco_utils as scu
from app import log from app import log
@ -88,7 +92,11 @@ def moduleimpl_inscriptions_edit(moduleimpl_id, etuds=[], submitted=False):
"Appliquer les modifications". "Appliquer les modifications".
</p> </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 # Liste des inscrits à ce semestre
inscrits = sco_formsemestre_inscriptions.do_formsemestre_inscription_listinscrits( inscrits = sco_formsemestre_inscriptions.do_formsemestre_inscription_listinscrits(
@ -304,8 +312,8 @@ def moduleimpl_inscriptions_stats(formsemestre_id):
H.append( 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>' '<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["ue"]["acronyme"] or "",
mod["module"]["code"], mod["module"]["code"] or "(module sans code)",
mod["nb_inscrits"], mod["nb_inscrits"],
c_link, c_link,
) )
@ -333,7 +341,11 @@ def moduleimpl_inscriptions_stats(formsemestre_id):
c_link = mod["module"]["titre"] c_link = mod["module"]["titre"]
H.append( H.append(
'<tr class="formsemestre_status_green"><td>%s</td><td class="formsemestre_status_code">%s</td><td>%s</td></tr>' '<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>") H.append("</table>")
@ -479,21 +491,21 @@ def get_etuds_with_capitalized_ue(formsemestre_id):
returns { ue_id : [ { infos } ] } returns { ue_id : [ { infos } ] }
""" """
UECaps = scu.DictDefault(defaultvalue=[]) UECaps = scu.DictDefault(defaultvalue=[])
nt = sco_cache.NotesTableCache.get( formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
formsemestre_id nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
) # > get_ues_stat_dict, get_etud_ue_status
inscrits = sco_formsemestre_inscriptions.do_formsemestre_inscription_list( inscrits = sco_formsemestre_inscriptions.do_formsemestre_inscription_list(
args={"formsemestre_id": formsemestre_id} args={"formsemestre_id": formsemestre_id}
) )
ues = nt.get_ues_stat_dict() ues = nt.get_ues_stat_dict()
for ue in ues: for ue in ues:
for etud in inscrits: for etud in inscrits:
status = nt.get_etud_ue_status(etud["etudid"], ue["ue_id"]) ue_status = nt.get_etud_ue_status(etud["etudid"], ue["ue_id"])
if status["was_capitalized"]: if ue_status and ue_status["was_capitalized"]:
UECaps[ue["ue_id"]].append( UECaps[ue["ue_id"]].append(
{ {
"etudid": etud["etudid"], "etudid": etud["etudid"],
"ue_status": status, "ue_status": ue_status,
"is_ins": is_inscrit_ue( "is_ins": is_inscrit_ue(
etud["etudid"], formsemestre_id, ue["ue_id"] etud["etudid"], formsemestre_id, ue["ue_id"]
), ),

View File

@ -203,7 +203,6 @@ def moduleimpl_status(moduleimpl_id=None, partition_id=None):
moduleimpl_id=M["moduleimpl_id"] moduleimpl_id=M["moduleimpl_id"]
) )
# nt = sco_cache.NotesTableCache.get(formsemestre_id)
nt: NotesTableCompat = res_sem.load_formsemestre_results(modimpl.formsemestre) nt: NotesTableCompat = res_sem.load_formsemestre_results(modimpl.formsemestre)
mod_evals = sco_evaluation_db.do_evaluation_list({"moduleimpl_id": moduleimpl_id}) mod_evals = sco_evaluation_db.do_evaluation_list({"moduleimpl_id": moduleimpl_id})

View File

@ -36,7 +36,7 @@ import app.scodoc.sco_utils as scu
import app.scodoc.notesdb as ndb import app.scodoc.notesdb as ndb
from app import log from app import log
from app.scodoc.scolog import logdb 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_formsemestre
from app.scodoc import sco_formations from app.scodoc import sco_formations
from app.scodoc.sco_codes_parcours import ( from app.scodoc.sco_codes_parcours import (
@ -111,9 +111,6 @@ class DecisionSem(object):
def SituationEtudParcours(etud, formsemestre_id): def SituationEtudParcours(etud, formsemestre_id):
"""renvoie une instance de SituationEtudParcours (ou sous-classe spécialisée)""" """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) formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre) nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
parcours = nt.parcours parcours = nt.parcours
@ -302,9 +299,6 @@ class SituationEtudParcoursGeneric(object):
sem["semestre_id"] == n1 sem["semestre_id"] == n1
and sem["formation_code"] == self.formation.formation_code 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"]) formsemestre = FormSemestre.query.get_or_404(sem["formsemestre_id"])
nt: NotesTableCompat = res_sem.load_formsemestre_results( nt: NotesTableCompat = res_sem.load_formsemestre_results(
formsemestre 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.""" sont validés. En sortie, sem_idx_set contient ceux qui n'ont pas été validés."""
for sem in self.get_semestres(): for sem in self.get_semestres():
if sem["formation_code"] == self.formation.formation_code: 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"]) formsemestre = FormSemestre.query.get_or_404(sem["formsemestre_id"])
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre) nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
decision = nt.get_etud_decision_sem(self.etudid) decision = nt.get_etud_decision_sem(self.etudid)
@ -336,15 +327,16 @@ class SituationEtudParcoursGeneric(object):
def _comp_semestres(self): def _comp_semestres(self):
# etud['sems'] est trie par date decroissante (voir fill_etuds_info) # 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 = self.etud["sems"][:] # copy
sems.reverse() sems.reverse()
# Nb max d'UE et acronymes # Nb max d'UE et acronymes
ue_acros = {} # acronyme ue : 1 ue_acros = {} # acronyme ue : 1
nb_max_ue = 0 nb_max_ue = 0
for sem in sems: 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"]) formsemestre = FormSemestre.query.get_or_404(sem["formsemestre_id"])
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre) nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
ues = nt.get_ues_stat_dict(filter_sport=True) ues = nt.get_ues_stat_dict(filter_sport=True)
@ -414,9 +406,6 @@ class SituationEtudParcoursGeneric(object):
if not sem: if not sem:
code = "" # non inscrit à ce semestre code = "" # non inscrit à ce semestre
else: else:
# nt = sco_cache.NotesTableCache.get(
# sem["formsemestre_id"]
# ) # > get_etud_decision_sem
formsemestre = FormSemestre.query.get_or_404(sem["formsemestre_id"]) formsemestre = FormSemestre.query.get_or_404(sem["formsemestre_id"])
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre) nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
decision = nt.get_etud_decision_sem(self.etudid) decision = nt.get_etud_decision_sem(self.etudid)
@ -486,7 +475,6 @@ class SituationEtudParcoursGeneric(object):
# Verifications basiques: # Verifications basiques:
# ? # ?
# Code etat du semestre precedent: # Code etat du semestre precedent:
# nt = sco_cache.NotesTableCache.get(prev["formsemestre_id"])
formsemestre = FormSemestre.query.get_or_404(prev["formsemestre_id"]) formsemestre = FormSemestre.query.get_or_404(prev["formsemestre_id"])
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre) nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
self.prev_decision = nt.get_etud_decision_sem(self.etudid) self.prev_decision = nt.get_etud_decision_sem(self.etudid)
@ -545,8 +533,6 @@ class SituationEtudParcoursGeneric(object):
sem["formation_code"] == self.formation.formation_code sem["formation_code"] == self.formation.formation_code
and sem["semestre_id"] == s 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"]) formsemestre = FormSemestre.query.get_or_404(sem["formsemestre_id"])
nt: NotesTableCompat = res_sem.load_formsemestre_results( nt: NotesTableCompat = res_sem.load_formsemestre_results(
formsemestre 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) valid_semestre = CODES_SEM_VALIDES.get(code_etat_sem, False)
cnx = ndb.GetDBConnexion() 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) formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre) nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
ue_ids = [x["ue_id"] for x in nt.get_ues_stat_dict(filter_sport=True)] 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 code == ADM:
if moy_ue is None: if moy_ue is None:
# stocke la moyenne d'UE capitalisée: # stocke la moyenne d'UE capitalisée:
moy_ue = nt.get_etud_ue_status(etudid, ue_id)["moy"] ue_status = nt.get_etud_ue_status(etudid, ue_id)
moy_ue = ue_status["moy"] if ue_status else ""
args["moy_ue"] = moy_ue args["moy_ue"] = moy_ue
log("formsemestre_validate_ue: create %s" % args) log("formsemestre_validate_ue: create %s" % args)
if code != None: if code != None:

View File

@ -257,7 +257,7 @@ class PlacementRunner:
self.moduleimpl_data["formsemestre_id"] self.moduleimpl_data["formsemestre_id"]
) )
self.evalname = "%s-%s" % ( self.evalname = "%s-%s" % (
self.module_data["code"], self.module_data["code"] or "?",
ndb.DateDMYtoISO(self.eval_data["jour"]), ndb.DateDMYtoISO(self.eval_data["jour"]),
) )
if self.eval_data["description"]: if self.eval_data["description"]:
@ -266,7 +266,8 @@ class PlacementRunner:
self.evaltitre = "évaluation du %s" % self.eval_data["jour"] self.evaltitre = "évaluation du %s" % self.eval_data["jour"]
self.desceval = [ # une liste de chaines: description de l'evaluation self.desceval = [ # une liste de chaines: description de l'evaluation
"%s" % self.sem["titreannee"], "%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, "Surveillants : %s" % self.surveillants,
"Batiment : %(batiment)s - Salle : %(salle)s" % self.__dict__, "Batiment : %(batiment)s - Salle : %(salle)s" % self.__dict__,
"Controle : %s (coef. %g)" "Controle : %s (coef. %g)"

View File

@ -33,6 +33,9 @@ import collections
from flask import url_for, g, request 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 import app.scodoc.sco_utils as scu
from app.scodoc import sco_abs from app.scodoc import sco_abs
from app.scodoc import sco_cache from app.scodoc import sco_cache
@ -58,17 +61,25 @@ def etud_get_poursuite_info(sem, etud):
for s in etud["sems"]: for s in etud["sems"]:
if s["semestre_id"] == sem_id: if s["semestre_id"] == sem_id:
etudid = etud["etudid"] 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) dec = nt.get_etud_decision_sem(etudid)
# Moyennes et rangs des UE # Moyennes et rangs des UE
ues = nt.get_ues_stat_dict(filter_sport=True) 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"], 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 = [ rg_ues = [
("rang_" + ue["acronyme"], nt.ue_rangs[ue["ue_id"]][0][etudid]) ("rang_" + ue["acronyme"], nt.ue_rangs[ue["ue_id"]][0][etudid])
for ue in ues for ue in ues
@ -81,14 +92,17 @@ def etud_get_poursuite_info(sem, etud):
for ue in ues: # on parcourt chaque UE for ue in ues: # on parcourt chaque UE
for modimpl in modimpls: # dans chaque UE les modules for modimpl in modimpls: # dans chaque UE les modules
if modimpl["module"]["ue_id"] == ue["ue_id"]: if modimpl["module"]["ue_id"] == ue["ue_id"]:
codeModule = modimpl["module"]["code"] codeModule = modimpl["module"]["code"] or ""
noteModule = scu.fmt_note( noteModule = scu.fmt_note(
nt.get_etud_mod_moy(modimpl["moduleimpl_id"], etudid) nt.get_etud_mod_moy(modimpl["moduleimpl_id"], etudid)
) )
if noteModule != "NI": # si étudiant inscrit au module if noteModule != "NI": # si étudiant inscrit au module
rangModule = nt.mod_rangs[modimpl["moduleimpl_id"]][0][ if nt.mod_rangs is not None:
etudid rangModule = nt.mod_rangs[modimpl["moduleimpl_id"]][
] 0
][etudid]
else:
rangModule = ""
modules.append([codeModule, noteModule]) modules.append([codeModule, noteModule])
rangs.append(["rang_" + codeModule, rangModule]) rangs.append(["rang_" + codeModule, rangModule])

View File

@ -68,17 +68,17 @@ des tuples (name, value, formsemestre_id).
Si formsemestre_id est NULL, la valeur concerne tous les semestres, Si formsemestre_id est NULL, la valeur concerne tous les semestres,
sinon, elle ne concerne que le semestre indiqué. sinon, elle ne concerne que le semestre indiqué.
* Utilisation dans ScoDoc8 * Utilisation dans ScoDoc 9
- lire une valeur: - lire une valeur:
get_preference(name, formsemestre_id) get_preference(name, formsemestre_id)
nb: les valeurs sont des chaines, sauf: 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 . les boolcheckbox qui sont des entiers 0 ou 1
- avoir un mapping (read only) de toutes les valeurs: - avoir un mapping (read only) de toutes les valeurs:
sco_preferences.SemPreferences(formsemestre_id) sco_preferences.SemPreferences(formsemestre_id)
- editer les preferences globales: - éditer les preferences globales:
sco_preferences.get_base_preferences(self).edit() sco_preferences.get_base_preferences(self).edit()
- editer les preferences d'un semestre: - éditer les preferences d'un semestre:
SemPreferences(formsemestre_id).edit() SemPreferences(formsemestre_id).edit()
* Implémentation: sco_preferences.py * Implémentation: sco_preferences.py
@ -123,22 +123,22 @@ from app.scodoc.TrivialFormulator import TrivialFormulator
import app.scodoc.notesdb as ndb import app.scodoc.notesdb as ndb
import app.scodoc.sco_utils as scu import app.scodoc.sco_utils as scu
_SCO_BASE_PREFERENCES = {} # { dept_acronym: BasePreferences instance }
def clear_base_preferences(): def clear_base_preferences():
"""Clear cached preferences""" """Clear cached preferences"""
# usefull only for tests, where the same process may run # usefull only for tests, where the same process may run
# successively on several databases # successively on several databases
_SCO_BASE_PREFERENCES.clear() g._SCO_BASE_PREFERENCES = {} # { dept_id: BasePreferences instance }
def get_base_preferences(): def get_base_preferences():
"""Return global preferences for the current department""" """Return global preferences for the current department"""
dept_acronym = g.scodoc_dept dept_id = g.scodoc_dept_id
if not dept_acronym in _SCO_BASE_PREFERENCES: if not hasattr(g, "_SCO_BASE_PREFERENCES"):
_SCO_BASE_PREFERENCES[dept_acronym] = BasePreferences(dept_acronym) g._SCO_BASE_PREFERENCES = {}
return _SCO_BASE_PREFERENCES[dept_acronym] 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): def get_preference(name, formsemestre_id=None):
@ -263,10 +263,10 @@ class BasePreferences(object):
filter_nulls=False, filter_nulls=False,
) )
def __init__(self, dept_acronym: str): def __init__(self, dept_id: int):
dept = Departement.query.filter_by(acronym=dept_acronym).first() dept = Departement.query.get(dept_id)
if not dept: if not dept:
raise ScoValueError(f"Invalid departement: {dept_acronym}") raise ScoValueError(f"Invalid departement: {dept_id}")
self.dept_id = dept.id self.dept_id = dept.id
self.init() self.init()
self.load() self.load()
@ -1859,7 +1859,7 @@ class BasePreferences(object):
def load(self): def load(self):
"""Load all preferences from db""" """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() cnx = ndb.GetDBConnexion()
preflist = self._editor.list(cnx, {"dept_id": self.dept_id}) preflist = self._editor.list(cnx, {"dept_id": self.dept_id})
@ -2114,7 +2114,7 @@ class BasePreferences(object):
return form return form
class SemPreferences(object): class SemPreferences:
"""Preferences for a formsemestre""" """Preferences for a formsemestre"""
def __init__(self, formsemestre_id=None): def __init__(self, formsemestre_id=None):
@ -2270,9 +2270,8 @@ def doc_preferences():
return "\n".join([" | ".join(x) for x in L]) 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" "dict avec les options d'affichages (préférences) pour ce semestre"
prefs = SemPreferences(formsemestre_id)
fields = ( fields = (
"bul_show_abs", "bul_show_abs",
"bul_show_abs_modules", "bul_show_abs_modules",

View File

@ -31,29 +31,30 @@ import time
from openpyxl.styles.numbers import FORMAT_NUMBER_00 from openpyxl.styles.numbers import FORMAT_NUMBER_00
from flask import flash
from flask import request from flask import request
from flask_login import current_user 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_abs
from app.scodoc import sco_codes_parcours
from app.scodoc import sco_groups 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_excel
from app.scodoc import sco_formsemestre from app.scodoc import sco_formsemestre
from app.scodoc import sco_parcours_dut 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 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): def feuille_preparation_jury(formsemestre_id):
"Feuille excel pour preparation des jurys" "Feuille excel pour preparation des jurys"
nt = sco_cache.NotesTableCache.get( formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
formsemestre_id nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
) # > get_etudids, get_etud_moy_gen, get_ues_stat_dict, get_etud_ue_status, get_etud_decision_sem, identdict, etuds: Identite = nt.get_inscrits(order_by="moy") # tri par moy gen
etudids = nt.get_etudids(sorted=True) # tri par moy gen
sem = sco_formsemestre.get_formsemestre(formsemestre_id) sem = sco_formsemestre.get_formsemestre(formsemestre_id)
etud_groups = sco_groups.formsemestre_get_etud_groupnames(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 groupestd = {} # etudid : nom groupe principal
nbabs = {} nbabs = {}
nbabsjust = {} nbabsjust = {}
for etudid in etudids: for etud in etuds:
info = sco_etud.get_etud_info(etudid=etudid, filled=True) Se = sco_parcours_dut.SituationEtudParcours(
if not info: etud.to_dict_scodoc7(), formsemestre_id
continue # should not occur... )
etud = info[0]
Se = sco_parcours_dut.SituationEtudParcours(etud, formsemestre_id)
if Se.prev: if Se.prev:
ntp = sco_cache.NotesTableCache.get( formsemestre_prev = FormSemestre.query.get_or_404(
Se.prev["formsemestre_id"] 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): 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_code_s = (
ue["ue_code"] + "_%s" % ntp.sem["semestre_id"] ue["ue_code"] + "_%s" % ntp.sem["semestre_id"]
) # code indentifiant l'UE ) # code indentifiant l'UE
prev_moy_ue[ue_code_s][etudid] = ue_status["moy"] 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'])
prev_ue_acro[ue_code_s] = (ue["numero"], ue["acronyme"], ue["titre"]) prev_ue_acro[ue_code_s] = (ue["numero"], ue["acronyme"], ue["titre"])
prev_moy[etudid] = ntp.get_etud_moy_gen(etudid) prev_moy[etud.id] = ntp.get_etud_moy_gen(etud.id)
prev_decision = ntp.get_etud_decision_sem(etudid) prev_decision = ntp.get_etud_decision_sem(etud.id)
if prev_decision: if prev_decision:
prev_code[etudid] = prev_decision["code"] prev_code[etud.id] = prev_decision["code"]
if prev_decision["compense_formsemestre_id"]: 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): 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"] ue_code_s = ue["ue_code"] + "_%s" % nt.sem["semestre_id"]
moy_ue[ue_code_s][etudid] = ue_status["moy"] moy_ue[ue_code_s][etud.id] = ue_status["moy"] if ue_status else ""
# ue_acro[ue_code_s] = (ue['numero'], ue['acronyme'])
ue_acro[ue_code_s] = (ue["numero"], ue["acronyme"], ue["titre"]) ue_acro[ue_code_s] = (ue["numero"], ue["acronyme"], ue["titre"])
if Se.prev: if Se.prev:
try: 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: except:
pass pass
decision = nt.get_etud_decision_sem(etudid) decision = nt.get_etud_decision_sem(etud.id)
if decision: if decision:
code[etudid] = decision["code"] code[etud.id] = decision["code"]
if decision["compense_formsemestre_id"]: if decision["compense_formsemestre_id"]:
code[etudid] += "+" # indique qu'il a servi a compenser code[etud.id] += "+" # indique qu'il a servi a compenser
assidu[etudid] = {False: "Non", True: "Oui"}.get(decision["assidu"], "") assidu[etud.id] = {False: "Non", True: "Oui"}.get(decision["assidu"], "")
aut_list = sco_parcours_dut.formsemestre_get_autorisation_inscription( 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:
parcours[etudid] = Se.get_parcours_descr() parcours[etud.id] = Se.get_parcours_descr()
# groupe principal (td) # groupe principal (td)
groupestd[etudid] = "" groupestd[etud.id] = ""
for s in etud["sems"]: for s in Se.etud["sems"]:
if s["formsemestre_id"] == formsemestre_id: 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, "" main_partition_id, ""
) )
# absences: # absences:
e_nbabs, e_nbabsjust = sco_abs.get_abs_count(etudid, sem) e_nbabs, e_nbabsjust = sco_abs.get_abs_count(etud.id, sem)
nbabs[etudid] = e_nbabs nbabs[etud.id] = e_nbabs
nbabsjust[etudid] = e_nbabs - e_nbabsjust nbabsjust[etud.id] = e_nbabs - e_nbabsjust
# Codes des UE "semestre précédent": # Codes des UE "semestre précédent":
ue_prev_codes = list(prev_moy_ue.keys()) ue_prev_codes = list(prev_moy_ue.keys())
@ -229,26 +227,26 @@ def feuille_preparation_jury(formsemestre_id):
return x return x
i = 1 # numero etudiant i = 1 # numero etudiant
for etudid in etudids: for etud in etuds:
cells = [] cells = []
etud = nt.identdict[etudid]
cells.append(ws.make_cell(str(i))) cells.append(ws.make_cell(str(i)))
if sco_preferences.get_preference("prepa_jury_nip"): 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"): 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( cells += ws.make_row(
[ [
etudid, etud.id,
etud["civilite_str"], etud.civilite_str,
sco_etud.format_nom(etud["nom"]), sco_etud.format_nom(etud.nom),
sco_etud.format_prenom(etud["prenom"]), sco_etud.format_prenom(etud.prenom),
etud["date_naissance"], etud.date_naissance,
etud["bac"], admission.bac,
etud["specialite"], admission.specialite,
etud["classement"], admission.classement,
parcours[etudid], parcours[etud.id],
groupestd[etudid], groupestd[etud.id],
] ]
) )
co = len(cells) co = len(cells)
@ -256,33 +254,35 @@ def feuille_preparation_jury(formsemestre_id):
for ue_acro in ue_prev_codes: for ue_acro in ue_prev_codes:
cells.append( cells.append(
ws.make_cell( 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 co += 1
cells.append( 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 ) # moy gen prev
cells.append( 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 ) # decision prev
co += 2 co += 2
for ue_acro in ue_codes: for ue_acro in ue_codes:
cells.append( 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 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 co += 1
if moy_inter: if moy_inter:
cells.append(ws.make_cell(fmt(moy_inter.get(etudid, "")), style_note)) cells.append(ws.make_cell(fmt(moy_inter.get(etud.id, "")), style_note))
cells.append(ws.make_cell(str(nbabs.get(etudid, "")), style_center)) cells.append(ws.make_cell(str(nbabs.get(etud.id, "")), style_center))
cells.append(ws.make_cell(str(nbabsjust.get(etudid, "")), style_center)) cells.append(ws.make_cell(str(nbabsjust.get(etud.id, "")), style_center))
if code: if code:
cells.append(ws.make_cell(code.get(etudid, ""), style_moy)) cells.append(ws.make_cell(code.get(etud.id, ""), style_moy))
cells.append(ws.make_cell(autorisations.get(etudid, ""), style_moy)) cells.append(ws.make_cell(autorisations.get(etud.id, ""), style_moy))
# l.append(assidu.get(etudid, '')) # l.append(assidu.get(etud.id, ''))
ws.append_row(cells) ws.append_row(cells)
i += 1 i += 1
# #
@ -326,6 +326,7 @@ def feuille_preparation_jury(formsemestre_id):
) )
) )
xls = ws.generate() xls = ws.generate()
flash("Feuille préparation jury générée")
return scu.send_file( return scu.send_file(
xls, xls,
f"PrepaJury{sn}", f"PrepaJury{sn}",

View File

@ -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"]: for ue_id in nt.validations.ue_capitalisees.loc[[etudid]]["ue_id"]:
try: try:
uelist.append(nt.get_etud_ue_status(etudid, ue_id)["ue"]) uelist.append(nt.get_etud_ue_status(etudid, ue_id)["ue"])
except KeyError: except (KeyError, TypeError):
pass pass
uelist.sort(key=itemgetter("numero")) uelist.sort(key=itemgetter("numero"))
@ -162,13 +162,13 @@ def _comp_ects_by_ue_code(nt, decision_ues):
return ects_by_ue_code 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""" """Calcul somme des ECTS des UE capitalisees"""
ues = nt.get_ues_stat_dict() ues = nt.get_ues_stat_dict()
ects_by_ue_code = {} ects_by_ue_code = {}
for ue in ues: for ue in ues:
ue_status = nt.get_etud_ue_status(etudid, ue["ue_id"]) ue_status = nt.get_etud_ue_status(etudid, ue["ue_id"])
if ue_status["is_capitalized"]: if ue_status and ue_status["is_capitalized"]:
ects_val = float(ue_status["ue"]["ects"] or 0.0) ects_val = float(ue_status["ue"]["ects"] or 0.0)
ects_by_ue_code[ue["ue_code"]] = ects_val ects_by_ue_code[ue["ue_code"]] = ects_val
@ -217,9 +217,6 @@ def dict_pvjury(
'decisions_dict' : { etudid : decision (comme ci-dessus) }, '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) formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre) nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
if etudids is None: if etudids is None:

View File

@ -50,7 +50,6 @@ from app.scodoc import sco_bulletins_json
from app.scodoc import sco_bulletins_xml from app.scodoc import sco_bulletins_xml
from app.scodoc import sco_bulletins, sco_excel from app.scodoc import sco_bulletins, sco_excel
from app.scodoc import sco_codes_parcours from app.scodoc import sco_codes_parcours
from app.scodoc import sco_cache
from app.scodoc import sco_evaluations from app.scodoc import sco_evaluations
from app.scodoc import sco_evaluation_db from app.scodoc import sco_evaluation_db
from app.scodoc import sco_formations from app.scodoc import sco_formations
@ -307,8 +306,6 @@ def make_formsemestre_recapcomplet(
)[0] )[0]
parcours = formsemestre.formation.get_parcours() parcours = formsemestre.formation.get_parcours()
# nt = sco_cache.NotesTableCache.get(formsemestre_id) # sco91
# sco92 :
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre) nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
modimpls = formsemestre.modimpls_sorted modimpls = formsemestre.modimpls_sorted
ues = nt.get_ues_stat_dict() # incluant le(s) UE de sport ues = nt.get_ues_stat_dict() # incluant le(s) UE de sport
@ -853,7 +850,13 @@ def _list_notes_evals_titles(codemodule: str, evals: list[Evaluation]) -> list[s
L = [] L = []
eval_index = len(evals) - 1 eval_index = len(evals) - 1
for e in evals: 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 eval_index -= 1
return L return L
@ -885,8 +888,8 @@ def _formsemestre_recapcomplet_xml(
force_publishing=True, force_publishing=True,
): ):
"XML export: liste tous les bulletins XML." "XML export: liste tous les bulletins XML."
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
nt = sco_cache.NotesTableCache.get(formsemestre_id) # > get_table_moyennes_triees nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
T = nt.get_table_moyennes_triees() T = nt.get_table_moyennes_triees()
if not T: if not T:
return "", "", "xml" return "", "", "xml"
@ -956,7 +959,8 @@ def _formsemestre_recapcomplet_json(
"bulletins": [], "bulletins": [],
} }
bulletins = J["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() T = nt.get_table_moyennes_triees()
for t in T: for t in T:
etudid = t[-1] etudid = t[-1]

View File

@ -39,14 +39,16 @@ from operator import itemgetter
from flask import url_for, g, request from flask import url_for, g, request
import pydot 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 import app.scodoc.sco_utils as scu
from app.models import FormationModalite from app.models import FormationModalite
from app.scodoc import notesdb as ndb from app.scodoc import notesdb as ndb
from app.scodoc import html_sco_header from app.scodoc import html_sco_header
from app.scodoc import sco_codes_parcours from app.scodoc import sco_codes_parcours
from app.scodoc import sco_cache
from app.scodoc import sco_etud from app.scodoc import sco_etud
from app.scodoc import sco_excel
from app.scodoc import sco_formsemestre from app.scodoc import sco_formsemestre
from app.scodoc import sco_formsemestre_inscriptions from app.scodoc import sco_formsemestre_inscriptions
from app.scodoc import sco_parcours_dut from app.scodoc import sco_parcours_dut
@ -61,9 +63,9 @@ MAX_ETUD_IN_DESCR = 20
def formsemestre_etuds_stats(sem, only_primo=False): def formsemestre_etuds_stats(sem, only_primo=False):
"""Récupère liste d'etudiants avec etat et decision.""" """Récupère liste d'etudiants avec etat et decision."""
nt = sco_cache.NotesTableCache.get( formsemestre = FormSemestre.query.get_or_404(sem["formsemestre_id"])
sem["formsemestre_id"] nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
) # > get_table_moyennes_triees, identdict, get_etud_decision_sem, get_etud_etat,
T = nt.get_table_moyennes_triees() T = nt.get_table_moyennes_triees()
# Construit liste d'étudiants du semestre avec leur decision # Construit liste d'étudiants du semestre avec leur decision
etuds = [] etuds = []
@ -400,9 +402,8 @@ def table_suivi_cohorte(
logt("table_suivi_cohorte: start") logt("table_suivi_cohorte: start")
# 1-- Liste des semestres posterieurs dans lesquels ont été les etudiants de sem # 1-- Liste des semestres posterieurs dans lesquels ont été les etudiants de sem
nt = sco_cache.NotesTableCache.get( formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
formsemestre_id nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
) # > get_etudids, get_etud_decision_sem
etudids = nt.get_etudids() etudids = nt.get_etudids()
logt("A: orig etuds set") logt("A: orig etuds set")
@ -456,9 +457,8 @@ def table_suivi_cohorte(
s["members"] = orig_set.intersection(inset) s["members"] = orig_set.intersection(inset)
nb_dipl = 0 # combien de diplomes dans ce semestre ? nb_dipl = 0 # combien de diplomes dans ce semestre ?
if s["semestre_id"] == nt.parcours.NB_SEM: if s["semestre_id"] == nt.parcours.NB_SEM:
nt = sco_cache.NotesTableCache.get( s_formsemestre = FormSemestre.query.get_or_404(s["formsemestre_id"])
s["formsemestre_id"] nt: NotesTableCompat = res_sem.load_formsemestre_results(s_formsemestre)
) # > get_etud_decision_sem
for etudid in s["members"]: for etudid in s["members"]:
dec = nt.get_etud_decision_sem(etudid) dec = nt.get_etud_decision_sem(etudid)
if dec and code_semestre_validant(dec["code"]): if dec and code_semestre_validant(dec["code"]):
@ -905,9 +905,9 @@ def _descr_etud_set(etudids):
def _count_dem_reo(formsemestre_id, etudids): def _count_dem_reo(formsemestre_id, etudids):
"count nb of demissions and reorientation in this etud set" "count nb of demissions and reorientation in this etud set"
nt = sco_cache.NotesTableCache.get( formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
formsemestre_id nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
) # > get_etud_etat, get_etud_decision_sem
dems = set() dems = set()
reos = set() reos = set()
for etudid in etudids: for etudid in etudids:
@ -971,9 +971,9 @@ def get_codeparcoursetud(etud, prefix="", separator=""):
i = len(sems) - 1 i = len(sems) - 1
while i >= 0: while i >= 0:
s = sems[i] # 'sems' est a l'envers, du plus recent au plus ancien s = sems[i] # 'sems' est a l'envers, du plus recent au plus ancien
nt = sco_cache.NotesTableCache.get( s_formsemestre = FormSemestre.query.get_or_404(s["formsemestre_id"])
s["formsemestre_id"] nt: NotesTableCompat = res_sem.load_formsemestre_results(s_formsemestre)
) # > get_etud_etat, get_etud_decision_sem
p.append(_codesem(s, prefix=prefix)) p.append(_codesem(s, prefix=prefix))
# code decisions jury de chaque semestre: # code decisions jury de chaque semestre:
if nt.get_etud_etat(etud["etudid"]) == "D": 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)) # log('tsp_etud_list(%s, bac="%s")' % (formsemestre_id,bac))
sem = sco_formsemestre.get_formsemestre(formsemestre_id) 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() etudids = nt.get_etudids()
etuds = [] etuds = []
bacs = set() bacs = set()
@ -1260,9 +1261,8 @@ def graph_parcours(
nxt = {} nxt = {}
etudid = etud["etudid"] etudid = etud["etudid"]
for s in etud["sems"]: # du plus recent au plus ancien for s in etud["sems"]: # du plus recent au plus ancien
nt = sco_cache.NotesTableCache.get( s_formsemestre = FormSemestre.query.get_or_404(s["formsemestre_id"])
s["formsemestre_id"] nt: NotesTableCompat = res_sem.load_formsemestre_results(s_formsemestre)
) # > get_etud_decision_sem, get_etud_etat
dec = nt.get_etud_decision_sem(etudid) dec = nt.get_etud_decision_sem(etudid)
if nxt: if nxt:
if ( if (

View File

@ -29,15 +29,16 @@
Formulaire revu en juillet 2016 Formulaire revu en juillet 2016
""" """
import sys
import time import time
import datetime
import psycopg2 import psycopg2
import flask import flask
from flask import g, url_for, request from flask import g, url_for, request
from flask_login import current_user 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.sco_utils as scu
from app.scodoc.sco_utils import ModuleType from app.scodoc.sco_utils import ModuleType
import app.scodoc.notesdb as ndb import app.scodoc.notesdb as ndb
@ -49,7 +50,6 @@ from app.scodoc.sco_exceptions import (
ScoGenError, ScoGenError,
ScoValueError, ScoValueError,
) )
from app.scodoc.sco_permissions import Permission
from app.scodoc.TrivialFormulator import TrivialFormulator, TF from app.scodoc.TrivialFormulator import TrivialFormulator, TF
from app.scodoc import html_sco_header, sco_users from app.scodoc import html_sco_header, sco_users
from app.scodoc import htmlutils from app.scodoc import htmlutils
@ -813,8 +813,8 @@ def feuille_saisie_notes(evaluation_id, group_ids=[]):
evaltitre = "évaluation du %s" % E["jour"] evaltitre = "évaluation du %s" % E["jour"]
description = "%s en %s (%s) resp. %s" % ( description = "%s en %s (%s) resp. %s" % (
evaltitre, evaltitre,
Mod["abbrev"], Mod["abbrev"] or "",
Mod["code"], Mod["code"] or "",
mod_responsable["prenomnom"], mod_responsable["prenomnom"],
) )
@ -872,9 +872,8 @@ def has_existing_decision(M, E, etudid):
Si oui, return True Si oui, return True
""" """
formsemestre_id = M["formsemestre_id"] formsemestre_id = M["formsemestre_id"]
nt = sco_cache.NotesTableCache.get( formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
formsemestre_id nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
) # > get_etud_decision_sem, get_etud_decision_ues
if nt.get_etud_decision_sem(etudid): if nt.get_etud_decision_sem(etudid):
return True return True
dec_ues = nt.get_etud_decision_ues(etudid) dec_ues = nt.get_etud_decision_ues(etudid)

View File

@ -42,6 +42,9 @@ sem_set_list()
import flask import flask
from flask import g 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 html_sco_header
from app.scodoc import sco_cache from app.scodoc import sco_cache
from app.scodoc import sco_etape_apogee from app.scodoc import sco_etape_apogee
@ -239,7 +242,8 @@ class SemSet(dict):
self["etuds_without_nip"] = set() # etudids self["etuds_without_nip"] = set() # etudids
self["jury_ok"] = True self["jury_ok"] = True
for sem in self.sems: 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["etuds"] = list(nt.identdict.values())
sem["nips"] = {e["code_nip"] for e in sem["etuds"] if e["code_nip"]} sem["nips"] = {e["code_nip"] for e in sem["etuds"] if e["code_nip"]}
sem["etuds_without_nip"] = { sem["etuds_without_nip"] = {

View File

@ -37,6 +37,9 @@ import http
from flask import g, url_for 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.sco_utils as scu
import app.scodoc.notesdb as ndb import app.scodoc.notesdb as ndb
from app import log 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] etud = sco_etud.get_etud_info(etudid=etudid, filled=True)[0]
R = [] R = []
for sem in etud["sems"]: 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() modimpls = nt.get_modimpls_dict()
for modimpl in modimpls: for modimpl in modimpls:
tags = module_tag_list(module_id=modimpl["module_id"]) tags = module_tag_list(module_id=modimpl["module_id"])

View File

@ -227,7 +227,10 @@ def get_mention(moy):
moy = float(moy) moy = float(moy)
except: except:
return "" return ""
if moy > 0.0:
return NOTES_MENTIONS_LABS[bisect.bisect_right(NOTES_MENTIONS_TH, moy)] return NOTES_MENTIONS_LABS[bisect.bisect_right(NOTES_MENTIONS_TH, moy)]
else:
return ""
class DictDefault(dict): # obsolete, use collections.defaultdict class DictDefault(dict): # obsolete, use collections.defaultdict

View File

@ -97,7 +97,8 @@ section>div:nth-child(1){
.hide_coef .synthese em, .hide_coef .synthese em,
.hide_coef .eval>em, .hide_coef .eval>em,
.hide_date_inscr .dateInscription, .hide_date_inscr .dateInscription,
.hide_ects .ects{ .hide_ects .ects,
.hide_rangs .rang{
display: none; display: none;
} }
@ -158,7 +159,10 @@ section>div:nth-child(1){
text-align: right; text-align: right;
} }
.rang{ .rang{
text-decoration: underline var(--couleurIntense); font-weight: bold;
}
.ue .rang{
font-weight: 400;
} }
.decision{ .decision{
margin: 5px 0; margin: 5px 0;
@ -186,6 +190,9 @@ section>div:nth-child(1){
.synthese h3{ .synthese h3{
background: var(--couleurFondTitresUE); background: var(--couleurFondTitresUE);
} }
.synthese .ue>div{
text-align: right;
}
.synthese em, .synthese em,
.eval em{ .eval em{
opacity: 0.6; opacity: 0.6;
@ -308,6 +315,14 @@ h3{
margin-bottom: 8px; margin-bottom: 8px;
} }
@media screen and (max-width: 700px) {
section{
padding: 16px;
}
.syntheseModule, .eval {
margin: 0;
}
}
/*.absences{ /*.absences{
display: grid; display: grid;
grid-template-columns: auto auto; grid-template-columns: auto auto;

View File

@ -254,6 +254,7 @@ class releveBUT extends HTMLElement {
</h3> </h3>
<div> <div>
<div class=moyenne>Moyenne&nbsp;:&nbsp;${dataUE.moyenne?.value || "-"}</div> <div class=moyenne>Moyenne&nbsp;:&nbsp;${dataUE.moyenne?.value || "-"}</div>
<div class=rang>Rang&nbsp;:&nbsp;${dataUE.moyenne?.rang}&nbsp;/&nbsp;${dataUE.moyenne?.total}</div>
<div class=info> <div class=info>
Bonus&nbsp;:&nbsp;${dataUE.bonus || 0}&nbsp;- Bonus&nbsp;:&nbsp;${dataUE.bonus || 0}&nbsp;-
Malus&nbsp;:&nbsp;${dataUE.malus || 0} Malus&nbsp;:&nbsp;${dataUE.malus || 0}
@ -299,7 +300,7 @@ class releveBUT extends HTMLElement {
Object.values(module.evaluations).forEach((evaluation) => { Object.values(module.evaluations).forEach((evaluation) => {
output += ` output += `
<div class=syntheseModule> <div class=syntheseModule>
<div>${module.titre} - ${evaluation.description}</div> <div>${module.titre} - ${evaluation.description || "Note"}</div>
<div> <div>
${evaluation.note.value ?? "-"} ${evaluation.note.value ?? "-"}
<em>Coef.&nbsp;${evaluation.coef}</em> <em>Coef.&nbsp;${evaluation.coef}</em>

View File

@ -6,10 +6,23 @@
<h1>Charger un référentiel de compétences</h1> <h1>Charger un référentiel de compétences</h1>
<div class="row"> <div class="row">
<div class="col-md-4"> <div class="col-md-5">
{{ wtf.quick_form(form) }} {{ wtf.quick_form(form) }}
</div> </div>
</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 %} {% endblock %}

View File

@ -35,6 +35,11 @@
<b>{{ue.acronyme}}</b> <a class="discretelink" href="{{ <b>{{ue.acronyme}}</b> <a class="discretelink" href="{{
url_for('notes.ue_infos', scodoc_dept=g.scodoc_dept, ue_id=ue.id)}}" url_for('notes.ue_infos', scodoc_dept=g.scodoc_dept, ue_id=ue.id)}}"
>{{ue.titre}}</a> >{{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> </span>
{% if editable and not ue.is_locked() %} {% if editable and not ue.is_locked() %}

View File

@ -12,7 +12,7 @@
<li>{{ue.acronyme}} <li>{{ue.acronyme}}
<ul> <ul>
<li>Semestre: {{ue.semestre_idx}}</li> <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>Type: {{ue.type}}</li>
<li>Externe: {{ "oui" if ue.is_external else "non" }}</li> <li>Externe: {{ "oui" if ue.is_external else "non" }}</li>
<li>Code Apogée: {{ue.code_apogee}}</li> <li>Code Apogée: {{ue.code_apogee}}</li>

View File

@ -60,22 +60,22 @@ from flask import g, request
from flask import url_for from flask import url_for
from flask_login import current_user 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 ( from app.decorators import (
scodoc, scodoc,
scodoc7func, scodoc7func,
permission_required, permission_required,
admin_required,
login_required,
permission_required_compat_scodoc7, 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.views import absences_bp as bp
# --------------- # ---------------
from app.models.absences import BilletAbsence
from app.scodoc import sco_utils as scu from app.scodoc import sco_utils as scu
from app.scodoc import notesdb as ndb from app.scodoc import notesdb as ndb
from app import log
from app.scodoc.scolog import logdb from app.scodoc.scolog import logdb
from app.scodoc.sco_permissions import Permission from app.scodoc.sco_permissions import Permission
from app.scodoc.sco_exceptions import ScoValueError, APIInvalidParams 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.gen_tables import GenTable
from app.scodoc import html_sco_header from app.scodoc import html_sco_header
from app.scodoc import sco_abs from app.scodoc import sco_abs
from app.scodoc import sco_abs_notification
from app.scodoc import sco_abs_views 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_etud
from app.scodoc import sco_excel
from app.scodoc import sco_find_etud from app.scodoc import sco_find_etud
from app.scodoc import sco_formsemestre from app.scodoc import sco_formsemestre
from app.scodoc import sco_groups
from app.scodoc import sco_groups_view from app.scodoc import sco_groups_view
from app.scodoc import sco_moduleimpl from app.scodoc import sco_moduleimpl
from app.scodoc import sco_preferences from app.scodoc import sco_preferences
@ -372,8 +367,12 @@ def SignaleAbsenceGrHebdo(
else: else:
# Si aucun etudiant n'est inscrit au module choisi... # Si aucun etudiant n'est inscrit au module choisi...
moduleimpl_id = None 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 # calcule dates jours de cette semaine
# liste de dates iso "yyyy-mm-dd" # liste de dates iso "yyyy-mm-dd"
@ -444,8 +443,9 @@ def SignaleAbsenceGrHebdo(
% { % {
"modimpl_id": modimpl["moduleimpl_id"], "modimpl_id": modimpl["moduleimpl_id"],
"modname": modimpl["module"]["code"] "modname": modimpl["module"]["code"]
or ""
+ " " + " "
+ (modimpl["module"]["abbrev"] or modimpl["module"]["titre"]), + (modimpl["module"]["abbrev"] or modimpl["module"]["titre"] or ""),
"sel": sel, "sel": sel,
} }
) )
@ -493,10 +493,11 @@ def SignaleAbsenceGrSemestre(
+ html_sco_header.sco_footer() + html_sco_header.sco_footer()
) )
formsemestre_id = groups_infos.formsemestre_id 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( require_module = sco_preferences.get_preference(
"abs_require_module", formsemestre_id "abs_require_module", formsemestre_id
) )
sem = sco_formsemestre.do_formsemestre_list({"formsemestre_id": formsemestre_id})[0]
etuds = [ etuds = [
sco_etud.get_etud_info(etudid=m["etudid"], filled=True)[0] sco_etud.get_etud_info(etudid=m["etudid"], filled=True)[0]
for m in groups_infos.members for m in groups_infos.members
@ -526,7 +527,7 @@ def SignaleAbsenceGrSemestre(
base_url = base_url_noweeks + "&nbweeks=%s" % nbweeks # sans le moduleimpl_id base_url = base_url_noweeks + "&nbweeks=%s" % nbweeks # sans le moduleimpl_id
if etuds: if etuds:
nt = sco_cache.NotesTableCache.get(formsemestre_id) nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
work_saturday = sco_abs.is_work_saturday() work_saturday = sco_abs.is_work_saturday()
jourdebut = sco_abs.ddmmyyyy(datedebut, work_saturday=work_saturday) jourdebut = sco_abs.ddmmyyyy(datedebut, work_saturday=work_saturday)
@ -611,6 +612,7 @@ def SignaleAbsenceGrSemestre(
% { % {
"modimpl_id": modimpl["moduleimpl_id"], "modimpl_id": modimpl["moduleimpl_id"],
"modname": modimpl["module"]["code"] "modname": modimpl["module"]["code"]
or ""
+ " " + " "
+ (modimpl["module"]["abbrev"] or modimpl["module"]["titre"]), + (modimpl["module"]["abbrev"] or modimpl["module"]["titre"]),
"sel": sel, "sel": sel,
@ -729,12 +731,13 @@ def _gen_form_saisie_groupe(
# UE capitalisee dans semestre courant ? # UE capitalisee dans semestre courant ?
cap = [] cap = []
if etud["cursem"]: if etud["cursem"]:
nt = sco_cache.NotesTableCache.get( formsemestre = FormSemestre.query.get_or_404(
etud["cursem"]["formsemestre_id"] 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(): for ue in nt.get_ues_stat_dict():
status = nt.get_etud_ue_status(etudid, ue["ue_id"]) ue_status = nt.get_etud_ue_status(etudid, ue["ue_id"])
if status["is_capitalized"]: if ue_status and ue_status["is_capitalized"]:
cap.append(ue["acronyme"]) cap.append(ue["acronyme"])
if cap: if cap:
capstr = ' <span class="capstr">(%s cap.)</span>' % ", ".join(cap) capstr = ' <span class="capstr">(%s cap.)</span>' % ", ".join(cap)

View File

@ -39,6 +39,9 @@ from flask import flash, jsonify, render_template, url_for
from flask import current_app, g, request from flask import current_app, g, request
from flask_login import current_user from flask_login import current_user
from werkzeug.utils import redirect 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 FormSemestre
from app.models.formsemestre import FormSemestreUEComputationExpr from app.models.formsemestre import FormSemestreUEComputationExpr
from app.models.ues import UniteEns from app.models.ues import UniteEns
@ -1319,7 +1322,7 @@ def formsemestre_enseignants_list(formsemestre_id, format="html"):
# description textuelle des modules # description textuelle des modules
for ens in sem_ens: for ens in sem_ens:
sem_ens[ens]["descr_mods"] = ", ".join( 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: # 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, S'il s'agit d'un semestre extérieur et qu'il n'y a plus d'inscrit,
le semestre sera supprimé. 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 # -- check lock
if not sem["etat"]: if not formsemestre.etat:
raise ScoValueError("desinscription impossible: semestre verrouille") raise ScoValueError("desinscription impossible: semestre verrouille")
# -- Si décisions de jury, désinscription interdite # -- 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): if nt.etud_has_decision(etudid):
raise ScoValueError( raise ScoValueError(
"""Désinscription impossible: l'étudiant a une décision de jury """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: if not dialog_confirmed:
etud = sco_etud.get_etud_info(etudid=etudid, filled=True)[0] etud = sco_etud.get_etud_info(etudid=etudid, filled=True)[0]
if sem["modalite"] != "EXT": if formsemestre.modalite != "EXT":
msg_ext = """ msg_ext = """
<p>%s sera désinscrit de tous les modules du semestre %s (%s - %s).</p> <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> ! <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" "envoi a chaque etudiant (inscrit et ayant un mail) son bulletin"
prefer_mail_perso = int(prefer_mail_perso) 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() etudids = nt.get_etudids()
# #
if not sco_bulletins.can_send_bulletin_by_mail(formsemestre_id): if not sco_bulletins.can_send_bulletin_by_mail(formsemestre_id):
@ -1920,7 +1925,7 @@ def formsemestre_bulletins_mailetuds(
nb_send = 0 nb_send = 0
for etudid in etudids: for etudid in etudids:
h, _ = sco_bulletins.do_formsemestre_bulletinetud( h, _ = sco_bulletins.do_formsemestre_bulletinetud(
formsemestre_id, formsemestre,
etudid, etudid,
version=version, version=version,
prefer_mail_perso=prefer_mail_perso, prefer_mail_perso=prefer_mail_perso,
@ -2247,9 +2252,10 @@ def formsemestre_validation_suppress_etud(
dest_url=scu.ScoURL(), dest_url=scu.ScoURL(),
) )
if not dialog_confirmed: if not dialog_confirmed:
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
etud = sco_etud.get_etud_info(etudid=etudid, filled=True)[0] 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) decision_jury = nt.get_etud_decision_sem(etudid)
if decision_jury: if decision_jury:
existing = ( existing = (

View File

@ -3,9 +3,11 @@ PN / Référentiel de compétences
Emmanuel Viennet, 2021 Emmanuel Viennet, 2021
""" """
from pathlib import Path
import re
from flask import url_for, flash from flask import jsonify, flash, url_for
from flask import jsonify from flask import Markup
from flask import current_app, g, request from flask import current_app, g, request
from flask.templating import render_template from flask.templating import render_template
from flask_login import current_user from flask_login import current_user
@ -15,7 +17,7 @@ from werkzeug.utils import secure_filename
from config import Config from config import Config
from app import db from app import db
from app import models from app import log
from app.decorators import scodoc, permission_required from app.decorators import scodoc, permission_required
from app.models import Formation 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.import_refcomp import orebut_import_refcomp
from app.but.forms.refcomp_forms import FormationRefCompForm, RefCompLoadForm from app.but.forms.refcomp_forms import FormationRefCompForm, RefCompLoadForm
from app.scodoc.gen_tables import GenTable from app.scodoc.gen_tables import GenTable
from app.scodoc import html_sidebar
from app.scodoc import sco_utils as scu 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.scodoc.sco_permissions import Permission
from app.views import notes_bp as bp from app.views import notes_bp as bp
from app.views import ScoData from app.views import ScoData
@ -171,14 +172,61 @@ def refcomp_load(formation_id=None):
formation = Formation.query.get_or_404(formation_id) formation = Formation.query.get_or_404(formation_id)
else: else:
formation = None 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 = 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.validate_on_submit():
if form.upload.data:
f = form.upload.data f = form.upload.data
filename = secure_filename(f.filename) 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: try:
xml_data = f.read() xml_data = f.read()
_ = orebut_import_refcomp( _ = 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: except TypeError as exc:
raise ScoFormatError( raise ScoFormatError(
@ -187,6 +235,11 @@ def refcomp_load(formation_id=None):
except ScoFormatError: except ScoFormatError:
raise raise
flash(
Markup(f"Référentiel <tt>{Path(filename).name}</tt> chargé."),
category="info",
)
if formation is not None: if formation is not None:
return redirect( return redirect(
url_for( url_for(

View File

@ -35,7 +35,15 @@ import io
import re import re
import flask 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 from flask import request
import flask_login import flask_login
from flask_login.utils import login_required, current_user 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 departements
from app.models import FormSemestre, FormSemestreInscription from app.models import FormSemestre, FormSemestreInscription
from app.models import ScoDocSiteConfig from app.models import ScoDocSiteConfig
from app.models import UniteEns
from app.scodoc import sco_codes_parcours, sco_logos from app.scodoc import sco_codes_parcours, sco_logos
from app.scodoc import sco_find_etud from app.scodoc import sco_find_etud
from app.scodoc import sco_utils as scu from app.scodoc import sco_utils as scu
@ -264,11 +273,15 @@ def get_bonus_description(bonus_name: str):
bonus_name = "" bonus_name = ""
bonus_class = ScoDocSiteConfig.get_bonus_sport_class_from_name(bonus_name) bonus_class = ScoDocSiteConfig.get_bonus_sport_class_from_name(bonus_name)
text = bonus_class.__doc__ text = bonus_class.__doc__
if text:
fields = re.split(r"\n\n", text, maxsplit=1) fields = re.split(r"\n\n", text, maxsplit=1)
if len(fields) > 1: if len(fields) > 1:
first_line, text = fields first_line, text = fields
else: else:
first_line, text = "", fields[0] 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> return f"""<div class="bonus_description_head">{first_line}</div>
<div>{text}</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 # essais
# @bp.route("/testlog") # @bp.route("/testlog")
# def testlog(): # def testlog():

View File

@ -35,7 +35,7 @@ import time
import flask import flask
from flask import jsonify, url_for, flash, render_template, make_response 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_login import current_user
from flask_wtf import FlaskForm from flask_wtf import FlaskForm
from flask_wtf.file import FileField, FileAllowed from flask_wtf.file import FileField, FileAllowed

View 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 ###

View File

@ -1,13 +1,14 @@
# -*- mode: python -*- # -*- mode: python -*-
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
SCOVERSION = "9.1.50a" SCOVERSION = "9.2a-57"
SCONAME = "ScoDoc" SCONAME = "ScoDoc"
SCONEWS = """ SCONEWS = """
<h4>Année 2021</h4> <h4>Année 2021</h4>
<ul> <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.1: gestion des formations par compétences, type BUT.</li>
<li>ScoDoc 9.0: nouvelle architecture logicielle (Flask/Python3/Debian 11)</li> <li>ScoDoc 9.0: nouvelle architecture logicielle (Flask/Python3/Debian 11)</li>
<li>Version mobile (en test)</li> <li>Version mobile (en test)</li>

View File

@ -14,6 +14,8 @@ import click
import flask import flask
from flask.cli import with_appcontext from flask.cli import with_appcontext
from flask.templating import render_template from flask.templating import render_template
import psycopg2
import sqlalchemy
from app import create_app, cli, db from app import create_app, cli, db
from app import initialize_scodoc_database 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" "Create a new user"
r = Role.get_named_role(role) r = Role.get_named_role(role)
if not r: 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 return 1
u = User.query.filter_by(user_name=username).first() u = User.query.filter_by(user_name=username).first()
if u: 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 return 2
if dept == "@all": if dept == "@all":
dept = None dept = None
@ -145,11 +147,26 @@ def user_create(username, role, dept, nom=None, prenom=None): # user-create
u.add_role(r, dept) u.add_role(r, dept)
db.session.add(u) db.session.add(u)
db.session.commit() db.session.commit()
click.echo( click.echo(f"created user, login: {u.user_name}, with role {r} in dept. {dept}")
"created user, login: {u.user_name}, with role {r} in dept. {dept}".format(
u=u, r=r, 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() @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("xlsfile", type=click.File("rb"))
@click.argument("zipfile", type=click.File("rb")) @click.argument("zipfile", type=click.File("rb"))
def photos_import_files(formsemestre_id: int, xlsfile: str, zipfile: str): 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 import app as mapp
from app.scodoc import sco_trombino, sco_photos from app.scodoc import sco_trombino, sco_photos
from app.scodoc import notesdb as ndb from app.scodoc import notesdb as ndb
@ -485,6 +503,7 @@ def recursive_help(cmd, parent=None):
@app.cli.command() @app.cli.command()
def dumphelp(): def dumphelp():
"""Génère la page d'aide complète pour la doc."""
recursive_help(app.cli) recursive_help(app.cli)

View File

@ -13,6 +13,9 @@ from flask import current_app, g
import app import app
from app import db 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_cache
from app.scodoc import sco_evaluations from app.scodoc import sco_evaluations
from app.scodoc import sco_evaluation_db 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 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.""" """Test construction et cache de NotesTable."""
app.set_sco_dept(DEPT) app.set_sco_dept(DEPT)
assert g.scodoc_dept == DEPT assert g.scodoc_dept == DEPT
@ -35,7 +38,8 @@ def test_notes_table(test_client):
assert len(sems) assert len(sems)
sem = sems[0] sem = sems[0]
formsemestre_id = sem["formsemestre_id"] 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 nt
assert sco_cache.NotesTableCache.get(formsemestre_id, compute=False) assert sco_cache.NotesTableCache.get(formsemestre_id, compute=False)
sco_cache.invalidate_formsemestre(formsemestre_id) sco_cache.invalidate_formsemestre(formsemestre_id)

View File

@ -10,6 +10,9 @@ from tests.unit import sco_fake_gen
from flask import g from flask import g
import app 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_bulletins, sco_formsemestre
from app.scodoc import sco_cache from app.scodoc import sco_cache
from app.scodoc import sco_formsemestre_inscriptions from app.scodoc import sco_formsemestre_inscriptions
@ -33,7 +36,8 @@ def check_nt(
(peut changer dans le futur, ne pas utiliser hors ScoDoc !) (peut changer dans le futur, ne pas utiliser hors ScoDoc !)
ne vérifie que les valeurs expected non False 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) mod_moy = nt.get_etud_mod_moy(moduleimpl_id, etudid)
if expected_moy_ue is not False: if expected_moy_ue is not False:
ue_status = nt.get_etud_ue_status(etudid, ue_id) ue_status = nt.get_etud_ue_status(etudid, ue_id)
@ -262,7 +266,8 @@ def test_notes_modules(test_client):
formsemestre_id=formsemestre_id, formsemestre_id=formsemestre_id,
) )
_, _, _ = G.create_note(evaluation=e1, etud=etud, note=12.5) _, _, _ = 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) 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_missing"] == 1 # 1 même si etud non inscrit à l'autre module
assert ue_status["nb_notes"] == 1 assert ue_status["nb_notes"] == 1
@ -276,7 +281,8 @@ def test_notes_modules(test_client):
{"etudid": etuds[1]["etudid"], "moduleimpl_id": moduleimpl_id2}, {"etudid": etuds[1]["etudid"], "moduleimpl_id": moduleimpl_id2},
formsemestre_id=formsemestre_id, 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) 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_missing"] == 1 # mi2 n'a pas encore de note
assert ue_status["nb_notes"] == 1 assert ue_status["nb_notes"] == 1
@ -288,7 +294,8 @@ def test_notes_modules(test_client):
coefficient=1.0, coefficient=1.0,
) )
_, _, _ = G.create_note(evaluation=e_m2, etud=etud, note=19.5) _, _, _ = 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) ue_status = nt.get_etud_ue_status(etudid, ue_id)
assert ue_status["nb_missing"] == 1 # manque une note assert ue_status["nb_missing"] == 1 # manque une note
assert ue_status["nb_notes"] == 1 assert ue_status["nb_notes"] == 1

View File

@ -19,7 +19,10 @@ from config import TestConfig
from tests.unit import sco_fake_gen from tests.unit import sco_fake_gen
import app 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
from app.scodoc import sco_abs_views from app.scodoc import sco_abs_views
from app.scodoc import sco_bulletins from app.scodoc import sco_bulletins
@ -208,7 +211,8 @@ def run_sco_basic(verbose=False):
redirect=False, redirect=False,
) )
# Vérifie que toutes les UE des étudiants notés ont été acquises: # 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]: for etud in etuds[:5]:
dec_ues = nt.get_etud_decision_ues(etud["etudid"]) dec_ues = nt.get_etud_decision_ues(etud["etudid"])
for ue_id in dec_ues: for ue_id in dec_ues:

View File

@ -483,7 +483,7 @@ SCO7_TABLES_ORDONNEES = [
), # (relation) avait un id modules_enseignants_id ), # (relation) avait un id modules_enseignants_id
("partition", "partition_id"), ("partition", "partition_id"),
("identite", "etudid"), ("identite", "etudid"),
("entreprises", "entreprise_id"), # ("entreprises", "entreprise_id"),
("notes_evaluation", "evaluation_id"), ("notes_evaluation", "evaluation_id"),
("group_descr", "group_id"), ("group_descr", "group_id"),
("group_membership", "group_membership_id"), # (relation, qui avait un id) ("group_membership", "group_membership_id"), # (relation, qui avait un id)
@ -498,8 +498,8 @@ SCO7_TABLES_ORDONNEES = [
("scolog", ""), ("scolog", ""),
("etud_annotations", "id"), ("etud_annotations", "id"),
("billet_absence", "billet_id"), ("billet_absence", "billet_id"),
("entreprise_correspondant", "entreprise_corresp_id"), # ("entreprise_correspondant", "entreprise_corresp_id"),
("entreprise_contact", "entreprise_contact_id"), # ("entreprise_contact", "entreprise_contact_id"),
("absences_notifications", ""), ("absences_notifications", ""),
# ("notes_form_modalites", "form_modalite_id"), : déjà initialisées # ("notes_form_modalites", "form_modalite_id"), : déjà initialisées
("notes_appreciations", "id"), ("notes_appreciations", "id"),