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)
- 9.1 (master) reproduit l'ensemble des fonctions de ScoDoc 7 (donc pas de BUT), sauf:
- 9.1.5x (master) reproduit l'ensemble des fonctions de ScoDoc 7 (donc pas de BUT), sauf:
- ancien module "Entreprises" (obsolète) et ajoute la gestion du BUT.
- 9.2 (branche refactor_nt) est la version de développement.
- 9.2 (branche dev92) est la version de développement.
### Lignes de commandes

View File

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

View File

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

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("bonus_sport_culture", value=str(bonus)))
# Liste les UE / modules /evals
for ue in results.ues:
for ue in results.ues: # avec bonus
rang_ue = 0 # XXX TODO rang de l'étudiant dans cette UE
nb_inscrits_ue = (
nb_inscrits # approx: compliqué de définir le "nb d'inscrit à une UE"
@ -161,25 +161,31 @@ def bulletin_but_xml_compat(
doc.append(x_ue)
if ue.type != sco_codes_parcours.UE_SPORT:
v = results.etud_moy_ue[ue.id][etud.id]
vmin = results.etud_moy_ue[ue.id].min()
vmax = results.etud_moy_ue[ue.id].max()
else:
v = 0 # XXX TODO valeur bonus sport pour cet étudiant
v = results.bonus or 0.0
vmin = vmax = 0.0
x_ue.append(
Element(
"note",
value=scu.fmt_note(v),
min=scu.fmt_note(results.etud_moy_ue[ue.id].min()),
max=scu.fmt_note(results.etud_moy_ue[ue.id].max()),
min=scu.fmt_note(vmin),
max=scu.fmt_note(vmax),
)
)
x_ue.append(Element("ects", value=str(ue.ects if ue.ects else 0)))
x_ue.append(Element("rang", value=str(rang_ue)))
x_ue.append(Element("effectif", value=str(nb_inscrits_ue)))
# Liste les modules rattachés à cette UE
for modimpl in results.modimpls:
for modimpl in results.formsemestre.modimpls:
# Liste ici uniquement les modules rattachés à cette UE
if modimpl.module.ue.id == ue.id:
# mod_moy = scu.fmt_note(results.etud_moy_ue[ue.id][etud.id])
coef = results.modimpl_coefs_df[modimpl.id][ue.id]
try:
coef = results.modimpl_coefs_df[modimpl.id][ue.id]
except KeyError:
coef = 0.0
x_mod = Element(
"module",
id=str(modimpl.id),
@ -192,40 +198,46 @@ def bulletin_but_xml_compat(
modimpl.module.code_apogee or ""
),
)
# XXX TODO rangs et effectifs
# --- notes de chaque eval:
if version != "short":
for e in modimpl.evaluations:
if e.visibulletin or version == "long":
x_eval = Element(
"evaluation",
jour=e.jour.isoformat() if e.jour else "",
heure_debut=e.heure_debut.isoformat()
if e.heure_debut
else "",
heure_fin=e.heure_fin.isoformat()
if e.heure_debut
else "",
coefficient=str(e.coefficient),
# pas les poids en XML compat
evaluation_type=str(e.evaluation_type),
description=scu.quote_xml_attr(e.description),
# notes envoyées sur 20, ceci juste pour garder trace:
note_max_origin=str(e.note_max),
)
x_mod.append(x_eval)
x_eval.append(
Element(
"note",
value=scu.fmt_note(
results.modimpls_results[
e.moduleimpl_id
].evals_notes[e.id][etud.id],
note_max=e.note_max,
),
# XXX TODO rangs et effectifs
# --- notes de chaque eval:
if version != "short":
for e in modimpl.evaluations:
if e.visibulletin or version == "long":
x_eval = Element(
"evaluation",
jour=e.jour.isoformat() if e.jour else "",
heure_debut=e.heure_debut.isoformat()
if e.heure_debut
else "",
heure_fin=e.heure_fin.isoformat()
if e.heure_debut
else "",
coefficient=str(e.coefficient),
# pas les poids en XML compat
evaluation_type=str(e.evaluation_type),
description=scu.quote_xml_attr(e.description),
# notes envoyées sur 20, ceci juste pour garder trace:
note_max_origin=str(e.note_max),
)
)
# XXX TODO: Evaluations incomplètes ou futures: XXX
x_mod.append(x_eval)
try:
x_eval.append(
Element(
"note",
value=scu.fmt_note(
results.modimpls_results[
e.moduleimpl_id
].evals_notes[e.id][etud.id],
note_max=e.note_max,
),
)
)
except KeyError:
x_eval.append(
Element("note", value="", note_max="")
)
# XXX TODO: Evaluations incomplètes ou futures: XXX
# XXX TODO UE capitalisee (listee seulement si meilleure que l'UE courante)
# --- Absences

View File

@ -19,10 +19,12 @@ class FormationRefCompForm(FlaskForm):
class RefCompLoadForm(FlaskForm):
referentiel_standard = SelectField(
"Choisir un référentiel de compétences officiel BUT"
)
upload = FileField(
label="Sélectionner un fichier XML Orébut",
label="Ou bien sélectionner un fichier XML au format Orébut",
validators=[
FileRequired(),
FileAllowed(
[
"xml",
@ -33,3 +35,13 @@ class RefCompLoadForm(FlaskForm):
)
submit = SubmitField("Valider")
cancel = SubmitField("Annuler")
def validate(self):
if not super().validate():
return False
if (self.referentiel_standard.data == "0") == (not self.upload.data):
self.referentiel_standard.errors.append(
"Choisir soit un référentiel, soit un fichier xml"
)
return False
return True

View File

@ -6,6 +6,8 @@
from xml.etree import ElementTree
from typing import TextIO
import sqlalchemy
from app import db
from app.models.but_refcomp import (
@ -19,7 +21,7 @@ from app.models.but_refcomp import (
ApcAnneeParcours,
ApcParcoursNiveauCompetence,
)
from app.scodoc.sco_exceptions import ScoFormatError
from app.scodoc.sco_exceptions import ScoFormatError, ScoValueError
def orebut_import_refcomp(xml_data: str, dept_id: int, orig_filename=None):
@ -27,6 +29,16 @@ def orebut_import_refcomp(xml_data: str, dept_id: int, orig_filename=None):
peut lever TypeError ou ScoFormatError
Résultat: instance de ApcReferentielCompetences
"""
# Vérifie que le même fichier n'a pas déjà été chargé:
if ApcReferentielCompetences.query.filter_by(
scodoc_orig_filename=orig_filename, dept_id=dept_id
).count():
raise ScoValueError(
f"""Un référentiel a déjà été chargé d'un fichier de même nom.
({orig_filename})
Supprimez-le ou changer le nom du fichier."""
)
try:
root = ElementTree.XML(xml_data)
except ElementTree.ParseError as exc:
@ -42,7 +54,16 @@ def orebut_import_refcomp(xml_data: str, dept_id: int, orig_filename=None):
if not competences:
raise ScoFormatError("élément 'competences' manquant")
for competence in competences.findall("competence"):
c = ApcCompetence(**ApcCompetence.attr_from_xml(competence.attrib))
try:
c = ApcCompetence(**ApcCompetence.attr_from_xml(competence.attrib))
db.session.flush()
except sqlalchemy.exc.IntegrityError:
# ne devrait plus se produire car pas d'unicité de l'id: donc inutile
db.session.rollback()
raise ScoValueError(
f"""Un référentiel a déjà été chargé avec les mêmes compétences ! ({competence.attrib["id"]})
"""
)
ref.competences.append(c)
# --- SITUATIONS
situations = competence.find("situations")

View File

@ -21,14 +21,17 @@ class StatsMoyenne:
Les valeurs NAN ou non numériques sont toujours enlevées.
Si vals is None, renvoie des zéros (utilisé pour UE bonus)
"""
if vals is None or len(vals) == 0:
try:
if vals is None or len(vals) == 0 or np.isnan(vals).all():
self.moy = self.min = self.max = self.size = self.nb_vals = 0
else:
self.moy = np.nanmean(vals)
self.min = np.nanmin(vals)
self.max = np.nanmax(vals)
self.size = len(vals)
self.nb_vals = self.size - np.count_nonzero(np.isnan(vals))
except TypeError: # que des NaN dans un array d'objets, ou ce genre de choses exotiques...
self.moy = self.min = self.max = self.size = self.nb_vals = 0
else:
self.moy = np.nanmean(vals)
self.min = np.nanmin(vals)
self.max = np.nanmax(vals)
self.size = len(vals)
self.nb_vals = self.size - np.count_nonzero(np.isnan(vals))
def to_dict(self):
"Tous les attributs dans un dict"

View File

@ -87,6 +87,8 @@ class BonusSport:
for m in formsemestre.modimpls_sorted
]
)
if not len(modimpl_mask):
modimpl_mask = np.s_[:] # il n'y a rien, on prend tout donc rien
self.modimpls_spo = [
modimpl
for i, modimpl in enumerate(formsemestre.modimpls_sorted)
@ -134,9 +136,12 @@ class BonusSport:
modimpl_inscr_spo, sem_modimpl_moys_no_nan, 0.0
)
modimpl_coefs_spo = modimpl_coefs_spo.T
modimpl_coefs_etuds = np.where(
modimpl_inscr_spo, np.stack([modimpl_coefs_spo] * nb_etuds), 0.0
)
if nb_etuds == 0:
modimpl_coefs_etuds = modimpl_inscr_spo # vide
else:
modimpl_coefs_etuds = np.where(
modimpl_inscr_spo, np.stack([modimpl_coefs_spo] * nb_etuds), 0.0
)
# Annule les coefs des modules NaN (nb_etuds x nb_mod_sport)
modimpl_coefs_etuds_no_nan = np.where(
np.isnan(sem_modimpl_moys_spo), 0.0, modimpl_coefs_etuds
@ -198,6 +203,9 @@ class BonusSportAdditif(BonusSport):
En APC: ndarray (nb_etuds, nb_mod_sport, nb_ues_non_bonus)
modimpl_coefs_etuds_no_nan:
"""
if 0 in sem_modimpl_moys_inscrits.shape:
# pas d'étudiants ou pas d'UE ou pas de module...
return
bonus_moy_arr = np.sum(
np.where(
sem_modimpl_moys_inscrits > self.seuil_moy_gen,
@ -249,6 +257,9 @@ class BonusSportMultiplicatif(BonusSport):
# bonus = m_0 (a - 1)
def compute_bonus(self, sem_modimpl_moys_inscrits, modimpl_coefs_etuds_no_nan):
"""calcul du bonus"""
if 0 in sem_modimpl_moys_inscrits.shape:
# pas d'étudiants ou pas d'UE ou pas de module...
return
# Calcule moyenne pondérée des notes de sport:
notes = np.sum(
sem_modimpl_moys_inscrits * modimpl_coefs_etuds_no_nan, axis=1

View File

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

View File

@ -136,8 +136,13 @@ def df_load_modimpl_coefs(
)
for mod_coef in mod_coefs:
modimpl_coefs_df[mod2impl[mod_coef.module_id]][mod_coef.ue_id] = mod_coef.coef
try:
modimpl_coefs_df[mod2impl[mod_coef.module_id]][
mod_coef.ue_id
] = mod_coef.coef
except IndexError:
# il peut y avoir en base des coefs sur des modules ou UE qui ont depuis été retirés de la formation
pass
# Initialisation des poids non fixés:
# 0 pour modules normaux, 1. pour bonus (car par défaut, on veut qu'un bonus agisse
# sur toutes les UE)
@ -229,12 +234,12 @@ def compute_ue_moys_apc(
modimpl_inscr_df: matrice d'inscription du semestre (etud x modimpl)
modimpl_coefs_df: matrice coefficients (UE x modimpl), sans UEs bonus sport
Résultat: DataFrame columns UE (sans sport), rows etudid
Résultat: DataFrame columns UE (sans bonus), rows etudid
"""
nb_etuds, nb_modules, nb_ues_no_bonus = sem_cube.shape
nb_ues_tot = len(ues)
assert len(modimpls) == nb_modules
if nb_modules == 0 or nb_etuds == 0:
if nb_modules == 0 or nb_etuds == 0 or nb_ues_no_bonus == 0:
return pd.DataFrame(
index=modimpl_inscr_df.index, columns=modimpl_coefs_df.index
)
@ -310,6 +315,17 @@ def compute_ue_moys_classic(
les coefficients effectifs de chaque UE pour chaque étudiant
(sommes de coefs de modules pris en compte)
"""
if (not len(modimpl_mask)) or (
sem_matrix.shape[0] == 0
): # aucun module ou aucun étudiant
# etud_moy_gen_s, etud_moy_ue_df, etud_coef_ue_df
return (
pd.Series(
[0.0] * len(modimpl_inscr_df.index), index=modimpl_inscr_df.index
),
pd.DataFrame(columns=[ue.id for ue in ues], index=modimpl_inscr_df.index),
pd.DataFrame(columns=[ue.id for ue in ues], index=modimpl_inscr_df.index),
)
# Restreint aux modules sélectionnés:
sem_matrix = sem_matrix[:, modimpl_mask]
modimpl_inscr = modimpl_inscr_df.values[:, modimpl_mask]
@ -391,8 +407,9 @@ def compute_malus(
) -> pd.DataFrame:
"""Calcul le malus sur les UE
Dans chaque UE, on peut avoir un ou plusieurs modules de MALUS.
Leurs notes sont positives ou négatives. leur somme sera _soustraite_ à la moyenne
de chaque UE.
Leurs notes sont positives ou négatives.
La somme des notes de malus somme est _soustraite_ à la moyenne de chaque UE.
Arguments:
- sem_modimpl_moys :
notes moyennes aux modules (tous les étuds x tous les modimpls)
@ -415,8 +432,9 @@ def compute_malus(
for m in formsemestre.modimpls_sorted
]
)
malus_moys = sem_modimpl_moys[:, modimpl_mask].sum(axis=1)
malus[ue.id] = malus_moys
if len(modimpl_mask):
malus_moys = sem_modimpl_moys[:, modimpl_mask].sum(axis=1)
malus[ue.id] = malus_moys
malus.fillna(0.0, inplace=True)
return malus

View File

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

View File

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

View File

@ -4,11 +4,12 @@
# See LICENSE
##############################################################################
from collections import defaultdict, Counter
from collections import Counter
from functools import cached_property
import numpy as np
import pandas as pd
from app import log
from app.comp.aux_stats import StatsMoyenne
from app.comp import moy_sem
from app.comp.res_cache import ResultatsCache
@ -19,8 +20,7 @@ from app.models import FormSemestreUECoef
from app.models.ues import UniteEns
from app.scodoc import sco_utils as scu
from app.scodoc.sco_cache import ResultatsSemestreCache
from app.scodoc.sco_codes_parcours import UE_SPORT, ATT, DEF
from app.scodoc.sco_exceptions import ScoValueError
from app.scodoc.sco_codes_parcours import UE_SPORT, DEF
# Il faut bien distinguer
# - ce qui est caché de façon persistente (via redis):
@ -51,6 +51,7 @@ class ResultatsSemestre(ResultatsCache):
"etud_moy_ue: DataFrame columns UE, rows etudid"
self.etud_moy_gen = {}
self.etud_moy_gen_ranks = {}
self.etud_moy_gen_ranks_int = {}
self.modimpls_results: ModuleImplResults = None
"Résultats de chaque modimpl: dict { modimpl.id : ModuleImplResults(Classique ou BUT) }"
self.etud_coef_ue_df = None
@ -183,7 +184,7 @@ class ResultatsSemestre(ResultatsCache):
sum_coefs_ue = 0.0
for ue in self.formsemestre.query_ues():
ue_cap = self.get_etud_ue_status(etudid, ue.id)
if ue_cap["is_capitalized"]:
if ue_cap and ue_cap["is_capitalized"]:
recompute_mg = True
coef = ue_cap["coef_ue"]
if not np.isnan(ue_cap["moy"]):
@ -213,15 +214,13 @@ class ResultatsSemestre(ResultatsCache):
def get_etud_ue_status(self, etudid: int, ue_id: int) -> dict:
"""L'état de l'UE pour cet étudiant.
L'UE doit être du semestre.
Result: dict.
Result: dict, ou None si l'UE n'est pas dans ce semestre.
"""
if not self.validations:
self.validations = res_sem.load_formsemestre_validations(self.formsemestre)
ue = UniteEns.query.get(ue_id) # TODO cacher nos UEs ?
if ue.type == UE_SPORT:
return {
"is_capitalized": False,
"was_capitalized": False,
"is_external": False,
"coef_ue": 0.0,
"cur_moy_ue": 0.0,
@ -232,9 +231,16 @@ class ResultatsSemestre(ResultatsCache):
"capitalized_ue_id": None,
"ects_pot": 0.0,
}
if not ue_id in self.etud_moy_ue:
return None
if not self.validations:
self.validations = res_sem.load_formsemestre_validations(self.formsemestre)
cur_moy_ue = self.etud_moy_ue[ue_id][etudid]
moy_ue = cur_moy_ue
is_capitalized = False
is_capitalized = False # si l'UE prise en compte est une UE capitalisée
was_capitalized = (
False # s'il y a precedemment une UE capitalisée (pas forcement meilleure)
)
if etudid in self.validations.ue_capitalisees.index:
ue_cap = self._get_etud_ue_cap(etudid, ue)
if (
@ -242,6 +248,7 @@ class ResultatsSemestre(ResultatsCache):
and not ue_cap.empty
and not np.isnan(ue_cap["moy_ue"])
):
was_capitalized = True
if ue_cap["moy_ue"] > cur_moy_ue or np.isnan(cur_moy_ue):
moy_ue = ue_cap["moy_ue"]
is_capitalized = True
@ -250,6 +257,7 @@ class ResultatsSemestre(ResultatsCache):
return {
"is_capitalized": is_capitalized,
"was_capitalized": was_capitalized,
"is_external": ue_cap["is_external"] if is_capitalized else ue.is_external,
"coef_ue": coef_ue,
"ects_pot": ue.ects or 0.0,
@ -301,6 +309,7 @@ class NotesTableCompat(ResultatsSemestre):
"bonus_ues",
"malus",
"etud_moy_gen_ranks",
"etud_moy_gen_ranks_int",
"ue_rangs",
)
@ -311,41 +320,54 @@ class NotesTableCompat(ResultatsSemestre):
self.bonus = None # virtuel
self.bonus_ues = None # virtuel
self.ue_rangs = {u.id: (None, nb_etuds) for u in self.ues}
self.mod_rangs = {
m.id: (None, nb_etuds) for m in self.formsemestre.modimpls_sorted
}
self.mod_rangs = None # sera surchargé en Classic, mais pas en APC
self.moy_min = "NA"
self.moy_max = "NA"
self.moy_moy = "NA"
self.expr_diagnostics = ""
self.parcours = self.formsemestre.formation.get_parcours()
def get_etudids(self, sorted=False) -> list[int]:
"""Liste des etudids inscrits, incluant les démissionnaires.
Si sorted, triée par moy. générale décroissante
Sinon, triée par ordre alphabetique de NOM
def get_inscrits(self, include_demdef=True, order_by=False) -> list[Identite]:
"""Liste des étudiants inscrits
order_by = False|'nom'|'moy' tri sur nom ou sur moyenne générale (indicative)
Note: pour récupérer les etudids des inscrits, non triés, il est plus efficace
d'utiliser `[ ins.etudid for ins in nt.formsemestre.inscriptions ]`
"""
etuds = self.formsemestre.get_inscrits(
include_demdef=include_demdef, order=(order_by == "nom")
)
if order_by == "moy":
etuds.sort(
key=lambda e: (
self.etud_moy_gen_ranks_int.get(e.id, 100000),
e.sort_key,
)
)
return etuds
def get_etudids(self) -> list[int]:
"""(deprecated)
Liste des etudids inscrits, incluant les démissionnaires.
triée par ordre alphabetique de NOM
(à éviter: renvoie les etudids, mais est moins efficace que get_inscrits)
"""
# Note: pour avoir les inscrits non triés,
# utiliser [ ins.etudid for ins in self.formsemestre.inscriptions ]
if sorted:
# Tri par moy. generale décroissante
return [x[-1] for x in self.T]
return [x["etudid"] for x in self.inscrlist]
@cached_property
def sem(self) -> dict:
"""le formsemestre, comme un dict (nt.sem)"""
return self.formsemestre.to_dict()
"""le formsemestre, comme un gros et gras dict (nt.sem)"""
return self.formsemestre.get_infos_dict()
@cached_property
def inscrlist(self) -> list[dict]: # utilisé par PE seulement
def inscrlist(self) -> list[dict]: # utilisé par PE
"""Liste des inscrits au semestre (avec DEM et DEF),
sous forme de dict etud,
classée dans l'ordre alphabétique de noms.
"""
etuds = self.formsemestre.get_inscrits(include_demdef=True)
etuds.sort(key=lambda e: e.sort_key)
etuds = self.formsemestre.get_inscrits(include_demdef=True, order=True)
return [e.to_dict_scodoc7() for e in etuds]
@cached_property
@ -379,7 +401,7 @@ class NotesTableCompat(ResultatsSemestre):
d = modimpl.to_dict()
# compat ScoDoc < 9.2: ajoute matières
d["mat"] = modimpl.module.matiere.to_dict()
modimpls_dict.append(d)
modimpls_dict.append(d)
return modimpls_dict
def compute_rangs(self):
@ -387,11 +409,14 @@ class NotesTableCompat(ResultatsSemestre):
Moyenne générale: etud_moy_gen_ranks
Par UE (sauf ue bonus)
"""
self.etud_moy_gen_ranks = moy_sem.comp_ranks_series(self.etud_moy_gen)
(
self.etud_moy_gen_ranks,
self.etud_moy_gen_ranks_int,
) = moy_sem.comp_ranks_series(self.etud_moy_gen)
for ue in self.formsemestre.query_ues():
moy_ue = self.etud_moy_ue[ue.id]
self.ue_rangs[ue.id] = (
moy_sem.comp_ranks_series(moy_ue),
moy_sem.comp_ranks_series(moy_ue)[0], # juste en chaine
int(moy_ue.count()),
)
# .count() -> nb of non NaN values
@ -420,12 +445,27 @@ class NotesTableCompat(ResultatsSemestre):
Return: True|False, message explicatif
"""
return self.parcours.check_barre_ues(
[
self.get_etud_ue_status(etudid, ue.id)
for ue in self.formsemestre.query_ues()
]
)
ue_status_list = []
for ue in self.formsemestre.query_ues():
ue_status = self.get_etud_ue_status(etudid, ue.id)
if ue_status:
ue_status_list.append(ue_status)
return self.parcours.check_barre_ues(ue_status_list)
def all_etuds_have_sem_decisions(self):
"""True si tous les étudiants du semestre ont une décision de jury.
Ne regarde pas les décisions d'UE.
"""
for ins in self.formsemestre.inscriptions:
if ins.etat != scu.INSCRIT:
continue # skip démissionnaires
if self.get_etud_decision_sem(ins.etudid) is None:
return False
return True
def etud_has_decision(self, etudid):
"""True s'il y a une décision de jury pour cet étudiant"""
return self.get_etud_decision_ues(etudid) or self.get_etud_decision_sem(etudid)
def get_etud_decision_ues(self, etudid: int) -> dict:
"""Decisions du jury pour les UE de cet etudiant, ou None s'il n'y en pas eu.
@ -525,25 +565,32 @@ class NotesTableCompat(ResultatsSemestre):
Évaluation "complete" ssi toutes notes saisies ou en attente.
"""
modimpl = ModuleImpl.query.get(moduleimpl_id)
modimpl_results = self.modimpls_results.get(moduleimpl_id)
if not modimpl_results:
return [] # safeguard
evals_results = []
for e in modimpl.evaluations:
if self.modimpls_results[moduleimpl_id].evaluations_completes_dict[e.id]:
if modimpl_results.evaluations_completes_dict.get(e.id, False):
d = e.to_dict()
moduleimpl_results = self.modimpls_results[e.moduleimpl_id]
d["heure_debut"] = e.heure_debut # datetime.time
d["heure_fin"] = e.heure_fin
d["jour"] = e.jour # datetime
d["notes"] = {
etud.id: {
"etudid": etud.id,
"value": moduleimpl_results.evals_notes[e.id][etud.id],
"value": modimpl_results.evals_notes[e.id][etud.id],
}
for etud in self.etuds
}
d["etat"] = {
"evalattente": moduleimpl_results.evaluations_etat[e.id].nb_attente,
"evalattente": modimpl_results.evaluations_etat[e.id].nb_attente,
}
evals_results.append(d)
elif e.id not in modimpl_results.evaluations_completes_dict:
# ne devrait pas arriver ? XXX
log(
f"Warning: 220213 get_evals_in_mod {e.id} not in mod {moduleimpl_id} ?"
)
return evals_results
def get_evaluations_etats(self):
@ -608,7 +655,7 @@ class NotesTableCompat(ResultatsSemestre):
"""
table_moyennes = []
etuds_inscriptions = self.formsemestre.etuds_inscriptions
ues = self.formsemestre.query_ues() # sans bonus
ues = self.formsemestre.query_ues(with_sport=True) # avec bonus
for etudid in etuds_inscriptions:
moy_gen = self.etud_moy_gen.get(etudid, False)
if moy_gen is False:
@ -623,8 +670,11 @@ class NotesTableCompat(ResultatsSemestre):
ue_is_cap = {}
for ue in ues:
ue_status = self.get_etud_ue_status(etudid, ue.id)
moy_ues.append(ue_status["moy"])
ue_is_cap[ue.id] = ue_status["is_capitalized"]
if ue_status:
moy_ues.append(ue_status["moy"])
ue_is_cap[ue.id] = ue_status["is_capitalized"]
else:
moy_ues.append("?")
t = [moy_gen] + list(moy_ues)
# Moyennes modules:
for modimpl in self.formsemestre.modimpls_sorted:

View File

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

View File

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

View File

@ -7,12 +7,14 @@
from functools import cached_property
from flask import abort, url_for
from flask import g, request
import sqlalchemy
from app import db
from app import models
from app.scodoc import notesdb as ndb
from app.scodoc.sco_bac import Baccalaureat
import app.scodoc.sco_utils as scu
class Identite(db.Model):
@ -73,6 +75,13 @@ class Identite(db.Model):
"""
return {"M": "M.", "F": "Mme", "X": ""}[self.civilite]
def sex_nom(self, no_accents=False) -> str:
"'M. DUPONTÉ', ou si no_accents, 'M. DUPONTE'"
s = f"{self.civilite_str} {(self.nom_usuel or self.nom).upper()}"
if no_accents:
return scu.suppress_accents(s)
return s
def nom_disp(self) -> str:
"Nom à afficher"
if self.nom_usuel:
@ -125,6 +134,7 @@ class Identite(db.Model):
# ScoDoc7 output_formators: (backward compat)
e["etudid"] = self.id
e["date_naissance"] = ndb.DateISOtoDMY(e["date_naissance"])
e["ne"] = {"M": "", "F": "ne"}.get(self.civilite, "(e)")
return {k: e[k] or "" for k in e} # convert_null_outputs_to_empty
def to_dict_bul(self, include_urls=True):
@ -302,6 +312,24 @@ class Admission(db.Model):
"Le bac. utiliser bac.abbrev() pour avoir une chaine de caractères."
return Baccalaureat(self.bac, specialite=self.specialite)
def to_dict(self, no_nulls=False):
"""Représentation dictionnaire,"""
e = dict(self.__dict__)
e.pop("_sa_instance_state", None)
if no_nulls:
for k in e:
if e[k] is None:
col_type = getattr(
sqlalchemy.inspect(models.Admission).columns, "apb_groupe"
).expression.type
if isinstance(col_type, sqlalchemy.Text):
e[k] = ""
elif isinstance(col_type, sqlalchemy.Integer):
e[k] = 0
elif isinstance(col_type, sqlalchemy.Boolean):
e[k] = False
return e
# Suivi scolarité / débouchés
class ItemSuivi(db.Model):

View File

@ -2,12 +2,16 @@
"""ScoDoc models: evaluations
"""
import datetime
from app import db
from app.models import UniteEns
from app.models import formsemestre
from app.models.formsemestre import FormSemestre
from app.models.moduleimpls import ModuleImpl
from app.models.ues import UniteEns
from app.scodoc.sco_exceptions import ScoValueError
import app.scodoc.notesdb as ndb
from app.scodoc import sco_evaluation_db
class Evaluation(db.Model):
@ -51,11 +55,11 @@ class Evaluation(db.Model):
e["evaluation_id"] = self.id
e["jour"] = ndb.DateISOtoDMY(e["jour"])
e["numero"] = ndb.int_null_is_zero(e["numero"])
return sco_evaluation_db.evaluation_enrich_dict(e)
return evaluation_enrich_dict(e)
def from_dict(self, data):
"""Set evaluation attributes from given dict values."""
sco_evaluation_db._check_evaluation_args(data)
check_evaluation_args(data)
for k in self.__dict__.keys():
if k != "_sa_instance_state" and k != "id" and k in data:
setattr(self, k, data[k])
@ -145,3 +149,89 @@ class EvaluationUEPoids(db.Model):
def __repr__(self):
return f"<EvaluationUEPoids {self.evaluation} {self.ue} poids={self.poids}>"
# Fonction héritée de ScoDoc7 à refactorer
def evaluation_enrich_dict(e):
"""add or convert some fileds in an evaluation dict"""
# For ScoDoc7 compat
heure_debut_dt = e["heure_debut"] or datetime.time(
8, 00
) # au cas ou pas d'heure (note externe?)
heure_fin_dt = e["heure_fin"] or datetime.time(8, 00)
e["heure_debut"] = ndb.TimefromISO8601(e["heure_debut"])
e["heure_fin"] = ndb.TimefromISO8601(e["heure_fin"])
e["jouriso"] = ndb.DateDMYtoISO(e["jour"])
heure_debut, heure_fin = e["heure_debut"], e["heure_fin"]
d = ndb.TimeDuration(heure_debut, heure_fin)
if d is not None:
m = d % 60
e["duree"] = "%dh" % (d / 60)
if m != 0:
e["duree"] += "%02d" % m
else:
e["duree"] = ""
if heure_debut and (not heure_fin or heure_fin == heure_debut):
e["descrheure"] = " à " + heure_debut
elif heure_debut and heure_fin:
e["descrheure"] = " de %s à %s" % (heure_debut, heure_fin)
else:
e["descrheure"] = ""
# matin, apresmidi: utile pour se referer aux absences:
if heure_debut_dt < datetime.time(12, 00):
e["matin"] = 1
else:
e["matin"] = 0
if heure_fin_dt > datetime.time(12, 00):
e["apresmidi"] = 1
else:
e["apresmidi"] = 0
return e
def check_evaluation_args(args):
"Check coefficient, dates and duration, raises exception if invalid"
moduleimpl_id = args["moduleimpl_id"]
# check bareme
note_max = args.get("note_max", None)
if note_max is None:
raise ScoValueError("missing note_max")
try:
note_max = float(note_max)
except ValueError:
raise ScoValueError("Invalid note_max value")
if note_max < 0:
raise ScoValueError("Invalid note_max value (must be positive or null)")
# check coefficient
coef = args.get("coefficient", None)
if coef is None:
raise ScoValueError("missing coefficient")
try:
coef = float(coef)
except ValueError:
raise ScoValueError("Invalid coefficient value")
if coef < 0:
raise ScoValueError("Invalid coefficient value (must be positive or null)")
# check date
jour = args.get("jour", None)
args["jour"] = jour
if jour:
modimpl = ModuleImpl.query.get(moduleimpl_id)
formsemestre = modimpl.formsemestre
y, m, d = [int(x) for x in ndb.DateDMYtoISO(jour).split("-")]
jour = datetime.date(y, m, d)
if (jour > formsemestre.date_fin) or (jour < formsemestre.date_debut):
raise ScoValueError(
"La date de l'évaluation (%s/%s/%s) n'est pas dans le semestre !"
% (d, m, y),
dest_url="javascript:history.back();",
)
heure_debut = args.get("heure_debut", None)
args["heure_debut"] = heure_debut
heure_fin = args.get("heure_fin", None)
args["heure_fin"] = heure_fin
if jour and ((not heure_debut) or (not heure_fin)):
raise ScoValueError("Les heures doivent être précisées")
d = ndb.TimeDuration(heure_debut, heure_fin)
if d and ((d < 0) or (d > 60 * 12)):
raise ScoValueError("Heures de l'évaluation incohérentes !")

View File

@ -117,10 +117,12 @@ class FormSemestre(db.Model):
return f"<{self.__class__.__name__} {self.id} {self.titre_num()}>"
def to_dict(self):
"dict (compatible ScoDoc7)"
d = dict(self.__dict__)
d.pop("_sa_instance_state", None)
# ScoDoc7 output_formators: (backward compat)
d["formsemestre_id"] = self.id
d["titre_num"] = self.titre_num()
if self.date_debut:
d["date_debut"] = self.date_debut.strftime("%d/%m/%Y")
d["date_debut_iso"] = self.date_debut.isoformat()
@ -144,6 +146,8 @@ class FormSemestre(db.Model):
d["annee_debut"] = str(self.date_debut.year)
d["annee"] = d["annee_debut"]
d["annee_fin"] = str(self.date_fin.year)
if d["annee_fin"] != d["annee_debut"]:
d["annee"] += "-" + str(d["annee_fin"])
d["mois_debut_ord"] = self.date_debut.month
d["mois_fin_ord"] = self.date_fin.month
# La période: considère comme "S1" (ou S3) les débuts en aout-sept-octobre
@ -152,15 +156,8 @@ class FormSemestre(db.Model):
d["periode"] = 1 # typiquement, début en septembre: S1, S3...
else:
d["periode"] = 2 # typiquement, début en février: S2, S4...
d["titre_num"] = self.titre_num
d["titreannee"] = "%s %s %s" % (
d["titre_num"],
self.modalite or "",
self.date_debut.year,
)
if d["annee_fin"] != d["annee_debut"]:
d["titreannee"] += "-" + str(d["annee_fin"])
d["annee"] += "-" + str(d["annee_fin"])
d["titre_num"] = self.titre_num()
d["titreannee"] = self.titre_annee()
d["mois_debut"] = f"{self.date_debut.month} {self.date_debut.year}"
d["mois_fin"] = f"{self.date_fin.month} {self.date_fin.year}"
d["titremois"] = "%s %s (%s - %s)" % (
@ -332,6 +329,15 @@ class FormSemestre(db.Model):
"-".join((imputation_dept, parcours_name, modalite, semestre_id, annee_sco))
)
def titre_annee(self) -> str:
""" """
titre_annee = (
f"{self.titre_num()} {self.modalite or ''} {self.date_debut.year}"
)
if self.date_fin.year != self.date_debut.year:
titre_annee += "-" + str(self.date_fin.year)
return titre_annee
def titre_mois(self) -> str:
"""Le titre et les dates du semestre, pour affichage dans des listes
Ex: "BUT QLIO (PN 2022) semestre 1 FI (Sept 2022 - Jan 2023)"
@ -359,15 +365,19 @@ class FormSemestre(db.Model):
etudid, self.date_debut.isoformat(), self.date_fin.isoformat()
)
def get_inscrits(self, include_demdef=False) -> list[Identite]:
def get_inscrits(self, include_demdef=False, order=False) -> list[Identite]:
"""Liste des étudiants inscrits à ce semestre
Si include_demdef, tous les étudiants, avec les démissionnaires
et défaillants.
Si order, tri par clé sort_key
"""
if include_demdef:
return [ins.etud for ins in self.inscriptions]
etuds = [ins.etud for ins in self.inscriptions]
else:
return [ins.etud for ins in self.inscriptions if ins.etat == scu.INSCRIT]
etuds = [ins.etud for ins in self.inscriptions if ins.etat == scu.INSCRIT]
if order:
etuds.sort(key=lambda e: e.sort_key)
return etuds
@cached_property
def etudids_actifs(self) -> set:

View File

@ -79,7 +79,7 @@ class ModuleImpl(db.Model):
)
def to_dict(self):
"""as a dict, with the same conversions as in ScoDoc7"""
"""as a dict, with the same conversions as in ScoDoc7, including module"""
e = dict(self.__dict__)
e.pop("_sa_instance_state", None)
# ScoDoc7 output_formators: (backward compat)

View File

@ -4,8 +4,9 @@
"""
from app import db
from app.models import SHORT_STR_LEN
from app.models import CODE_STR_LEN
import app.scodoc.notesdb as ndb
import app.scodoc.sco_utils as scu
class BulAppreciations(db.Model):
@ -67,3 +68,32 @@ class NotesNotesLog(db.Model):
comment = db.Column(db.Text) # texte libre
date = db.Column(db.DateTime(timezone=True), server_default=db.func.now())
uid = db.Column(db.Integer, db.ForeignKey("user.id"))
def etud_has_notes_attente(etudid, formsemestre_id):
"""Vrai si cet etudiant a au moins une note en attente dans ce semestre.
(ne compte que les notes en attente dans des évaluation avec coef. non nul).
"""
# XXX ancienne méthode de notes_table à ré-écrire
cnx = ndb.GetDBConnexion()
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
cursor.execute(
"""SELECT n.*
FROM notes_notes n, notes_evaluation e, notes_moduleimpl m,
notes_moduleimpl_inscription i
WHERE n.etudid = %(etudid)s
and n.value = %(code_attente)s
and n.evaluation_id = e.id
and e.moduleimpl_id = m.id
and m.formsemestre_id = %(formsemestre_id)s
and e.coefficient != 0
and m.id = i.moduleimpl_id
and i.etudid=%(etudid)s
""",
{
"formsemestre_id": formsemestre_id,
"etudid": etudid,
"code_attente": scu.NOTES_ATTENTE,
},
)
return len(cursor.fetchall()) > 0

View File

@ -87,7 +87,7 @@ def get_tags_latex(code_latex):
"""
if code_latex:
# 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]
else:
return []

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -209,7 +209,7 @@ def formsemestre_bulletinetud_published_dict(
acronyme=scu.quote_xml_attr(ue["acronyme"]),
titre=scu.quote_xml_attr(ue["titre"]),
note=dict(
value=scu.fmt_note(ue_status["cur_moy_ue"]),
value=scu.fmt_note(ue_status["cur_moy_ue"] if ue_status else ""),
min=scu.fmt_note(ue["min"]),
max=scu.fmt_note(ue["max"]),
moy=scu.fmt_note(
@ -254,7 +254,10 @@ def formsemestre_bulletinetud_published_dict(
m["note"][k] = scu.fmt_note(m["note"][k])
u["module"].append(m)
if sco_preferences.get_preference("bul_show_mod_rangs", formsemestre_id):
if (
sco_preferences.get_preference("bul_show_mod_rangs", formsemestre_id)
and nt.mod_rangs is not None
):
m["rang"] = dict(
value=nt.mod_rangs[modimpl["moduleimpl_id"]][0][etudid]
)

View File

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

View File

@ -56,19 +56,22 @@ import time
import traceback
from pydoc import html
from reportlab.platypus.doctemplate import PageTemplate, BaseDocTemplate
from reportlab.platypus.doctemplate import BaseDocTemplate
from flask import g, request
import app.scodoc.sco_utils as scu
from app import log, ScoValueError
from app.models import FormSemestre
from app.scodoc import sco_cache
from app.scodoc import sco_formsemestre
from app.scodoc import sco_codes_parcours
from app.scodoc import sco_pdf
from app.scodoc import sco_preferences
from app.scodoc import sco_etud
import sco_version
from app.scodoc.sco_logos import find_logo
import app.scodoc.sco_utils as scu
import sco_version
def pdfassemblebulletins(
@ -178,22 +181,21 @@ def get_formsemestre_bulletins_pdf(formsemestre_id, version="selectedevals"):
if cached:
return cached[1], cached[0]
fragments = []
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
# Make each bulletin
nt = sco_cache.NotesTableCache.get(formsemestre_id) # > get_etudids, get_sexnom
formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
bookmarks = {}
filigrannes = {}
i = 1
for etudid in nt.get_etudids():
for etud in formsemestre.get_inscrits(include_demdef=True, order=True):
frag, filigranne = sco_bulletins.do_formsemestre_bulletinetud(
formsemestre_id,
etudid,
formsemestre,
etud.id,
format="pdfpart",
version=version,
)
fragments += frag
filigrannes[i] = filigranne
bookmarks[i] = scu.suppress_accents(nt.get_sexnom(etudid))
bookmarks[i] = etud.sex_nom(no_accents=True)
i = i + 1
#
infos = {"DeptName": sco_preferences.get_preference("DeptName", formsemestre_id)}
@ -206,7 +208,7 @@ def get_formsemestre_bulletins_pdf(formsemestre_id, version="selectedevals"):
pdfdoc = pdfassemblebulletins(
formsemestre_id,
fragments,
sem["titremois"],
formsemestre.titre_mois(),
infos,
bookmarks,
filigranne=filigrannes,
@ -216,7 +218,7 @@ def get_formsemestre_bulletins_pdf(formsemestre_id, version="selectedevals"):
sco_pdf.PDFLOCK.release()
#
dt = time.strftime("%Y-%m-%d")
filename = "bul-%s-%s.pdf" % (sem["titre_num"], dt)
filename = "bul-%s-%s.pdf" % (formsemestre.titre_num(), dt)
filename = scu.unescape_html(filename).replace(" ", "_").replace("&", "")
# fill cache
sco_cache.SemBulletinsPDFCache.set(
@ -235,8 +237,9 @@ def get_etud_bulletins_pdf(etudid, version="selectedevals"):
filigrannes = {}
i = 1
for sem in etud["sems"]:
formsemestre = FormSemestre.query.get(sem["formsemestre_id"])
frag, filigranne = sco_bulletins.do_formsemestre_bulletinetud(
sem["formsemestre_id"],
formsemestre,
etudid,
format="pdfpart",
version=version,
@ -271,3 +274,16 @@ def get_etud_bulletins_pdf(etudid, version="selectedevals"):
)
return pdfdoc, filename
def get_filigranne(etud_etat: str, prefs) -> str:
"""Texte à placer en "filigranne" sur le bulletin pdf"""
if etud_etat == scu.DEMISSION:
return "Démission"
elif etud_etat == sco_codes_parcours.DEF:
return "Défaillant"
elif (prefs["bul_show_temporary"] and not I["decision_sem"]) or prefs[
"bul_show_temporary_forced"
]:
return prefs["bul_temporary_txt"]
return ""

View File

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

View File

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

View File

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

View File

@ -31,15 +31,17 @@ Rapport (table) avec dernier semestre fréquenté et débouché de chaque étudi
import http
from flask import url_for, g, request
from app import log
from app.comp import res_sem
from app.comp.res_common import NotesTableCompat
from app.models import FormSemestre
import app.scodoc.sco_utils as scu
import app.scodoc.notesdb as ndb
from app import log
from app.scodoc.sco_exceptions import AccessDenied
from app.scodoc.scolog import logdb
from app.scodoc.gen_tables import GenTable
from app.scodoc import safehtml
from app.scodoc import html_sco_header
from app.scodoc import sco_cache
from app.scodoc import sco_permissions_check
from app.scodoc import sco_preferences
from app.scodoc import sco_tag_module
@ -115,7 +117,7 @@ def get_etudids_with_debouche(start_year):
def table_debouche_etudids(etudids, keep_numeric=True):
"""Rapport pour ces etudiants"""
"""Rapport pour ces étudiants"""
L = []
for etudid in etudids:
etud = sco_etud.get_etud_info(filled=True, etudid=etudid)[0]
@ -124,7 +126,8 @@ def table_debouche_etudids(etudids, keep_numeric=True):
es = [(s["date_fin_iso"], i) for i, s in enumerate(sems)]
imax = max(es)[1]
last_sem = sems[imax]
nt = sco_cache.NotesTableCache.get(last_sem["formsemestre_id"])
formsemestre = FormSemestre.query.get_or_404(last_sem["formsemestre_id"])
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
row = {
"etudid": etudid,
"civilite": etud["civilite"],

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):
# il y a des numeros identiques !
scu.objects_renumber(db, others)
ue.formation.invalidate_cached_sems()
if len(others) > 1:
idx = [u.id for u in others].index(ue.id)
neigh = None # object to swap with

View File

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

View File

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

View File

@ -128,13 +128,14 @@ def do_ue_create(args):
formation = Formation.query.get(args["formation_id"])
formation.invalidate_module_coefs()
# news
F = sco_formations.formation_list(args={"formation_id": args["formation_id"]})[0]
formation = Formation.query.get(args["formation_id"])
sco_news.add(
typ=sco_news.NEWS_FORM,
object=args["formation_id"],
text="Modification de la formation %(acronyme)s" % F,
text="Modification de la formation {formation.acronyme}",
max_frequency=3,
)
formation.invalidate_cached_sems()
return ue_id
@ -250,7 +251,7 @@ def ue_edit(ue_id=None, create=False, formation_id=None, default_semestre_idx=No
title = f"Modification de l'UE {ue.acronyme} {ue.titre}"
initvalues = ue_dict
submitlabel = "Modifier les valeurs"
can_change_semestre_id = ue.modules.count() == 0
can_change_semestre_id = (ue.modules.count() == 0) or (ue.semestre_idx is None)
else:
ue = None
title = "Création d'une UE"
@ -361,7 +362,7 @@ def ue_edit(ue_id=None, create=False, formation_id=None, default_semestre_idx=No
{
"size": 12,
"title": "Code UE",
"explanation": "code interne (optionnel). Toutes les UE partageant le même code (et le même code de formation) sont compatibles (compensation de semestres, capitalisation d'UE). Voir liste ci-dessous.",
"explanation": "code interne (non vide). Toutes les UE partageant le même code (et le même code de formation) sont compatibles (compensation de semestres, capitalisation d'UE). Voir liste ci-dessous.",
},
),
(
@ -410,7 +411,7 @@ def ue_edit(ue_id=None, create=False, formation_id=None, default_semestre_idx=No
submitlabel=submitlabel,
)
if tf[0] == 0:
if ue and ue.modules.count():
if ue and ue.modules.count() and ue.semestre_idx is not None:
modules_div = f"""<div id="ue_list_modules">
<div><b>{ue.modules.count()} modules sont rattachés
à cette UE</b> du semestre S{ue.semestre_idx},
@ -575,7 +576,9 @@ def ue_table(formation_id=None, semestre_idx=1, msg=""): # was ue_list
semestre_ids = range(1, parcours.NB_SEM + 1)
# transition: on requete ici via l'ORM mais on utilise les fonctions ScoDoc7
# basées sur des dicts
ues_obj = UniteEns.query.filter_by(formation_id=formation_id, is_external=False)
ues_obj = UniteEns.query.filter_by(
formation_id=formation_id, is_external=False
).order_by(UniteEns.semestre_idx, UniteEns.numero)
ues_externes_obj = UniteEns.query.filter_by(
formation_id=formation_id, is_external=True
)
@ -1415,15 +1418,15 @@ def ue_list_semestre_ids(ue: dict):
UE_PALETTE = [
"#EFA00B",
"#99C24D",
"#EC9192",
"#0075C4",
"#D65108",
"#DEC0F1",
"#B02E0C",
"#151E3F",
"#FB3640",
"#B80004", # rouge
"#F97B3D", # Orange Crayola
"#FEB40B", # Honey Yellow
"#80CB3F", # Yellow Green
"#05162E", # Oxford Blue
"#548687", # Steel Teal
"#444054", # Independence
"#889696", # Spanish Gray
"#0CA4A5", # Viridian Green
]
@ -1436,8 +1439,8 @@ def colorie_anciennes_ues(ues: list[UniteEns]) -> None:
last_sem_idx = 0
for ue in ues:
if ue.semestre_idx != last_sem_idx:
last_sem_idx = ue.semestre_idx
index = 0
last_sem_idx = ue.semestre_idx
if ue.color is None:
ue.color = UE_PALETTE[index % nb_colors]
index += 1

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)
nt = sco_cache.NotesTableCache.get( formsemestre_id)
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
#
s = SemSet('NSS29902')
apo_data = sco_apogee_csv.ApoData(open('/opt/scodoc/var/scodoc/archives/apo_csv/RT/2015-2/2016-07-10-11-26-15/V1RT.csv').read(), periode=1)

View File

@ -38,6 +38,7 @@ from flask_mail import Message
from app import email
from app import log
from app.models import Admission
from app.models.etudiants import make_etud_args
import app.scodoc.sco_utils as scu
import app.scodoc.notesdb as ndb
@ -859,19 +860,24 @@ def list_scolog(etudid):
return cursor.dictfetchall()
def fill_etuds_info(etuds):
def fill_etuds_info(etuds, add_admission=True):
"""etuds est une liste d'etudiants (mappings)
Pour chaque etudiant, ajoute ou formatte les champs
-> informations pour fiche etudiant ou listes diverses
"""
from app.scodoc import sco_formsemestre
from app.scodoc import sco_formsemestre_inscriptions
Si add_admission: ajoute au dict le schamps "admission" s'il n'y sont pas déjà.
"""
cnx = ndb.GetDBConnexion()
# open('/tmp/t','w').write( str(etuds) )
for etud in etuds:
etudid = etud["etudid"]
etud["dept"] = g.scodoc_dept
# Admission
if add_admission and "nomlycee" not in etud:
admission = (
Admission.query.filter_by(etudid=etudid).first().to_dict(no_nulls=True)
)
etud.update(admission)
#
adrs = adresse_list(cnx, {"etudid": etudid})
if not adrs:
# certains "vieux" etudiants n'ont pas d'adresse
@ -884,79 +890,37 @@ def fill_etuds_info(etuds):
etud.update(adr)
format_etud_ident(etud)
# Semestres dans lesquel il est inscrit
ins = sco_formsemestre_inscriptions.do_formsemestre_inscription_list(
{"etudid": etudid}
)
etud["ins"] = ins
sems = []
cursem = None # semestre "courant" ou il est inscrit
for i in ins:
sem = sco_formsemestre.get_formsemestre(i["formsemestre_id"])
if sco_formsemestre.sem_est_courant(sem):
cursem = sem
curi = i
sem["ins"] = i
sems.append(sem)
# trie les semestres par date de debut, le plus recent d'abord
# (important, ne pas changer (suivi cohortes))
sems.sort(key=itemgetter("dateord"), reverse=True)
etud["sems"] = sems
etud["cursem"] = cursem
if cursem:
etud["inscription"] = cursem["titremois"]
etud["inscriptionstr"] = "Inscrit en " + cursem["titremois"]
etud["inscription_formsemestre_id"] = cursem["formsemestre_id"]
etud["etatincursem"] = curi["etat"]
etud["situation"] = descr_situation_etud(etudid, etud["ne"])
# XXX est-ce utile ? sco_groups.etud_add_group_infos( etud, cursem)
else:
if etud["sems"]:
if etud["sems"][0]["dateord"] > time.strftime(
"%Y-%m-%d", time.localtime()
):
etud["inscription"] = "futur"
etud["situation"] = "futur élève"
else:
etud["inscription"] = "ancien"
etud["situation"] = "ancien élève"
else:
etud["inscription"] = "non inscrit"
etud["situation"] = etud["inscription"]
etud["inscriptionstr"] = etud["inscription"]
etud["inscription_formsemestre_id"] = None
# XXXetud['partitions'] = {} # ne va pas chercher les groupes des anciens semestres
etud["etatincursem"] = "?"
etud.update(etud_inscriptions_infos(etudid, etud["ne"]))
# nettoyage champs souvents vides
if etud["nomlycee"]:
# nettoyage champs souvent vides
if etud.get("nomlycee"):
etud["ilycee"] = "Lycée " + format_lycee(etud["nomlycee"])
if etud["villelycee"]:
etud["ilycee"] += " (%s)" % etud["villelycee"]
etud["ilycee"] += " (%s)" % etud.get("villelycee", "")
etud["ilycee"] += "<br/>"
else:
if etud["codelycee"]:
if etud.get("codelycee"):
etud["ilycee"] = format_lycee_from_code(etud["codelycee"])
else:
etud["ilycee"] = ""
rap = ""
if etud["rapporteur"] or etud["commentaire"]:
if etud.get("rapporteur") or etud.get("commentaire"):
rap = "Note du rapporteur"
if etud["rapporteur"]:
if etud.get("rapporteur"):
rap += " (%s)" % etud["rapporteur"]
rap += ": "
if etud["commentaire"]:
if etud.get("commentaire"):
rap += "<em>%s</em>" % etud["commentaire"]
etud["rap"] = rap
# if etud['boursier_prec']:
# pass
if etud["telephone"]:
if etud.get("telephone"):
etud["telephonestr"] = "<b>Tél.:</b> " + format_telephone(etud["telephone"])
else:
etud["telephonestr"] = ""
if etud["telephonemobile"]:
if etud.get("telephonemobile"):
etud["telephonemobilestr"] = "<b>Mobile:</b> " + format_telephone(
etud["telephonemobile"]
)
@ -964,8 +928,56 @@ def fill_etuds_info(etuds):
etud["telephonemobilestr"] = ""
def descr_situation_etud(etudid, ne=""):
"""chaine decrivant la situation actuelle de l'etudiant"""
def etud_inscriptions_infos(etudid: int, ne="") -> dict:
"""Dict avec les informations sur les semestres passés et courant"""
from app.scodoc import sco_formsemestre
from app.scodoc import sco_formsemestre_inscriptions
etud = {}
# Semestres dans lesquel il est inscrit
ins = sco_formsemestre_inscriptions.do_formsemestre_inscription_list(
{"etudid": etudid}
)
etud["ins"] = ins
sems = []
cursem = None # semestre "courant" ou il est inscrit
for i in ins:
sem = sco_formsemestre.get_formsemestre(i["formsemestre_id"])
if sco_formsemestre.sem_est_courant(sem):
cursem = sem
curi = i
sem["ins"] = i
sems.append(sem)
# trie les semestres par date de debut, le plus recent d'abord
# (important, ne pas changer (suivi cohortes))
sems.sort(key=itemgetter("dateord"), reverse=True)
etud["sems"] = sems
etud["cursem"] = cursem
if cursem:
etud["inscription"] = cursem["titremois"]
etud["inscriptionstr"] = "Inscrit en " + cursem["titremois"]
etud["inscription_formsemestre_id"] = cursem["formsemestre_id"]
etud["etatincursem"] = curi["etat"]
etud["situation"] = descr_situation_etud(etudid, ne)
else:
if etud["sems"]:
if etud["sems"][0]["dateord"] > time.strftime("%Y-%m-%d", time.localtime()):
etud["inscription"] = "futur"
etud["situation"] = "futur élève"
else:
etud["inscription"] = "ancien"
etud["situation"] = "ancien élève"
else:
etud["inscription"] = "non inscrit"
etud["situation"] = etud["inscription"]
etud["inscriptionstr"] = etud["inscription"]
etud["inscription_formsemestre_id"] = None
etud["etatincursem"] = "?"
return etud
def descr_situation_etud(etudid: int, ne="") -> str:
"""chaîne décrivant la situation actuelle de l'étudiant"""
from app.scodoc import sco_formsemestre
cnx = ndb.GetDBConnexion()
@ -982,7 +994,7 @@ def descr_situation_etud(etudid, ne=""):
)
r = cursor.dictfetchone()
if not r:
situation = "non inscrit"
situation = "non inscrit" + ne
else:
sem = sco_formsemestre.get_formsemestre(r["formsemestre_id"])
if r["etat"] == "I":

View File

@ -237,7 +237,11 @@ def formsemestre_check_absences_html(formsemestre_id):
if evals:
H.append(
'<div class="module_check_absences"><h2><a href="moduleimpl_status?moduleimpl_id=%s">%s: %s</a></h2>'
% (M["moduleimpl_id"], M["module"]["code"], M["module"]["abbrev"])
% (
M["moduleimpl_id"],
M["module"]["code"] or "",
M["module"]["abbrev"] or "",
)
)
for E in evals:
H.append(

View File

@ -36,6 +36,8 @@ from flask import url_for, g
from flask_login import current_user
from app import log
from app.models.evaluations import evaluation_enrich_dict, check_evaluation_args
import app.scodoc.sco_utils as scu
import app.scodoc.notesdb as ndb
from app.scodoc.sco_exceptions import AccessDenied, ScoValueError
@ -81,43 +83,6 @@ _evaluationEditor = ndb.EditableTable(
)
def evaluation_enrich_dict(e):
"""add or convert some fileds in an evaluation dict"""
# For ScoDoc7 compat
heure_debut_dt = e["heure_debut"] or datetime.time(
8, 00
) # au cas ou pas d'heure (note externe?)
heure_fin_dt = e["heure_fin"] or datetime.time(8, 00)
e["heure_debut"] = ndb.TimefromISO8601(e["heure_debut"])
e["heure_fin"] = ndb.TimefromISO8601(e["heure_fin"])
e["jouriso"] = ndb.DateDMYtoISO(e["jour"])
heure_debut, heure_fin = e["heure_debut"], e["heure_fin"]
d = ndb.TimeDuration(heure_debut, heure_fin)
if d is not None:
m = d % 60
e["duree"] = "%dh" % (d / 60)
if m != 0:
e["duree"] += "%02d" % m
else:
e["duree"] = ""
if heure_debut and (not heure_fin or heure_fin == heure_debut):
e["descrheure"] = " à " + heure_debut
elif heure_debut and heure_fin:
e["descrheure"] = " de %s à %s" % (heure_debut, heure_fin)
else:
e["descrheure"] = ""
# matin, apresmidi: utile pour se referer aux absences:
if heure_debut_dt < datetime.time(12, 00):
e["matin"] = 1
else:
e["matin"] = 0
if heure_fin_dt > datetime.time(12, 00):
e["apresmidi"] = 1
else:
e["apresmidi"] = 0
return e
def do_evaluation_list(args, sortkey=None):
"""List evaluations, sorted by numero (or most recent date first).
@ -127,7 +92,7 @@ def do_evaluation_list(args, sortkey=None):
'apresmidi' : 1 (termine après 12:00) ou 0
'descrheure' : ' de 15h00 à 16h30'
"""
# Attention: transformation fonction ScoDc7 en SQLAlchemy
# Attention: transformation fonction ScoDoc7 en SQLAlchemy
cnx = ndb.GetDBConnexion()
evals = _evaluationEditor.list(cnx, args, sortkey=sortkey)
# calcule duree (chaine de car.) de chaque evaluation et ajoute jouriso, matin, apresmidi
@ -146,59 +111,6 @@ def do_evaluation_list_in_formsemestre(formsemestre_id):
return evals
def _check_evaluation_args(args):
"Check coefficient, dates and duration, raises exception if invalid"
moduleimpl_id = args["moduleimpl_id"]
# check bareme
note_max = args.get("note_max", None)
if note_max is None:
raise ScoValueError("missing note_max")
try:
note_max = float(note_max)
except ValueError:
raise ScoValueError("Invalid note_max value")
if note_max < 0:
raise ScoValueError("Invalid note_max value (must be positive or null)")
# check coefficient
coef = args.get("coefficient", None)
if coef is None:
raise ScoValueError("missing coefficient")
try:
coef = float(coef)
except ValueError:
raise ScoValueError("Invalid coefficient value")
if coef < 0:
raise ScoValueError("Invalid coefficient value (must be positive or null)")
# check date
jour = args.get("jour", None)
args["jour"] = jour
if jour:
M = sco_moduleimpl.moduleimpl_list(moduleimpl_id=moduleimpl_id)[0]
sem = sco_formsemestre.get_formsemestre(M["formsemestre_id"])
d, m, y = [int(x) for x in sem["date_debut"].split("/")]
date_debut = datetime.date(y, m, d)
d, m, y = [int(x) for x in sem["date_fin"].split("/")]
date_fin = datetime.date(y, m, d)
# passe par ndb.DateDMYtoISO pour avoir date pivot
y, m, d = [int(x) for x in ndb.DateDMYtoISO(jour).split("-")]
jour = datetime.date(y, m, d)
if (jour > date_fin) or (jour < date_debut):
raise ScoValueError(
"La date de l'évaluation (%s/%s/%s) n'est pas dans le semestre !"
% (d, m, y),
dest_url="javascript:history.back();",
)
heure_debut = args.get("heure_debut", None)
args["heure_debut"] = heure_debut
heure_fin = args.get("heure_fin", None)
args["heure_fin"] = heure_fin
if jour and ((not heure_debut) or (not heure_fin)):
raise ScoValueError("Les heures doivent être précisées")
d = ndb.TimeDuration(heure_debut, heure_fin)
if d and ((d < 0) or (d > 60 * 12)):
raise ScoValueError("Heures de l'évaluation incohérentes !")
def do_evaluation_create(
moduleimpl_id=None,
jour=None,
@ -220,7 +132,7 @@ def do_evaluation_create(
)
args = locals()
log("do_evaluation_create: args=" + str(args))
_check_evaluation_args(args)
check_evaluation_args(args)
# Check numeros
module_evaluation_renumber(moduleimpl_id, only_if_unumbered=True)
if not "numero" in args or args["numero"] is None:
@ -263,6 +175,7 @@ def do_evaluation_create(
# news
M = sco_moduleimpl.moduleimpl_list(moduleimpl_id=moduleimpl_id)[0]
sco_cache.invalidate_formsemestre(formsemestre_id=M["formsemestre_id"])
mod = sco_edit_module.module_list(args={"module_id": M["module_id"]})[0]
mod["moduleimpl_id"] = M["moduleimpl_id"]
mod["url"] = "Notes/moduleimpl_status?moduleimpl_id=%(moduleimpl_id)s" % mod
@ -288,7 +201,7 @@ def do_evaluation_edit(args):
"Modification évaluation impossible pour %s" % current_user.get_nomplogin()
)
args["moduleimpl_id"] = moduleimpl_id
_check_evaluation_args(args)
check_evaluation_args(args)
cnx = ndb.GetDBConnexion()
_evaluationEditor.edit(cnx, args)

View File

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

View File

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

View File

@ -29,6 +29,9 @@
"""
from flask import url_for, g, request
from app.comp import res_sem
from app.comp.res_common import NotesTableCompat
from app.models import FormSemestre
import app.scodoc.notesdb as ndb
import app.scodoc.sco_utils as scu
from app import log
@ -77,7 +80,8 @@ def _build_results_table(start_date=None, end_date=None, types_parcours=[]):
{}
) # etudid : { formsemestre_id d'inscription le plus recent dans les dates considérées, etud }
for formsemestre_id in formsemestre_ids_parcours:
nt = sco_cache.NotesTableCache.get(formsemestre_id) # > get_etudids
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
etudids = nt.get_etudids()
for etudid in etudids:
if etudid not in etuds_infos: # pas encore traité ?

View File

@ -601,7 +601,7 @@ def do_formsemestre_createwithmodules(edit=False):
"input_type": "text_suggest",
"size": 50,
"withcheckbox": True,
"title": "%s %s" % (mod["code"], mod["titre"]),
"title": "%s %s" % (mod["code"] or "", mod["titre"] or ""),
"allowed_values": allowed_user_names,
"template": itemtemplate,
"text_suggest_options": {
@ -802,7 +802,9 @@ def do_formsemestre_createwithmodules(edit=False):
}
moduleimpl_id = sco_moduleimpl.do_moduleimpl_create(modargs)
mod = sco_edit_module.module_list({"module_id": module_id})[0]
msg += ["création de %s (%s)" % (mod["code"], mod["titre"])]
msg += [
"création de %s (%s)" % (mod["code"] or "?", mod["titre"] or "?")
]
# INSCRIPTIONS DES ETUDIANTS
log(
'inscription module: %s = "%s"'
@ -824,7 +826,7 @@ def do_formsemestre_createwithmodules(edit=False):
)
msg += [
"inscription de %d étudiants au module %s"
% (len(etudids), mod["code"])
% (len(etudids), mod["code"] or "(module sans code)")
]
else:
log(
@ -919,11 +921,19 @@ def formsemestre_delete_moduleimpls(formsemestre_id, module_ids_to_del):
if evals:
msg += [
'<b>impossible de supprimer %s (%s) car il y a %d évaluations définies (<a href="moduleimpl_status?moduleimpl_id=%s" class="stdlink">supprimer les d\'abord</a>)</b>'
% (mod["code"], mod["titre"], len(evals), moduleimpl_id)
% (
mod["code"] or "(module sans code)",
mod["titre"],
len(evals),
moduleimpl_id,
)
]
ok = False
else:
msg += ["suppression de %s (%s)" % (mod["code"], mod["titre"])]
msg += [
"suppression de %s (%s)"
% (mod["code"] or "(module sans code)", mod["titre"] or "")
]
sco_moduleimpl.do_moduleimpl_delete(
moduleimpl_id, formsemestre_id=formsemestre_id
)

View File

@ -37,6 +37,9 @@ import flask
from flask import url_for, g, request
from flask_login import current_user
from app.comp import res_sem
from app.comp.res_common import NotesTableCompat
from app.models import FormSemestre
import app.scodoc.sco_utils as scu
import app.scodoc.notesdb as ndb
from app import log
@ -260,7 +263,8 @@ def formsemestre_ext_edit_ue_validations(formsemestre_id, etudid):
def _make_page(etud, sem, tf, message=""):
nt = sco_cache.NotesTableCache.get(sem["formsemestre_id"])
formsemestre = FormSemestre.query.get_or_404(sem["formsemestre_id"])
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
moy_gen = nt.get_etud_moy_gen(etud["etudid"])
H = [
html_sco_header.sco_header(

View File

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

View File

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

View File

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

View File

@ -45,6 +45,9 @@ from flask import g, request
from flask import url_for, make_response
from app import db
from app.comp import res_sem
from app.comp.res_common import NotesTableCompat
from app.models import FormSemestre, formsemestre
from app.models import GROUPNAME_STR_LEN, SHORT_STR_LEN
from app.models.groups import Partition
import app.scodoc.sco_utils as scu
@ -488,17 +491,14 @@ def XMLgetGroupsInPartition(partition_id): # was XMLgetGroupesTD
<group ...>
...
"""
from app.scodoc import sco_formsemestre
cnx = ndb.GetDBConnexion()
t0 = time.time()
partition = get_partition(partition_id)
formsemestre_id = partition["formsemestre_id"]
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
etuds_set = {ins.etudid for ins in formsemestre.inscriptions}
sem = formsemestre.get_infos_dict() # transition TODO
groups = get_partition_groups(partition)
nt = sco_cache.NotesTableCache.get(formsemestre_id) # > inscrdict
etuds_set = set(nt.inscrdict)
# Build XML:
t1 = time.time()
doc = Element("ajax-response")
@ -1277,13 +1277,13 @@ def groups_auto_repartition(partition_id=None):
partition = get_partition(partition_id)
formsemestre_id = partition["formsemestre_id"]
formsemestre = FormSemestre.query.get(formsemestre_id)
# renvoie sur page édition groupes
dest_url = url_for(
"scolar.affect_groups", scodoc_dept=g.scodoc_dept, partition_id=partition_id
)
if not sco_permissions_check.can_change_groups(formsemestre_id):
raise AccessDenied("Vous n'avez pas le droit d'effectuer cette opération !")
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
descr = [
("partition_id", {"input_type": "hidden"}),
@ -1301,7 +1301,7 @@ def groups_auto_repartition(partition_id=None):
H = [
html_sco_header.sco_header(page_title="Répartition des groupes"),
"<h2>Répartition des groupes de %s</h2>" % partition["partition_name"],
"<p>Semestre %s</p>" % sem["titreannee"],
f"<p>Semestre {formsemestre.titre_annee()}</p>",
"""<p class="help">Les groupes existants seront <b>effacés</b> et remplacés par
ceux créés ici. La répartition aléatoire tente d'uniformiser le niveau
des groupes (en utilisant la dernière moyenne générale disponible pour
@ -1343,7 +1343,7 @@ def groups_auto_repartition(partition_id=None):
# return '\n'.join(H) + tf[1] + html_sco_header.sco_footer()
group_ids.append(create_group(partition_id, group_name))
#
nt = sco_cache.NotesTableCache.get(formsemestre_id) # > identdict
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
identdict = nt.identdict
# build: { civilite : liste etudids trie par niveau croissant }
civilites = set([x["civilite"] for x in identdict.values()])
@ -1384,9 +1384,8 @@ def _get_prev_moy(etudid, formsemestre_id):
etud = info[0]
Se = sco_parcours_dut.SituationEtudParcours(etud, formsemestre_id)
if Se.prev:
nt = sco_cache.NotesTableCache.get(
Se.prev["formsemestre_id"]
) # > get_etud_moy_gen
prev_sem = FormSemestre.query.get(Se.prev["formsemestre_id"])
nt: NotesTableCompat = res_sem.load_formsemestre_results(prev_sem)
return nt.get_etud_moy_gen(etudid)
else:
return 0.0

View File

@ -31,16 +31,17 @@
import flask
from flask import url_for, g, request
from app import models
from app.models.evaluations import Evaluation
from app.models.moduleimpls import ModuleImpl
import app.scodoc.sco_utils as scu
import app.scodoc.notesdb as ndb
from app import log
from app import models
from app.comp import res_sem
from app.comp import moy_mod
from app.comp.moy_mod import ModuleImplResults
from app.comp.res_common import NotesTableCompat
from app.models import FormSemestre
from app.models.evaluations import Evaluation
from app.models.moduleimpls import ModuleImpl
import app.scodoc.sco_utils as scu
import app.scodoc.notesdb as ndb
from app.scodoc.TrivialFormulator import TrivialFormulator
from app.scodoc import sco_cache
from app.scodoc import sco_edit_module
@ -788,7 +789,9 @@ def _add_moymod_column(
):
"""Ajoute la colonne moymod à rows"""
col_id = "moymod"
nt = sco_cache.NotesTableCache.get(formsemestre_id) # > get_etud_mod_moy
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
nb_notes = 0
sum_notes = 0
notes = [] # liste des notes numeriques, pour calcul histogramme uniquement

View File

@ -29,6 +29,7 @@
"""
from flask_login import current_user
import psycopg2
import app.scodoc.sco_utils as scu
import app.scodoc.notesdb as ndb
@ -271,7 +272,12 @@ _moduleimpl_inscriptionEditor = ndb.EditableTable(
def do_moduleimpl_inscription_create(args, formsemestre_id=None):
"create a moduleimpl_inscription"
cnx = ndb.GetDBConnexion()
r = _moduleimpl_inscriptionEditor.create(cnx, args)
try:
r = _moduleimpl_inscriptionEditor.create(cnx, args)
except psycopg2.errors.UniqueViolation as exc:
raise ScoValueError(
"Inscription impossible car déjà existante: vérifiez la situation"
)
sco_cache.invalidate_formsemestre(
formsemestre_id=formsemestre_id
) # > moduleimpl_inscription

View File

@ -33,6 +33,10 @@ import flask
from flask import url_for, g, request
from flask_login import current_user
from app.comp import res_sem
from app.comp.res_common import NotesTableCompat
from app.models import FormSemestre
import app.scodoc.notesdb as ndb
import app.scodoc.sco_utils as scu
from app import log
@ -88,7 +92,11 @@ def moduleimpl_inscriptions_edit(moduleimpl_id, etuds=[], submitted=False):
"Appliquer les modifications".
</p>
"""
% (moduleimpl_id, mod["titre"], mod["code"]),
% (
moduleimpl_id,
mod["titre"] or "(module sans titre)",
mod["code"] or "(module sans code)",
),
]
# Liste des inscrits à ce semestre
inscrits = sco_formsemestre_inscriptions.do_formsemestre_inscription_listinscrits(
@ -304,8 +312,8 @@ def moduleimpl_inscriptions_stats(formsemestre_id):
H.append(
'<tr class="formsemestre_status"><td>%s</td><td class="formsemestre_status_code">%s</td><td class="formsemestre_status_inscrits">%s</td><td>%s</td></tr>'
% (
mod["ue"]["acronyme"],
mod["module"]["code"],
mod["ue"]["acronyme"] or "",
mod["module"]["code"] or "(module sans code)",
mod["nb_inscrits"],
c_link,
)
@ -333,7 +341,11 @@ def moduleimpl_inscriptions_stats(formsemestre_id):
c_link = mod["module"]["titre"]
H.append(
'<tr class="formsemestre_status_green"><td>%s</td><td class="formsemestre_status_code">%s</td><td>%s</td></tr>'
% (mod["ue"]["acronyme"], mod["module"]["code"], c_link)
% (
mod["ue"]["acronyme"],
mod["module"]["code"] or "(module sans code)",
c_link,
)
)
H.append("</table>")
@ -402,14 +414,14 @@ def moduleimpl_inscriptions_stats(formsemestre_id):
H.append("</ul></li>")
H.append("</ul>")
H.append(
"""<hr/><p class="help">Cette page décrit les inscriptions actuelles.
Vous pouvez changer (si vous en avez le droit) les inscrits dans chaque module en
cliquant sur la ligne du module.</p>
<p class="help">Note: la déinscription d'un module ne perd pas les notes. Ainsi, si
l'étudiant est ensuite réinscrit au même module, il retrouvera ses notes.</p>
"""
)
H.append(
"""<hr/><p class="help">Cette page décrit les inscriptions actuelles.
Vous pouvez changer (si vous en avez le droit) les inscrits dans chaque module en
cliquant sur la ligne du module.</p>
<p class="help">Note: la déinscription d'un module ne perd pas les notes. Ainsi, si
l'étudiant est ensuite réinscrit au même module, il retrouvera ses notes.</p>
"""
)
H.append(html_sco_header.sco_footer())
return "\n".join(H)
@ -479,21 +491,21 @@ def get_etuds_with_capitalized_ue(formsemestre_id):
returns { ue_id : [ { infos } ] }
"""
UECaps = scu.DictDefault(defaultvalue=[])
nt = sco_cache.NotesTableCache.get(
formsemestre_id
) # > get_ues_stat_dict, get_etud_ue_status
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
inscrits = sco_formsemestre_inscriptions.do_formsemestre_inscription_list(
args={"formsemestre_id": formsemestre_id}
)
ues = nt.get_ues_stat_dict()
for ue in ues:
for etud in inscrits:
status = nt.get_etud_ue_status(etud["etudid"], ue["ue_id"])
if status["was_capitalized"]:
ue_status = nt.get_etud_ue_status(etud["etudid"], ue["ue_id"])
if ue_status and ue_status["was_capitalized"]:
UECaps[ue["ue_id"]].append(
{
"etudid": etud["etudid"],
"ue_status": status,
"ue_status": ue_status,
"is_ins": is_inscrit_ue(
etud["etudid"], formsemestre_id, ue["ue_id"]
),

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

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

View File

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

View File

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

View File

@ -42,6 +42,9 @@ sem_set_list()
import flask
from flask import g
from app.comp import res_sem
from app.comp.res_common import NotesTableCompat
from app.models import FormSemestre
from app.scodoc import html_sco_header
from app.scodoc import sco_cache
from app.scodoc import sco_etape_apogee
@ -239,7 +242,8 @@ class SemSet(dict):
self["etuds_without_nip"] = set() # etudids
self["jury_ok"] = True
for sem in self.sems:
nt = sco_cache.NotesTableCache.get(sem["formsemestre_id"])
formsemestre = FormSemestre.query.get_or_404(sem["formsemestre_id"])
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
sem["etuds"] = list(nt.identdict.values())
sem["nips"] = {e["code_nip"] for e in sem["etuds"] if e["code_nip"]}
sem["etuds_without_nip"] = {

View File

@ -37,6 +37,9 @@ import http
from flask import g, url_for
from app.comp import res_sem
from app.comp.res_common import NotesTableCompat
from app.models import FormSemestre
import app.scodoc.sco_utils as scu
import app.scodoc.notesdb as ndb
from app import log
@ -269,7 +272,8 @@ def get_etud_tagged_modules(etudid, tagname):
etud = sco_etud.get_etud_info(etudid=etudid, filled=True)[0]
R = []
for sem in etud["sems"]:
nt = sco_cache.NotesTableCache.get(sem["formsemestre_id"])
formsemestre = FormSemestre.query.get_or_404(sem["formsemestre_id"])
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
modimpls = nt.get_modimpls_dict()
for modimpl in modimpls:
tags = module_tag_list(module_id=modimpl["module_id"])

View File

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

View File

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

View File

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

View File

@ -6,10 +6,23 @@
<h1>Charger un référentiel de compétences</h1>
<div class="row">
<div class="col-md-4">
<div class="col-md-5">
{{ wtf.quick_form(form) }}
</div>
</div>
<div class="row">
<div class="col-md-5">
<ul>
<li>
<a href="{{ url_for('notes.refcomp_table', scodoc_dept=g.scodoc_dept, ) }}">
Liste des référentiels de compétences chargés</a>
</li>
<li>
<a href="{{ url_for('notes.refcomp_assoc_formation', scodoc_dept=g.scodoc_dept, formation_id=formation.id) }}">
Association à la formation {{ formation.acronyme }}</a>
</li>
</div>
</div>
{% endblock %}

View File

@ -35,6 +35,11 @@
<b>{{ue.acronyme}}</b> <a class="discretelink" href="{{
url_for('notes.ue_infos', scodoc_dept=g.scodoc_dept, ue_id=ue.id)}}"
>{{ue.titre}}</a>
{% set virg = joiner(", ") %}
<span class="ue_code">(
{%- if ue.ue_code -%}{{ virg() }}code {{ue.ue_code}} {%- endif -%}
{{ virg() }}{{ue.ects or 0}} ECTS)
</span>
</span>
{% if editable and not ue.is_locked() %}

View File

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

View File

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

View File

@ -39,6 +39,9 @@ from flask import flash, jsonify, render_template, url_for
from flask import current_app, g, request
from flask_login import current_user
from werkzeug.utils import redirect
from app.comp import res_sem
from app.comp.res_common import NotesTableCompat
from app.models.formsemestre import FormSemestre
from app.models.formsemestre import FormSemestreUEComputationExpr
from app.models.ues import UniteEns
@ -1319,7 +1322,7 @@ def formsemestre_enseignants_list(formsemestre_id, format="html"):
# description textuelle des modules
for ens in sem_ens:
sem_ens[ens]["descr_mods"] = ", ".join(
[x["module"]["code"] for x in sem_ens[ens]["mods"]]
[x["module"]["code"] or "?" for x in sem_ens[ens]["mods"]]
)
# ajoute infos sur enseignant:
@ -1427,13 +1430,14 @@ def formsemestre_desinscription(etudid, formsemestre_id, dialog_confirmed=False)
S'il s'agit d'un semestre extérieur et qu'il n'y a plus d'inscrit,
le semestre sera supprimé.
"""
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
sem = formsemestre.to_dict() # compat
# -- check lock
if not sem["etat"]:
if not formsemestre.etat:
raise ScoValueError("desinscription impossible: semestre verrouille")
# -- Si décisions de jury, désinscription interdite
nt = sco_cache.NotesTableCache.get(formsemestre_id)
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
if nt.etud_has_decision(etudid):
raise ScoValueError(
"""Désinscription impossible: l'étudiant a une décision de jury
@ -1446,7 +1450,7 @@ def formsemestre_desinscription(etudid, formsemestre_id, dialog_confirmed=False)
)
if not dialog_confirmed:
etud = sco_etud.get_etud_info(etudid=etudid, filled=True)[0]
if sem["modalite"] != "EXT":
if formsemestre.modalite != "EXT":
msg_ext = """
<p>%s sera désinscrit de tous les modules du semestre %s (%s - %s).</p>
<p>Cette opération ne doit être utilisée que pour corriger une <b>erreur</b> !
@ -1898,7 +1902,8 @@ def formsemestre_bulletins_mailetuds(
):
"envoi a chaque etudiant (inscrit et ayant un mail) son bulletin"
prefer_mail_perso = int(prefer_mail_perso)
nt = sco_cache.NotesTableCache.get(formsemestre_id) # > get_etudids
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
etudids = nt.get_etudids()
#
if not sco_bulletins.can_send_bulletin_by_mail(formsemestre_id):
@ -1920,7 +1925,7 @@ def formsemestre_bulletins_mailetuds(
nb_send = 0
for etudid in etudids:
h, _ = sco_bulletins.do_formsemestre_bulletinetud(
formsemestre_id,
formsemestre,
etudid,
version=version,
prefer_mail_perso=prefer_mail_perso,
@ -2247,9 +2252,10 @@ def formsemestre_validation_suppress_etud(
dest_url=scu.ScoURL(),
)
if not dialog_confirmed:
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
etud = sco_etud.get_etud_info(etudid=etudid, filled=True)[0]
nt = sco_cache.NotesTableCache.get(formsemestre_id) # > get_etud_decision_sem
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
sem = formsemestre.to_dict()
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
decision_jury = nt.get_etud_decision_sem(etudid)
if decision_jury:
existing = (

View File

@ -3,9 +3,11 @@ PN / Référentiel de compétences
Emmanuel Viennet, 2021
"""
from pathlib import Path
import re
from flask import url_for, flash
from flask import jsonify
from flask import jsonify, flash, url_for
from flask import Markup
from flask import current_app, g, request
from flask.templating import render_template
from flask_login import current_user
@ -15,7 +17,7 @@ from werkzeug.utils import secure_filename
from config import Config
from app import db
from app import models
from app import log
from app.decorators import scodoc, permission_required
from app.models import Formation
@ -23,9 +25,8 @@ from app.models.but_refcomp import ApcReferentielCompetences
from app.but.import_refcomp import orebut_import_refcomp
from app.but.forms.refcomp_forms import FormationRefCompForm, RefCompLoadForm
from app.scodoc.gen_tables import GenTable
from app.scodoc import html_sidebar
from app.scodoc import sco_utils as scu
from app.scodoc.sco_exceptions import ScoFormatError
from app.scodoc.sco_exceptions import ScoFormatError, ScoValueError
from app.scodoc.sco_permissions import Permission
from app.views import notes_bp as bp
from app.views import ScoData
@ -171,14 +172,61 @@ def refcomp_load(formation_id=None):
formation = Formation.query.get_or_404(formation_id)
else:
formation = None
refs_distrib_files = sorted(
list(
(
Path(current_app.config["SCODOC_DIR"])
/ "ressources/referentiels/but2022/competences"
).glob("*.xml")
)
)
refs_distrib_dict = [{"id": 0, "specialite": "Aucun", "created": "", "serial": ""}]
i = 1
for filename in refs_distrib_files:
m = re.match(r".*/but-([A-Za-z_]+)-([0-9]+)-([0-9]+).xml", str(filename))
if (
m
and ApcReferentielCompetences.query.filter_by(
scodoc_orig_filename=Path(filename).name, dept_id=g.scodoc_dept_id
).count()
== 0
):
refs_distrib_dict.append(
{
"id": i,
"specialite": m.group(1),
"created": m.group(2),
"serial": m.group(3),
"filename": str(filename),
}
)
i += 1
else:
log(f"refcomp_load: ignoring {filename} (invalid filename)")
form = RefCompLoadForm()
form.referentiel_standard.choices = [
(r["id"], f"{r['specialite']} ({r['created']}-{r['serial']})")
for r in refs_distrib_dict
]
if form.validate_on_submit():
f = form.upload.data
filename = secure_filename(f.filename)
if form.upload.data:
f = form.upload.data
filename = secure_filename(f.filename)
elif form.referentiel_standard.data:
try:
filename = refs_distrib_dict[int(form.referentiel_standard.data)][
"filename"
]
except (ValueError, IndexError):
raise ScoValueError("choix invalide")
f = open(filename)
else:
raise ScoValueError("choix invalide")
try:
xml_data = f.read()
_ = orebut_import_refcomp(
xml_data, dept_id=g.scodoc_dept_id, orig_filename=filename
xml_data, dept_id=g.scodoc_dept_id, orig_filename=Path(filename).name
)
except TypeError as exc:
raise ScoFormatError(
@ -187,6 +235,11 @@ def refcomp_load(formation_id=None):
except ScoFormatError:
raise
flash(
Markup(f"Référentiel <tt>{Path(filename).name}</tt> chargé."),
category="info",
)
if formation is not None:
return redirect(
url_for(

View File

@ -35,7 +35,15 @@ import io
import re
import flask
from flask import abort, flash, url_for, redirect, render_template, send_file
from flask import (
abort,
flash,
make_response,
redirect,
render_template,
send_file,
url_for,
)
from flask import request
import flask_login
from flask_login.utils import login_required, current_user
@ -54,6 +62,7 @@ from app.models import Departement, Identite
from app.models import departements
from app.models import FormSemestre, FormSemestreInscription
from app.models import ScoDocSiteConfig
from app.models import UniteEns
from app.scodoc import sco_codes_parcours, sco_logos
from app.scodoc import sco_find_etud
from app.scodoc import sco_utils as scu
@ -264,11 +273,15 @@ def get_bonus_description(bonus_name: str):
bonus_name = ""
bonus_class = ScoDocSiteConfig.get_bonus_sport_class_from_name(bonus_name)
text = bonus_class.__doc__
fields = re.split(r"\n\n", text, maxsplit=1)
if len(fields) > 1:
first_line, text = fields
if text:
fields = re.split(r"\n\n", text, maxsplit=1)
if len(fields) > 1:
first_line, text = fields
else:
first_line, text = "", fields[0]
else:
first_line, text = "", fields[0]
text = ""
first_line = "pas de fonction bonus configurée."
return f"""<div class="bonus_description_head">{first_line}</div>
<div>{text}</div>
@ -350,6 +363,29 @@ def get_logo(name: str, dept_id: int):
)
# ---
@bp.route("/ScoDoc/ue_colors_css/<int:formation_id>/<int:semestre_idx>")
def ue_colors_css(formation_id: int, semestre_idx: int):
"""Feuille de style pour les couleurs d'UE"""
ues = UniteEns.query.filter_by(
formation_id=formation_id, semestre_idx=semestre_idx
).order_by(UniteEns.numero)
txt = (
":root{\n"
+ "\n".join(
[
f"--color-UE{semestre_idx}-{ue_idx+1}: {ue.color};"
for ue_idx, ue in enumerate(ues)
if ue.color
]
)
+ "\n}\n"
)
response = make_response(txt)
response.headers["Content-Type"] = "text/css"
return response
# essais
# @bp.route("/testlog")
# def testlog():

View File

@ -35,7 +35,7 @@ import time
import flask
from flask import jsonify, url_for, flash, render_template, make_response
from flask import current_app, g, request
from flask import g, request
from flask_login import current_user
from flask_wtf import FlaskForm
from flask_wtf.file import FileField, FileAllowed

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

View File

@ -14,6 +14,8 @@ import click
import flask
from flask.cli import with_appcontext
from flask.templating import render_template
import psycopg2
import sqlalchemy
from app import create_app, cli, db
from app import initialize_scodoc_database
@ -133,11 +135,11 @@ def user_create(username, role, dept, nom=None, prenom=None): # user-create
"Create a new user"
r = Role.get_named_role(role)
if not r:
sys.stderr.write("user_create: role {r} does not exist\n".format(r=role))
sys.stderr.write(f"user_create: role {role} does not exist\n")
return 1
u = User.query.filter_by(user_name=username).first()
if u:
sys.stderr.write("user_create: user {u} already exists\n".format(u=u))
sys.stderr.write(f"user_create: user {u} already exists\n")
return 2
if dept == "@all":
dept = None
@ -145,11 +147,26 @@ def user_create(username, role, dept, nom=None, prenom=None): # user-create
u.add_role(r, dept)
db.session.add(u)
db.session.commit()
click.echo(
"created user, login: {u.user_name}, with role {r} in dept. {dept}".format(
u=u, r=r, dept=dept
click.echo(f"created user, login: {u.user_name}, with role {r} in dept. {dept}")
@app.cli.command()
@click.argument("username")
def user_delete(username): # user-delete
"Try to delete this user. Fails if it's associated to some scodoc objects."
u = User.query.filter_by(user_name=username).first()
if not u:
sys.stderr.write(f"user_delete: user {username} not found\n")
return 2
db.session.delete(u)
try:
db.session.commit()
except (sqlalchemy.exc.IntegrityError, psycopg2.errors.ForeignKeyViolation):
sys.stderr.write(
f"""\nuser_delete: ne peux pas supprimer l'utilisateur {username}\ncar il est associé à des objets dans ScoDoc (modules, notes, ...).\n"""
)
)
return 1
click.echo(f"deleted user, login: {username}")
@app.cli.command()
@ -417,6 +434,7 @@ def localize_logo(logo: str = None, dept: str = None): # migrate-scodoc7-dept-l
@click.argument("xlsfile", type=click.File("rb"))
@click.argument("zipfile", type=click.File("rb"))
def photos_import_files(formsemestre_id: int, xlsfile: str, zipfile: str):
"""Import des photos d'étudiants à partir d'une liste excel et d'un zip avec les images."""
import app as mapp
from app.scodoc import sco_trombino, sco_photos
from app.scodoc import notesdb as ndb
@ -485,6 +503,7 @@ def recursive_help(cmd, parent=None):
@app.cli.command()
def dumphelp():
"""Génère la page d'aide complète pour la doc."""
recursive_help(app.cli)

View File

@ -13,6 +13,9 @@ from flask import current_app, g
import app
from app import db
from app.comp import res_sem
from app.comp.res_common import NotesTableCompat
from app.models import FormSemestre
from app.scodoc import sco_cache
from app.scodoc import sco_evaluations
from app.scodoc import sco_evaluation_db
@ -24,7 +27,7 @@ from tests.unit.test_sco_basic import run_sco_basic
DEPT = TestConfig.DEPT_TEST
def test_notes_table(test_client):
def test_notes_table(test_client): # XXX A REVOIR POUR TESTER RES TODO
"""Test construction et cache de NotesTable."""
app.set_sco_dept(DEPT)
assert g.scodoc_dept == DEPT
@ -35,7 +38,8 @@ def test_notes_table(test_client):
assert len(sems)
sem = sems[0]
formsemestre_id = sem["formsemestre_id"]
nt = sco_cache.NotesTableCache.get(formsemestre_id)
formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
assert nt
assert sco_cache.NotesTableCache.get(formsemestre_id, compute=False)
sco_cache.invalidate_formsemestre(formsemestre_id)

View File

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

View File

@ -19,7 +19,10 @@ from config import TestConfig
from tests.unit import sco_fake_gen
import app
from app.scodoc import notesdb as ndb, sco_formsemestre
from app.comp import res_sem
from app.comp.res_common import NotesTableCompat
from app.models import FormSemestre
from app.scodoc import sco_formsemestre
from app.scodoc import sco_abs
from app.scodoc import sco_abs_views
from app.scodoc import sco_bulletins
@ -208,7 +211,8 @@ def run_sco_basic(verbose=False):
redirect=False,
)
# Vérifie que toutes les UE des étudiants notés ont été acquises:
nt = sco_cache.NotesTableCache.get(formsemestre_id)
formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
for etud in etuds[:5]:
dec_ues = nt.get_etud_decision_ues(etud["etudid"])
for ue_id in dec_ues:

View File

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