forked from ScoDoc/ScoDoc
Merge branch 'refactor_nt' of https://scodoc.org/git/viennet/ScoDoc into entreprises
This commit is contained in:
commit
6acba813c1
@ -1,7 +1,7 @@
|
||||
|
||||
# ScoDoc - Gestion de la scolarité - Version ScoDoc 9
|
||||
|
||||
(c) Emmanuel Viennet 1999 - 2021 (voir LICENCE.txt)
|
||||
(c) Emmanuel Viennet 1999 - 2022 (voir LICENCE.txt)
|
||||
|
||||
Installation: voir instructions à jour sur <https://scodoc.org/GuideInstallDebian11>
|
||||
|
||||
|
@ -5,7 +5,7 @@
|
||||
#
|
||||
# Gestion scolarite IUT
|
||||
#
|
||||
# Copyright (c) 1999 - 2021 Emmanuel Viennet. All rights reserved.
|
||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
|
@ -5,7 +5,7 @@
|
||||
#
|
||||
# Gestion scolarite IUT
|
||||
#
|
||||
# Copyright (c) 1999 - 2021 Emmanuel Viennet. All rights reserved.
|
||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
|
@ -1,121 +1,44 @@
|
||||
##############################################################################
|
||||
# ScoDoc
|
||||
# Copyright (c) 1999 - 2021 Emmanuel Viennet. All rights reserved.
|
||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||
# See LICENSE
|
||||
##############################################################################
|
||||
|
||||
from collections import defaultdict
|
||||
"""Génération bulletin BUT
|
||||
"""
|
||||
|
||||
import datetime
|
||||
from flask import url_for, g
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
|
||||
from app import db
|
||||
|
||||
from app.comp import moy_ue, moy_sem, inscr_mod
|
||||
from app.models import ModuleImpl
|
||||
from app.scodoc import sco_utils as scu
|
||||
from app.scodoc.sco_cache import ResultatsSemestreBUTCache
|
||||
from app.scodoc import sco_bulletins_json
|
||||
from app.scodoc import sco_preferences
|
||||
from app.scodoc.sco_utils import jsnan, fmt_note
|
||||
from app.scodoc.sco_utils import fmt_note
|
||||
from app.comp.res_but import ResultatsSemestreBUT
|
||||
|
||||
|
||||
class ResultatsSemestreBUT:
|
||||
"""Structure légère pour stocker les résultats du semestre et
|
||||
générer les bulletins.
|
||||
__init__ : charge depuis le cache ou calcule
|
||||
class BulletinBUT(ResultatsSemestreBUT):
|
||||
"""Génération du bulletin BUT.
|
||||
Cette classe génère des dictionnaires avec toutes les informations
|
||||
du bulletin, qui sont immédiatement traduisibles en JSON.
|
||||
"""
|
||||
|
||||
_cached_attrs = (
|
||||
"sem_cube",
|
||||
"modimpl_inscr_df",
|
||||
"modimpl_coefs_df",
|
||||
"etud_moy_ue",
|
||||
"modimpls_evals_poids",
|
||||
"modimpls_evals_notes",
|
||||
"etud_moy_gen",
|
||||
"etud_moy_gen_ranks",
|
||||
"modimpls_evaluations_complete",
|
||||
)
|
||||
|
||||
def __init__(self, formsemestre):
|
||||
self.formsemestre = formsemestre
|
||||
self.ues = formsemestre.query_ues().all()
|
||||
self.modimpls = formsemestre.modimpls.all()
|
||||
self.etuds = self.formsemestre.get_inscrits(include_dem=False)
|
||||
self.etud_index = {e.id: idx for idx, e in enumerate(self.etuds)}
|
||||
self.saes = [
|
||||
m for m in self.modimpls if m.module.module_type == scu.ModuleType.SAE
|
||||
]
|
||||
self.ressources = [
|
||||
m for m in self.modimpls if m.module.module_type == scu.ModuleType.RESSOURCE
|
||||
]
|
||||
if not self.load_cached():
|
||||
self.compute()
|
||||
self.store()
|
||||
|
||||
def load_cached(self) -> bool:
|
||||
"Load cached dataframes, returns False si pas en cache"
|
||||
data = ResultatsSemestreBUTCache.get(self.formsemestre.id)
|
||||
if not data:
|
||||
return False
|
||||
for attr in self._cached_attrs:
|
||||
setattr(self, attr, data[attr])
|
||||
return True
|
||||
|
||||
def store(self):
|
||||
"Cache our dataframes"
|
||||
ResultatsSemestreBUTCache.set(
|
||||
self.formsemestre.id,
|
||||
{attr: getattr(self, attr) for attr in self._cached_attrs},
|
||||
)
|
||||
|
||||
def compute(self):
|
||||
"Charge les notes et inscriptions et calcule toutes les moyennes"
|
||||
(
|
||||
self.sem_cube,
|
||||
self.modimpls_evals_poids,
|
||||
self.modimpls_evals_notes,
|
||||
modimpls_evaluations,
|
||||
self.modimpls_evaluations_complete,
|
||||
) = moy_ue.notes_sem_load_cube(self.formsemestre)
|
||||
self.modimpl_inscr_df = inscr_mod.df_load_modimpl_inscr(self.formsemestre)
|
||||
self.modimpl_coefs_df, _, _ = moy_ue.df_load_modimpl_coefs(
|
||||
self.formsemestre, ues=self.ues, modimpls=self.modimpls
|
||||
)
|
||||
# l'idx de la colonne du mod modimpl.id est
|
||||
# modimpl_coefs_df.columns.get_loc(modimpl.id)
|
||||
# idx de l'UE: modimpl_coefs_df.index.get_loc(ue.id)
|
||||
self.etud_moy_ue = moy_ue.compute_ue_moys(
|
||||
self.sem_cube,
|
||||
self.etuds,
|
||||
self.modimpls,
|
||||
self.ues,
|
||||
self.modimpl_inscr_df,
|
||||
self.modimpl_coefs_df,
|
||||
)
|
||||
self.etud_moy_gen = moy_sem.compute_sem_moys(
|
||||
self.etud_moy_ue, self.modimpl_coefs_df
|
||||
)
|
||||
self.etud_moy_gen_ranks = moy_sem.comp_ranks_series(self.etud_moy_gen)
|
||||
|
||||
def etud_ue_mod_results(self, etud, ue, modimpls) -> dict:
|
||||
"dict synthèse résultats dans l'UE pour les modules indiqués"
|
||||
d = {}
|
||||
etud_idx = self.etud_index[etud.id]
|
||||
ue_idx = self.modimpl_coefs_df.index.get_loc(ue.id)
|
||||
etud_moy_module = self.sem_cube[etud_idx] # module x UE
|
||||
for mi in modimpls:
|
||||
coef = self.modimpl_coefs_df[mi.id][ue.id]
|
||||
for modimpl in modimpls:
|
||||
coef = self.modimpl_coefs_df[modimpl.id][ue.id]
|
||||
if coef > 0:
|
||||
d[mi.module.code] = {
|
||||
"id": mi.id,
|
||||
d[modimpl.module.code] = {
|
||||
"id": modimpl.id,
|
||||
"coef": coef,
|
||||
"moyenne": fmt_note(
|
||||
etud_moy_module[self.modimpl_coefs_df.columns.get_loc(mi.id)][
|
||||
ue_idx
|
||||
]
|
||||
etud_moy_module[
|
||||
self.modimpl_coefs_df.columns.get_loc(modimpl.id)
|
||||
][ue_idx]
|
||||
),
|
||||
}
|
||||
return d
|
||||
@ -149,7 +72,7 @@ class ResultatsSemestreBUT:
|
||||
avec évaluations de chacun."""
|
||||
d = {}
|
||||
# etud_idx = self.etud_index[etud.id]
|
||||
for mi in modimpls:
|
||||
for modimpl in modimpls:
|
||||
# mod_idx = self.modimpl_coefs_df.columns.get_loc(mi.id)
|
||||
# # moyennes indicatives (moyennes de moyennes d'UE)
|
||||
# try:
|
||||
@ -163,14 +86,15 @@ class ResultatsSemestreBUT:
|
||||
# moy_indicative_mod = np.nanmean(self.sem_cube[etud_idx, mod_idx])
|
||||
# except RuntimeWarning: # all nans in np.nanmean
|
||||
# pass
|
||||
d[mi.module.code] = {
|
||||
"id": mi.id,
|
||||
"titre": mi.module.titre,
|
||||
"code_apogee": mi.module.code_apogee,
|
||||
modimpl_results = self.modimpls_results[modimpl.id]
|
||||
d[modimpl.module.code] = {
|
||||
"id": modimpl.id,
|
||||
"titre": modimpl.module.titre,
|
||||
"code_apogee": modimpl.module.code_apogee,
|
||||
"url": url_for(
|
||||
"notes.moduleimpl_status",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
moduleimpl_id=mi.id,
|
||||
moduleimpl_id=modimpl.id,
|
||||
),
|
||||
"moyenne": {
|
||||
# # moyenne indicative de module: moyenne des UE, ignorant celles sans notes (nan)
|
||||
@ -181,16 +105,17 @@ class ResultatsSemestreBUT:
|
||||
},
|
||||
"evaluations": [
|
||||
self.etud_eval_results(etud, e)
|
||||
for eidx, e in enumerate(mi.evaluations)
|
||||
for e in modimpl.evaluations
|
||||
if e.visibulletin
|
||||
and self.modimpls_evaluations_complete[mi.id][eidx]
|
||||
and modimpl_results.evaluations_etat[e.id].is_complete
|
||||
],
|
||||
}
|
||||
return d
|
||||
|
||||
def etud_eval_results(self, etud, e) -> dict:
|
||||
"dict resultats d'un étudiant à une évaluation"
|
||||
eval_notes = self.modimpls_evals_notes[e.moduleimpl_id][e.id] # pd.Series
|
||||
# eval_notes est une pd.Series avec toutes les notes des étudiants inscrits
|
||||
eval_notes = self.modimpls_results[e.moduleimpl_id].evals_notes[e.id]
|
||||
notes_ok = eval_notes.where(eval_notes > scu.NOTES_ABSENCE).dropna()
|
||||
d = {
|
||||
"id": e.id,
|
||||
@ -202,7 +127,7 @@ class ResultatsSemestreBUT:
|
||||
"poids": {p.ue.acronyme: p.poids for p in e.ue_poids},
|
||||
"note": {
|
||||
"value": fmt_note(
|
||||
self.modimpls_evals_notes[e.moduleimpl_id][e.id][etud.id],
|
||||
eval_notes[etud.id],
|
||||
note_max=e.note_max,
|
||||
),
|
||||
"min": fmt_note(notes_ok.min()),
|
||||
@ -233,7 +158,7 @@ class ResultatsSemestreBUT:
|
||||
},
|
||||
"formsemestre_id": formsemestre.id,
|
||||
"etat_inscription": etat_inscription,
|
||||
"options": bulletin_option_affichage(formsemestre),
|
||||
"options": sco_preferences.bulletin_option_affichage(formsemestre.id),
|
||||
}
|
||||
semestre_infos = {
|
||||
"etapes": [str(x.etape_apo) for x in formsemestre.etapes if x.etape_apo],
|
||||
@ -244,8 +169,8 @@ class ResultatsSemestreBUT:
|
||||
"numero": formsemestre.semestre_id,
|
||||
"groupes": [], # XXX TODO
|
||||
"absences": { # XXX TODO
|
||||
"injustifie": 1,
|
||||
"total": 33,
|
||||
"injustifie": -1,
|
||||
"total": -1,
|
||||
},
|
||||
}
|
||||
semestre_infos.update(
|
||||
@ -298,125 +223,3 @@ class ResultatsSemestreBUT:
|
||||
)
|
||||
|
||||
return d
|
||||
|
||||
|
||||
def bulletin_option_affichage(formsemestre):
|
||||
"dict avec les options d'affichages (préférences) pour ce semestre"
|
||||
prefs = sco_preferences.SemPreferences(formsemestre.id)
|
||||
fields = (
|
||||
"bul_show_abs",
|
||||
"bul_show_abs_modules",
|
||||
"bul_show_ects",
|
||||
"bul_show_codemodules",
|
||||
"bul_show_matieres",
|
||||
"bul_show_rangs",
|
||||
"bul_show_ue_rangs",
|
||||
"bul_show_mod_rangs",
|
||||
"bul_show_moypromo",
|
||||
"bul_show_minmax",
|
||||
"bul_show_minmax_mod",
|
||||
"bul_show_minmax_eval",
|
||||
"bul_show_coef",
|
||||
"bul_show_ue_cap_details",
|
||||
"bul_show_ue_cap_current",
|
||||
"bul_show_temporary",
|
||||
"bul_temporary_txt",
|
||||
"bul_show_uevalid",
|
||||
"bul_show_date_inscr",
|
||||
)
|
||||
# on enlève le "bul_" de la clé:
|
||||
return {field[4:]: prefs[field] for field in fields}
|
||||
|
||||
|
||||
# Pour raccorder le code des anciens bulletins qui attendent une NoteTable
|
||||
class APCNotesTableCompat:
|
||||
"""Implementation partielle de NotesTable pour les formations APC
|
||||
Accès aux notes et rangs.
|
||||
"""
|
||||
|
||||
def __init__(self, formsemestre):
|
||||
self.results = ResultatsSemestreBUT(formsemestre)
|
||||
nb_etuds = len(self.results.etuds)
|
||||
self.rangs = self.results.etud_moy_gen_ranks
|
||||
self.moy_min = self.results.etud_moy_gen.min()
|
||||
self.moy_max = self.results.etud_moy_gen.max()
|
||||
self.moy_moy = self.results.etud_moy_gen.mean()
|
||||
self.bonus = defaultdict(lambda: 0.0) # XXX
|
||||
self.ue_rangs = {
|
||||
u.id: (defaultdict(lambda: 0.0), nb_etuds) for u in self.results.ues
|
||||
}
|
||||
self.mod_rangs = {
|
||||
m.id: (defaultdict(lambda: 0), nb_etuds) for m in self.results.modimpls
|
||||
}
|
||||
|
||||
def get_ues(self):
|
||||
ues = []
|
||||
for ue in self.results.ues:
|
||||
d = ue.to_dict()
|
||||
d.update(
|
||||
{
|
||||
"max": self.results.etud_moy_ue[ue.id].max(),
|
||||
"min": self.results.etud_moy_ue[ue.id].min(),
|
||||
"moy": self.results.etud_moy_ue[ue.id].mean(),
|
||||
"nb_moy": len(self.results.etud_moy_ue),
|
||||
}
|
||||
)
|
||||
ues.append(d)
|
||||
return ues
|
||||
|
||||
def get_modimpls(self):
|
||||
return [m.to_dict() for m in self.results.modimpls]
|
||||
|
||||
def get_etud_moy_gen(self, etudid):
|
||||
return self.results.etud_moy_gen[etudid]
|
||||
|
||||
def get_moduleimpls_attente(self):
|
||||
return [] # XXX TODO
|
||||
|
||||
def get_etud_rang(self, etudid):
|
||||
return self.rangs[etudid]
|
||||
|
||||
def get_etud_rang_group(self, etudid, group_id):
|
||||
return (None, 0) # XXX unimplemented TODO
|
||||
|
||||
def get_etud_ue_status(self, etudid, ue_id):
|
||||
return {
|
||||
"cur_moy_ue": self.results.etud_moy_ue[ue_id][etudid],
|
||||
"is_capitalized": False, # XXX TODO
|
||||
}
|
||||
|
||||
def get_etud_mod_moy(self, moduleimpl_id, etudid):
|
||||
mod_idx = self.results.modimpl_coefs_df.columns.get_loc(moduleimpl_id)
|
||||
etud_idx = self.results.etud_index[etudid]
|
||||
# moyenne sur les UE:
|
||||
self.results.sem_cube[etud_idx, mod_idx].mean()
|
||||
|
||||
def get_mod_stats(self, moduleimpl_id):
|
||||
return {
|
||||
"moy": "-",
|
||||
"max": "-",
|
||||
"min": "-",
|
||||
"nb_notes": "-",
|
||||
"nb_missing": "-",
|
||||
"nb_valid_evals": "-",
|
||||
}
|
||||
|
||||
def get_evals_in_mod(self, moduleimpl_id):
|
||||
mi = ModuleImpl.query.get(moduleimpl_id)
|
||||
evals_results = []
|
||||
for e in mi.evaluations:
|
||||
d = e.to_dict()
|
||||
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": self.results.modimpls_evals_notes[e.moduleimpl_id][e.id][
|
||||
etud.id
|
||||
],
|
||||
}
|
||||
for etud in self.results.etuds
|
||||
}
|
||||
evals_results.append(d)
|
||||
return evals_results
|
||||
|
@ -5,7 +5,7 @@
|
||||
#
|
||||
# Gestion scolarite IUT
|
||||
#
|
||||
# Copyright (c) 1999 - 2021 Emmanuel Viennet. All rights reserved.
|
||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
@ -108,8 +108,8 @@ def bulletin_but_xml_compat(
|
||||
code_ine=etud.code_ine or "",
|
||||
nom=scu.quote_xml_attr(etud.nom),
|
||||
prenom=scu.quote_xml_attr(etud.prenom),
|
||||
civilite=scu.quote_xml_attr(etud.civilite_str()),
|
||||
sexe=scu.quote_xml_attr(etud.civilite_str()), # compat
|
||||
civilite=scu.quote_xml_attr(etud.civilite_str),
|
||||
sexe=scu.quote_xml_attr(etud.civilite_str), # compat
|
||||
photo_url=scu.quote_xml_attr(sco_photos.get_etud_photo_url(etud.id)),
|
||||
email=scu.quote_xml_attr(etud.get_first_email() or ""),
|
||||
emailperso=scu.quote_xml_attr(etud.get_first_email("emailperso") or ""),
|
||||
@ -216,9 +216,9 @@ def bulletin_but_xml_compat(
|
||||
Element(
|
||||
"note",
|
||||
value=scu.fmt_note(
|
||||
results.modimpls_evals_notes[e.moduleimpl_id][
|
||||
e.id
|
||||
][etud.id]
|
||||
results.modimpls_results[
|
||||
e.moduleimpl_id
|
||||
].evals_notes[e.id][etud.id]
|
||||
),
|
||||
)
|
||||
)
|
||||
@ -314,13 +314,3 @@ def bulletin_but_xml_compat(
|
||||
return None
|
||||
else:
|
||||
return sco_xml.XML_HEADER + ElementTree.tostring(doc).decode(scu.SCO_ENCODING)
|
||||
|
||||
|
||||
"""
|
||||
formsemestre_id=718
|
||||
etudid=12496
|
||||
from app.but.bulletin_but import *
|
||||
mapp.set_sco_dept("RT")
|
||||
sem = FormSemestre.query.get(formsemestre_id)
|
||||
r = ResultatsSemestreBUT(sem)
|
||||
"""
|
||||
|
@ -1,6 +1,6 @@
|
||||
##############################################################################
|
||||
# ScoDoc
|
||||
# Copyright (c) 1999 - 2021 Emmanuel Viennet. All rights reserved.
|
||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||
# See LICENSE
|
||||
##############################################################################
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
##############################################################################
|
||||
# ScoDoc
|
||||
# Copyright (c) 1999 - 2021 Emmanuel Viennet. All rights reserved.
|
||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||
# See LICENSE
|
||||
##############################################################################
|
||||
from xml.etree import ElementTree
|
||||
|
37
app/comp/aux.py
Normal file
37
app/comp/aux.py
Normal file
@ -0,0 +1,37 @@
|
||||
##############################################################################
|
||||
# ScoDoc
|
||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||
# See LICENSE
|
||||
##############################################################################
|
||||
|
||||
import numpy as np
|
||||
|
||||
"""Quelques classes auxiliaires pour les calculs des notes
|
||||
"""
|
||||
|
||||
|
||||
class StatsMoyenne:
|
||||
"""Une moyenne d'un ensemble étudiants sur quelque chose
|
||||
(moyenne générale d'un semestre, d'un module, d'un groupe...)
|
||||
et les statistiques associées: min, max, moy, effectif
|
||||
"""
|
||||
|
||||
def __init__(self, vals):
|
||||
"""Calcul les statistiques.
|
||||
Les valeurs NAN ou non numériques sont toujours enlevées.
|
||||
"""
|
||||
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"
|
||||
return {
|
||||
"min": self.min,
|
||||
"max": self.max,
|
||||
"moy": self.moy,
|
||||
"size": self.size,
|
||||
"nb_vals": self.nb_vals,
|
||||
}
|
@ -5,7 +5,7 @@
|
||||
#
|
||||
# Gestion scolarite IUT
|
||||
#
|
||||
# Copyright (c) 1999 - 2021 Emmanuel Viennet. All rights reserved.
|
||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
@ -43,7 +43,7 @@ class ModuleCoefsCache(sco_cache.ScoDocCache):
|
||||
class EvaluationsPoidsCache(sco_cache.ScoDocCache):
|
||||
"""Cache for poids evals
|
||||
Clé: moduleimpl_id
|
||||
Valeur: DataFrame (df_load_evaluations_poids)
|
||||
Valeur: DataFrame (load_evaluations_poids)
|
||||
"""
|
||||
|
||||
prefix = "EPC"
|
||||
|
@ -5,7 +5,7 @@
|
||||
#
|
||||
# Gestion scolarite IUT
|
||||
#
|
||||
# Copyright (c) 1999 - 2021 Emmanuel Viennet. All rights reserved.
|
||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
@ -27,21 +27,241 @@
|
||||
|
||||
"""Fonctions de calcul des moyennes de modules (modules, ressources ou SAÉ)
|
||||
|
||||
Pour les formations classiques et le BUT
|
||||
|
||||
Rappel: pour éviter les confusions, on appelera *poids* les coefficients d'une
|
||||
évaluation dans un module, et *coefficients* ceux utilisés pour le calcul de la
|
||||
moyenne générale d'une UE.
|
||||
"""
|
||||
from dataclasses import dataclass
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
from pandas.core.frame import DataFrame
|
||||
|
||||
from app import db
|
||||
from app import models
|
||||
from app.models import ModuleImpl, Evaluation, EvaluationUEPoids
|
||||
from app.scodoc import sco_utils as scu
|
||||
|
||||
|
||||
def df_load_evaluations_poids(
|
||||
@dataclass
|
||||
class EvaluationEtat:
|
||||
"""Classe pour stocker quelques infos sur les résultats d'une évaluation"""
|
||||
|
||||
evaluation_id: int
|
||||
nb_attente: int
|
||||
is_complete: bool
|
||||
|
||||
|
||||
class ModuleImplResults:
|
||||
"""Classe commune à toutes les formations (standard et APC).
|
||||
Les notes des étudiants d'un moduleimpl.
|
||||
Les poids des évals sont à part car on en a besoin sans les notes pour les
|
||||
tableaux de bord.
|
||||
Les attributs sont tous des objets simples cachables dans Redis;
|
||||
les caches sont gérés par ResultatsSemestre.
|
||||
"""
|
||||
|
||||
def __init__(self, moduleimpl: ModuleImpl):
|
||||
self.moduleimpl_id = moduleimpl.id
|
||||
self.module_id = moduleimpl.module.id
|
||||
self.etudids = None
|
||||
"liste des étudiants inscrits au SEMESTRE"
|
||||
self.nb_inscrits_module = None
|
||||
"nombre d'inscrits (non DEM) au module"
|
||||
self.evaluations_completes = []
|
||||
"séquence de booléens, indiquant les évals à prendre en compte."
|
||||
self.evaluations_etat = {}
|
||||
"{ evaluation_id: EvaluationEtat }"
|
||||
#
|
||||
self.evals_notes = None
|
||||
"""DataFrame, colonnes: EVALS, Lignes: etudid
|
||||
valeur: notes brutes, float ou NOTES_ATTENTE, NOTES_NEUTRALISE,
|
||||
NOTES_ABSENCE.
|
||||
Les NaN désignent les notes manquantes (non saisies).
|
||||
"""
|
||||
self.etuds_moy_module = None
|
||||
"""DataFrame, colonnes UE, lignes etud
|
||||
= la note de l'étudiant dans chaque UE pour ce module.
|
||||
ou NaN si les évaluations (dans lesquelles l'étudiant a des notes)
|
||||
ne donnent pas de coef vers cette UE.
|
||||
"""
|
||||
self.load_notes()
|
||||
|
||||
def load_notes(self): # ré-écriture de df_load_modimpl_notes
|
||||
"""Charge toutes les notes de toutes les évaluations du module.
|
||||
Dataframe evals_notes
|
||||
colonnes: le nom de la colonne est l'evaluation_id (int)
|
||||
index (lignes): etudid (int)
|
||||
|
||||
L'ensemble des étudiants est celui des inscrits au SEMESTRE.
|
||||
|
||||
Les notes sont "brutes" (séries de floats) et peuvent prendre les valeurs:
|
||||
note : float (valeur enregistrée brute, NON normalisée sur 20)
|
||||
pas de note: NaN (rien en bd, ou étudiant non inscrit au module)
|
||||
absent: NOTES_ABSENCE (NULL en bd)
|
||||
excusé: NOTES_NEUTRALISE (voir sco_utils)
|
||||
attente: NOTES_ATTENTE
|
||||
|
||||
Évaluation "complete" (prise en compte dans les calculs) si:
|
||||
- soit tous les étudiants inscrits au module ont des notes
|
||||
- soit elle a été déclarée "à prise ne compte immédiate" (publish_incomplete)
|
||||
|
||||
Évaluation "attente" (prise en compte dans les calculs, mais il y
|
||||
manque des notes) ssi il y a des étudiants inscrits au semestre et au module
|
||||
qui ont des notes ATT.
|
||||
"""
|
||||
moduleimpl = ModuleImpl.query.get(self.moduleimpl_id)
|
||||
self.etudids = self._etudids()
|
||||
|
||||
# --- Calcul nombre d'inscrits pour déterminer les évaluations "completes":
|
||||
# on prend les inscrits au module ET au semestre (donc sans démissionnaires)
|
||||
inscrits_module = {ins.etud.id for ins in moduleimpl.inscriptions}.intersection(
|
||||
self.etudids
|
||||
)
|
||||
self.nb_inscrits_module = len(inscrits_module)
|
||||
|
||||
# dataFrame vide, index = tous les inscrits au SEMESTRE
|
||||
evals_notes = pd.DataFrame(index=self.etudids, dtype=float)
|
||||
self.evaluations_completes = []
|
||||
for evaluation in moduleimpl.evaluations:
|
||||
eval_df = self._load_evaluation_notes(evaluation)
|
||||
# is_complete ssi tous les inscrits (non dem) au semestre ont une note
|
||||
# ou évaluaton déclarée "à prise en compte immédiate"
|
||||
is_complete = (
|
||||
len(set(eval_df.index).intersection(self.etudids))
|
||||
== self.nb_inscrits_module
|
||||
) or evaluation.publish_incomplete # immédiate
|
||||
self.evaluations_completes.append(is_complete)
|
||||
|
||||
# NULL en base => ABS (= -999)
|
||||
eval_df.fillna(scu.NOTES_ABSENCE, inplace=True)
|
||||
# Ce merge ne garde que les étudiants inscrits au module
|
||||
# et met à NULL les notes non présentes
|
||||
# (notes non saisies ou etuds non inscrits au module):
|
||||
evals_notes = evals_notes.merge(
|
||||
eval_df, how="left", left_index=True, right_index=True
|
||||
)
|
||||
# Notes en attente: (on prend dans evals_notes pour ne pas avoir les dem.)
|
||||
nb_att = sum(evals_notes[str(evaluation.id)] == scu.NOTES_ATTENTE)
|
||||
self.evaluations_etat[evaluation.id] = EvaluationEtat(
|
||||
evaluation_id=evaluation.id, nb_attente=nb_att, is_complete=is_complete
|
||||
)
|
||||
|
||||
# Force columns names to integers (evaluation ids)
|
||||
evals_notes.columns = pd.Int64Index(
|
||||
[int(x) for x in evals_notes.columns], dtype="int"
|
||||
)
|
||||
self.evals_notes = evals_notes
|
||||
|
||||
def _load_evaluation_notes(self, evaluation: Evaluation) -> pd.DataFrame:
|
||||
"""Charge les notes de l'évaluation
|
||||
Resultat: dataframe, index: etudid ayant une note, valeur: note brute.
|
||||
"""
|
||||
eval_df = pd.read_sql_query(
|
||||
"""SELECT n.etudid, n.value AS "%(evaluation_id)s"
|
||||
FROM notes_notes n, notes_moduleimpl_inscription i
|
||||
WHERE evaluation_id=%(evaluation_id)s
|
||||
AND n.etudid = i.etudid
|
||||
AND i.moduleimpl_id = %(moduleimpl_id)s
|
||||
""",
|
||||
db.engine,
|
||||
params={
|
||||
"evaluation_id": evaluation.id,
|
||||
"moduleimpl_id": evaluation.moduleimpl.id,
|
||||
},
|
||||
index_col="etudid",
|
||||
)
|
||||
eval_df[str(evaluation.id)] = pd.to_numeric(eval_df[str(evaluation.id)])
|
||||
return eval_df
|
||||
|
||||
def _etudids(self):
|
||||
"""L'index du dataframe est la liste des étudiants inscrits au semestre,
|
||||
sans les démissionnaires.
|
||||
"""
|
||||
return [
|
||||
e.etudid
|
||||
for e in ModuleImpl.query.get(self.moduleimpl_id).formsemestre.get_inscrits(
|
||||
include_dem=False
|
||||
)
|
||||
]
|
||||
|
||||
def get_evaluations_coefs(self, moduleimpl: ModuleImpl) -> np.array:
|
||||
"""Coefficients des évaluations, met à zéro ceux des évals incomplètes.
|
||||
Résultat: 2d-array of floats, shape (nb_evals, 1)
|
||||
"""
|
||||
return (
|
||||
np.array(
|
||||
[e.coefficient for e in moduleimpl.evaluations],
|
||||
dtype=float,
|
||||
)
|
||||
* self.evaluations_completes
|
||||
).reshape(-1, 1)
|
||||
|
||||
def get_eval_notes_sur_20(self, moduleimpl: ModuleImpl) -> np.array:
|
||||
"""Les notes des évaluations,
|
||||
remplace les ATT, EXC, ABS, NaN par zéro et mets les notes sur 20.
|
||||
Résultat: 2d array of floats, shape nb_etuds x nb_evaluations
|
||||
"""
|
||||
return np.where(
|
||||
self.evals_notes.values > scu.NOTES_ABSENCE, self.evals_notes.values, 0.0
|
||||
) / [e.note_max / 20.0 for e in moduleimpl.evaluations]
|
||||
|
||||
|
||||
class ModuleImplResultsAPC(ModuleImplResults):
|
||||
"Calcul des moyennes de modules à la mode BUT"
|
||||
|
||||
def compute_module_moy(
|
||||
self,
|
||||
evals_poids_df: pd.DataFrame,
|
||||
) -> pd.DataFrame:
|
||||
"""Calcule les moyennes des étudiants dans ce module
|
||||
|
||||
Argument: evals_poids: DataFrame, colonnes: UEs, Lignes: EVALs
|
||||
|
||||
Résultat: DataFrame, colonnes UE, lignes etud
|
||||
= la note de l'étudiant dans chaque UE pour ce module.
|
||||
ou NaN si les évaluations (dans lesquelles l'étudiant a des notes)
|
||||
ne donnent pas de coef vers cette UE.
|
||||
"""
|
||||
moduleimpl = ModuleImpl.query.get(self.moduleimpl_id)
|
||||
nb_etuds, nb_evals = self.evals_notes.shape
|
||||
nb_ues = evals_poids_df.shape[1]
|
||||
assert evals_poids_df.shape[0] == nb_evals # compat notes/poids
|
||||
if nb_etuds == 0:
|
||||
return pd.DataFrame(index=[], columns=evals_poids_df.columns)
|
||||
evals_coefs = self.get_evaluations_coefs(moduleimpl)
|
||||
evals_poids = evals_poids_df.values * evals_coefs
|
||||
# -> evals_poids shape : (nb_evals, nb_ues)
|
||||
assert evals_poids.shape == (nb_evals, nb_ues)
|
||||
evals_notes_20 = self.get_eval_notes_sur_20(moduleimpl)
|
||||
|
||||
# Les poids des évals pour chaque étudiant: là où il a des notes
|
||||
# non neutralisées
|
||||
# (ABS n'est pas neutralisée, mais ATTENTE et NEUTRALISE oui)
|
||||
# Note: les NaN sont remplacés par des 0 dans evals_notes
|
||||
# et dans dans evals_poids_etuds
|
||||
# (rappel: la comparaison est toujours false face à un NaN)
|
||||
# shape: (nb_etuds, nb_evals, nb_ues)
|
||||
poids_stacked = np.stack([evals_poids] * nb_etuds)
|
||||
evals_poids_etuds = np.where(
|
||||
np.stack([self.evals_notes.values] * nb_ues, axis=2) > scu.NOTES_NEUTRALISE,
|
||||
poids_stacked,
|
||||
0,
|
||||
)
|
||||
# Calcule la moyenne pondérée sur les notes disponibles:
|
||||
evals_notes_stacked = np.stack([evals_notes_20] * nb_ues, axis=2)
|
||||
with np.errstate(invalid="ignore"): # ignore les 0/0 (-> NaN)
|
||||
etuds_moy_module = np.sum(
|
||||
evals_poids_etuds * evals_notes_stacked, axis=1
|
||||
) / np.sum(evals_poids_etuds, axis=1)
|
||||
self.etuds_moy_module = pd.DataFrame(
|
||||
etuds_moy_module,
|
||||
index=self.evals_notes.index,
|
||||
columns=evals_poids_df.columns,
|
||||
)
|
||||
return self.etuds_moy_module
|
||||
|
||||
|
||||
def load_evaluations_poids(
|
||||
moduleimpl_id: int, default_poids=1.0
|
||||
) -> tuple[pd.DataFrame, list]:
|
||||
"""Charge poids des évaluations d'un module et retourne un dataframe
|
||||
@ -55,23 +275,25 @@ def df_load_evaluations_poids(
|
||||
ues = modimpl.formsemestre.query_ues(with_sport=False).all()
|
||||
ue_ids = [ue.id for ue in ues]
|
||||
evaluation_ids = [evaluation.id for evaluation in evaluations]
|
||||
df = pd.DataFrame(columns=ue_ids, index=evaluation_ids, dtype=float)
|
||||
for eval_poids in EvaluationUEPoids.query.join(
|
||||
evals_poids = pd.DataFrame(columns=ue_ids, index=evaluation_ids, dtype=float)
|
||||
for ue_poids in EvaluationUEPoids.query.join(
|
||||
EvaluationUEPoids.evaluation
|
||||
).filter_by(moduleimpl_id=moduleimpl_id):
|
||||
df[eval_poids.ue_id][eval_poids.evaluation_id] = eval_poids.poids
|
||||
evals_poids[ue_poids.ue_id][ue_poids.evaluation_id] = ue_poids.poids
|
||||
if default_poids is not None:
|
||||
df.fillna(value=default_poids, inplace=True)
|
||||
return df, ues
|
||||
evals_poids.fillna(value=default_poids, inplace=True)
|
||||
return evals_poids, ues
|
||||
|
||||
|
||||
def check_moduleimpl_conformity(
|
||||
def moduleimpl_is_conforme(
|
||||
moduleimpl, evals_poids: pd.DataFrame, modules_coefficients: pd.DataFrame
|
||||
) -> bool:
|
||||
"""Vérifie que les évaluations de ce moduleimpl sont bien conformes
|
||||
au PN.
|
||||
Un module est dit *conforme* si et seulement si la somme des poids de ses
|
||||
évaluations vers une UE de coefficient non nul est non nulle.
|
||||
|
||||
Argument: evals_poids: DataFrame, colonnes: UEs, Lignes: EVALs
|
||||
"""
|
||||
nb_evals, nb_ues = evals_poids.shape
|
||||
if nb_evals == 0:
|
||||
@ -79,160 +301,52 @@ def check_moduleimpl_conformity(
|
||||
if nb_ues == 0:
|
||||
return False # situation absurde (pas d'UE)
|
||||
if len(modules_coefficients) != nb_ues:
|
||||
raise ValueError("check_moduleimpl_conformity: nb ue incoherent")
|
||||
raise ValueError("moduleimpl_is_conforme: nb ue incoherent")
|
||||
module_evals_poids = evals_poids.transpose().sum(axis=1).to_numpy() != 0
|
||||
check = all(
|
||||
(modules_coefficients[moduleimpl.module.id].to_numpy() != 0)
|
||||
(modules_coefficients[moduleimpl.module_id].to_numpy() != 0)
|
||||
== module_evals_poids
|
||||
)
|
||||
return check
|
||||
|
||||
|
||||
def df_load_modimpl_notes(moduleimpl_id: int) -> tuple:
|
||||
"""Construit un dataframe avec toutes les notes de toutes les évaluations du module.
|
||||
colonnes: le nom de la colonne est l'evaluation_id (int)
|
||||
index (lignes): etudid (int)
|
||||
class ModuleImplResultsClassic(ModuleImplResults):
|
||||
"Calcul des moyennes de modules des formations classiques"
|
||||
|
||||
Résultat: (evals_notes, liste de évaluations du moduleimpl,
|
||||
liste de booleens indiquant si l'évaluation est "complete")
|
||||
|
||||
L'ensemble des étudiants est celui des inscrits au SEMESTRE.
|
||||
|
||||
Les notes renvoyées sont "brutes" (séries de floats) et peuvent prendre les valeurs:
|
||||
note : float (valeur enregistrée brute, non normalisée sur 20)
|
||||
pas de note: NaN (rien en bd, ou étudiant non inscrit au module)
|
||||
absent: NOTES_ABSENCE (NULL en bd)
|
||||
excusé: NOTES_NEUTRALISE (voir sco_utils)
|
||||
attente: NOTES_ATTENTE
|
||||
|
||||
L'évaluation "complete" (prise en compte dans les calculs) si:
|
||||
- soit tous les étudiants inscrits au module ont des notes
|
||||
- soit elle a été déclarée "à prise ne compte immédiate" (publish_incomplete)
|
||||
|
||||
N'utilise pas de cache ScoDoc.
|
||||
"""
|
||||
# L'index du dataframe est la liste des étudiants inscrits au semestre,
|
||||
# sans les démissionnaires
|
||||
etudids = [
|
||||
e.etudid
|
||||
for e in ModuleImpl.query.get(moduleimpl_id).formsemestre.get_inscrits(
|
||||
include_dem=False
|
||||
)
|
||||
]
|
||||
evaluations = Evaluation.query.filter_by(moduleimpl_id=moduleimpl_id).all()
|
||||
# --- Calcul nombre d'inscrits pour détermnier si évaluation "complete":
|
||||
if evaluations:
|
||||
# on prend les inscrits au module ET au semestre (donc sans démissionnaires)
|
||||
inscrits_module = {
|
||||
ins.etud.id for ins in evaluations[0].moduleimpl.inscriptions
|
||||
}.intersection(etudids)
|
||||
nb_inscrits_module = len(inscrits_module)
|
||||
else:
|
||||
nb_inscrits_module = 0
|
||||
# empty df with all students:
|
||||
evals_notes = pd.DataFrame(index=etudids, dtype=float)
|
||||
evaluations_completes = []
|
||||
for evaluation in evaluations:
|
||||
eval_df = pd.read_sql_query(
|
||||
"""SELECT n.etudid, n.value AS "%(evaluation_id)s"
|
||||
FROM notes_notes n, notes_moduleimpl_inscription i
|
||||
WHERE evaluation_id=%(evaluation_id)s
|
||||
AND n.etudid = i.etudid
|
||||
AND i.moduleimpl_id = %(moduleimpl_id)s
|
||||
ORDER BY n.etudid
|
||||
""",
|
||||
db.engine,
|
||||
params={
|
||||
"evaluation_id": evaluation.id,
|
||||
"moduleimpl_id": evaluation.moduleimpl.id,
|
||||
},
|
||||
index_col="etudid",
|
||||
)
|
||||
eval_df[str(evaluation.id)] = pd.to_numeric(eval_df[str(evaluation.id)])
|
||||
# is_complete ssi tous les inscrits (non dem) au semestre ont une note
|
||||
is_complete = (
|
||||
len(set(eval_df.index).intersection(etudids)) == nb_inscrits_module
|
||||
) or evaluation.publish_incomplete
|
||||
evaluations_completes.append(is_complete)
|
||||
# NULL en base => ABS (= -999)
|
||||
eval_df.fillna(scu.NOTES_ABSENCE, inplace=True)
|
||||
# Ce merge met à NULL les élements non présents
|
||||
# (notes non saisies ou etuds non inscrits au module):
|
||||
evals_notes = evals_notes.merge(
|
||||
eval_df, how="left", left_index=True, right_index=True
|
||||
)
|
||||
# Force columns names to integers (evaluation ids)
|
||||
evals_notes.columns = pd.Int64Index(
|
||||
[int(x) for x in evals_notes.columns], dtype="int64"
|
||||
)
|
||||
return evals_notes, evaluations, evaluations_completes
|
||||
|
||||
|
||||
def compute_module_moy(
|
||||
evals_notes_df: pd.DataFrame,
|
||||
evals_poids_df: pd.DataFrame,
|
||||
evaluations: list,
|
||||
evaluations_completes: list,
|
||||
) -> pd.DataFrame:
|
||||
def compute_module_moy(self) -> pd.Series:
|
||||
"""Calcule les moyennes des étudiants dans ce module
|
||||
|
||||
- evals_notes : DataFrame, colonnes: EVALS, Lignes: etudid
|
||||
valeur: notes brutes, float ou NOTES_ATTENTE, NOTES_NEUTRALISE,
|
||||
NOTES_ABSENCE.
|
||||
Les NaN désignent les notes manquantes (non saisies).
|
||||
|
||||
- evals_poids: DataFrame, colonnes: UEs, Lignes: EVALs
|
||||
|
||||
- evaluations: séquence d'évaluations (utilisées pour le coef et
|
||||
le barème)
|
||||
|
||||
- evaluations_completes: séquence de booléens indiquant les
|
||||
évals à prendre en compte.
|
||||
|
||||
Résultat: DataFrame, colonnes UE, lignes etud
|
||||
= la note de l'étudiant dans chaque UE pour ce module.
|
||||
ou NaN si les évaluations (dans lesquelles l'étudiant à des notes)
|
||||
ne donnent pas de coef vers cette UE.
|
||||
Résultat: Series, lignes etud
|
||||
= la note (moyenne) de l'étudiant pour ce module.
|
||||
ou NaN si les évaluations (dans lesquelles l'étudiant a des notes)
|
||||
ne donnent pas de coef.
|
||||
"""
|
||||
nb_etuds, nb_evals = evals_notes_df.shape
|
||||
nb_ues = evals_poids_df.shape[1]
|
||||
assert evals_poids_df.shape[0] == nb_evals # compat notes/poids
|
||||
modimpl = ModuleImpl.query.get(self.moduleimpl_id)
|
||||
nb_etuds, nb_evals = self.evals_notes.shape
|
||||
if nb_etuds == 0:
|
||||
return pd.DataFrame(index=[], columns=evals_poids_df.columns)
|
||||
# Coefficients des évaluations, met à zéro ceux des évals incomplètes:
|
||||
evals_coefs = (
|
||||
np.array(
|
||||
[e.coefficient for e in evaluations],
|
||||
dtype=float,
|
||||
)
|
||||
* evaluations_completes
|
||||
).reshape(-1, 1)
|
||||
evals_poids = evals_poids_df.values * evals_coefs
|
||||
# -> evals_poids shape : (nb_evals, nb_ues)
|
||||
assert evals_poids.shape == (nb_evals, nb_ues)
|
||||
# Remplace les notes ATT, EXC, ABS, NaN par zéro et mets les notes sur 20:
|
||||
evals_notes = np.where(
|
||||
evals_notes_df.values > scu.NOTES_ABSENCE, evals_notes_df.values, 0.0
|
||||
) / [e.note_max / 20.0 for e in evaluations]
|
||||
# Les poids des évals pour les étudiant: là où il a des notes non neutralisées
|
||||
return pd.Series()
|
||||
evals_coefs = self.get_evaluations_coefs(modimpl).reshape(-1)
|
||||
assert evals_coefs.shape == (nb_evals,)
|
||||
evals_notes_20 = self.get_eval_notes_sur_20(modimpl)
|
||||
# Les coefs des évals pour chaque étudiant: là où il a des notes
|
||||
# non neutralisées
|
||||
# (ABS n'est pas neutralisée, mais ATTENTE et NEUTRALISE oui)
|
||||
# Note: les NaN sont remplacés par des 0 dans evals_notes
|
||||
# et dans dans evals_poids_etuds
|
||||
# (rappel: la comparaison est toujours false face à un NaN)
|
||||
# shape: (nb_etuds, nb_evals, nb_ues)
|
||||
poids_stacked = np.stack([evals_poids] * nb_etuds)
|
||||
evals_poids_etuds = np.where(
|
||||
np.stack([evals_notes_df.values] * nb_ues, axis=2) > scu.NOTES_NEUTRALISE,
|
||||
poids_stacked,
|
||||
0,
|
||||
# (rappel: la comparaison est toujours False face à un NaN)
|
||||
# shape: (nb_etuds, nb_evals)
|
||||
coefs_stacked = np.stack([evals_coefs] * nb_etuds)
|
||||
evals_coefs_etuds = np.where(
|
||||
self.evals_notes.values > scu.NOTES_NEUTRALISE, coefs_stacked, 0
|
||||
)
|
||||
# Calcule la moyenne pondérée sur les notes disponibles:
|
||||
evals_notes_stacked = np.stack([evals_notes] * nb_ues, axis=2)
|
||||
with np.errstate(invalid="ignore"): # ignore les 0/0 (-> NaN)
|
||||
etuds_moy_module = np.sum(
|
||||
evals_poids_etuds * evals_notes_stacked, axis=1
|
||||
) / np.sum(evals_poids_etuds, axis=1)
|
||||
etuds_moy_module_df = pd.DataFrame(
|
||||
etuds_moy_module, index=evals_notes_df.index, columns=evals_poids_df.columns
|
||||
evals_coefs_etuds * evals_notes_20, axis=1
|
||||
) / np.sum(evals_coefs_etuds, axis=1)
|
||||
|
||||
self.etuds_moy_module = pd.Series(
|
||||
etuds_moy_module,
|
||||
index=self.evals_notes.index,
|
||||
)
|
||||
return etuds_moy_module_df
|
||||
return self.etuds_moy_module
|
||||
|
@ -5,7 +5,7 @@
|
||||
#
|
||||
# Gestion scolarite IUT
|
||||
#
|
||||
# Copyright (c) 1999 - 2021 Emmanuel Viennet. All rights reserved.
|
||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
@ -31,7 +31,7 @@ import numpy as np
|
||||
import pandas as pd
|
||||
|
||||
|
||||
def compute_sem_moys(etud_moy_ue_df, modimpl_coefs_df):
|
||||
def compute_sem_moys_apc(etud_moy_ue_df, modimpl_coefs_df):
|
||||
"""Calcule la moyenne générale indicative
|
||||
= moyenne des moyennes d'UE, pondérée par la somme de leurs coefs
|
||||
|
||||
|
@ -5,7 +5,7 @@
|
||||
#
|
||||
# Gestion scolarite IUT
|
||||
#
|
||||
# Copyright (c) 1999 - 2021 Emmanuel Viennet. All rights reserved.
|
||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
@ -25,7 +25,7 @@
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
"""Fonctions de calcul des moyennes d'UE
|
||||
"""Fonctions de calcul des moyennes d'UE (classiques ou BUT)
|
||||
"""
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
@ -39,9 +39,9 @@ from app.scodoc import sco_codes_parcours
|
||||
|
||||
|
||||
def df_load_module_coefs(formation_id: int, semestre_idx: int = None) -> pd.DataFrame:
|
||||
"""Charge les coefs des modules de la formation pour le semestre indiqué.
|
||||
"""Charge les coefs APC des modules de la formation pour le semestre indiqué.
|
||||
|
||||
Ces coefs lient les modules à chaque UE.
|
||||
En APC, ces coefs lient les modules à chaque UE.
|
||||
|
||||
Résultat: (module_coefs_df, ues, modules)
|
||||
DataFrame rows = UEs, columns = modules, value = coef.
|
||||
@ -86,7 +86,7 @@ def df_load_module_coefs(formation_id: int, semestre_idx: int = None) -> pd.Data
|
||||
def df_load_modimpl_coefs(
|
||||
formsemestre: models.FormSemestre, ues=None, modimpls=None
|
||||
) -> pd.DataFrame:
|
||||
"""Charge les coefs des modules du formsemestre indiqué.
|
||||
"""Charge les coefs APC des modules du formsemestre indiqué.
|
||||
|
||||
Comme df_load_module_coefs mais prend seulement les UE
|
||||
et modules du formsemestre.
|
||||
@ -127,45 +127,32 @@ def notes_sem_assemble_cube(modimpls_notes: list[pd.DataFrame]) -> np.ndarray:
|
||||
return modimpls_notes.swapaxes(0, 1)
|
||||
|
||||
|
||||
def notes_sem_load_cube(formsemestre):
|
||||
def notes_sem_load_cube(formsemestre: FormSemestre) -> tuple:
|
||||
"""Calcule le cube des notes du semestre
|
||||
(charge toutes les notes, calcule les moyenne des modules
|
||||
et assemble le cube)
|
||||
Resultat:
|
||||
sem_cube : ndarray (etuds x modimpls x UEs)
|
||||
modimpls_evals_poids dict { modimpl.id : evals_poids }
|
||||
modimpls_evals_notes dict { modimpl.id : evals_notes }
|
||||
modimpls_evaluations dict { modimpl.id : liste des évaluations }
|
||||
modimpls_evaluations_complete: {modimpl_id : liste de booleens (complete/non)}
|
||||
modimpls_results dict { modimpl.id : ModuleImplResultsAPC }
|
||||
"""
|
||||
modimpls_results = {}
|
||||
modimpls_evals_poids = {}
|
||||
modimpls_evals_notes = {}
|
||||
modimpls_evaluations = {}
|
||||
modimpls_evaluations_complete = {}
|
||||
modimpls_notes = []
|
||||
for modimpl in formsemestre.modimpls:
|
||||
evals_notes, evaluations, evaluations_completes = moy_mod.df_load_modimpl_notes(
|
||||
modimpl.id
|
||||
)
|
||||
evals_poids, ues = moy_mod.df_load_evaluations_poids(modimpl.id)
|
||||
etuds_moy_module = moy_mod.compute_module_moy(
|
||||
evals_notes, evals_poids, evaluations, evaluations_completes
|
||||
)
|
||||
modimpls_evals_poids[modimpl.id] = evals_poids
|
||||
modimpls_evals_notes[modimpl.id] = evals_notes
|
||||
modimpls_evaluations[modimpl.id] = evaluations
|
||||
modimpls_evaluations_complete[modimpl.id] = evaluations_completes
|
||||
mod_results = moy_mod.ModuleImplResultsAPC(modimpl)
|
||||
evals_poids, _ = moy_mod.load_evaluations_poids(modimpl.id)
|
||||
etuds_moy_module = mod_results.compute_module_moy(evals_poids)
|
||||
modimpls_results[modimpl.id] = mod_results
|
||||
modimpls_notes.append(etuds_moy_module)
|
||||
return (
|
||||
notes_sem_assemble_cube(modimpls_notes),
|
||||
modimpls_evals_poids,
|
||||
modimpls_evals_notes,
|
||||
modimpls_evaluations,
|
||||
modimpls_evaluations_complete,
|
||||
modimpls_results,
|
||||
)
|
||||
|
||||
|
||||
def compute_ue_moys(
|
||||
def compute_ue_moys_apc(
|
||||
sem_cube: np.array,
|
||||
etuds: list,
|
||||
modimpls: list,
|
||||
@ -173,7 +160,7 @@ def compute_ue_moys(
|
||||
modimpl_inscr_df: pd.DataFrame,
|
||||
modimpl_coefs_df: pd.DataFrame,
|
||||
) -> pd.DataFrame:
|
||||
"""Calcul de la moyenne d'UE
|
||||
"""Calcul de la moyenne d'UE en mode APC (BUT).
|
||||
La moyenne d'UE est un nombre (note/20), ou NI ou NA ou ERR
|
||||
NI non inscrit à (au moins un) module de cette UE
|
||||
NA pas de notes disponibles
|
||||
@ -182,11 +169,11 @@ def compute_ue_moys(
|
||||
sem_cube: notes moyennes aux modules
|
||||
ndarray (etuds x modimpls x UEs)
|
||||
(floats avec des NaN)
|
||||
etuds : lites des étudiants (dim. 0 du cube)
|
||||
etuds : listes des étudiants (dim. 0 du cube)
|
||||
modimpls : liste des modules à considérer (dim. 1 du cube)
|
||||
ues : liste des UE (dim. 2 du cube)
|
||||
module_inscr_df: matrice d'inscription du semestre (etud x modimpl)
|
||||
module_coefs_df: matrice coefficients (UE x modimpl)
|
||||
modimpl_inscr_df: matrice d'inscription du semestre (etud x modimpl)
|
||||
modimpl_coefs_df: matrice coefficients (UE x modimpl)
|
||||
|
||||
Resultat: DataFrame columns UE, rows etudid
|
||||
"""
|
||||
@ -228,3 +215,70 @@ def compute_ue_moys(
|
||||
return pd.DataFrame(
|
||||
etud_moy_ue, index=modimpl_inscr_df.index, columns=modimpl_coefs_df.index
|
||||
)
|
||||
|
||||
|
||||
def compute_ue_moys_classic(
|
||||
formsemestre: FormSemestre,
|
||||
sem_matrix: np.array,
|
||||
ues: list,
|
||||
modimpl_inscr_df: pd.DataFrame,
|
||||
modimpl_coefs: np.array,
|
||||
) -> pd.DataFrame:
|
||||
"""Calcul de la moyenne d'UE en mode classique.
|
||||
La moyenne d'UE est un nombre (note/20), ou NI ou NA ou ERR
|
||||
NI non inscrit à (au moins un) module de cette UE
|
||||
NA pas de notes disponibles
|
||||
ERR erreur dans une formule utilisateur. [XXX pas encore gérées ici]
|
||||
|
||||
sem_matrix: notes moyennes aux modules
|
||||
ndarray (etuds x modimpls)
|
||||
(floats avec des NaN)
|
||||
etuds : listes des étudiants (dim. 0 de la matrice)
|
||||
ues : liste des UE
|
||||
modimpl_inscr_df: matrice d'inscription du semestre (etud x modimpl)
|
||||
modimpl_coefs: vecteur des coefficients de modules
|
||||
|
||||
Résultat:
|
||||
- moyennes générales: pd.Series, index etudid
|
||||
- moyennes d'UE: DataFrame columns UE, rows etudid
|
||||
"""
|
||||
nb_etuds, nb_modules = sem_matrix.shape
|
||||
assert len(modimpl_coefs) == nb_modules
|
||||
nb_ues = len(ues)
|
||||
modimpl_inscr = modimpl_inscr_df.values
|
||||
# Enlève les NaN du numérateur:
|
||||
sem_matrix_no_nan = np.nan_to_num(sem_matrix, nan=0.0)
|
||||
# Ne prend pas en compte les notes des étudiants non inscrits au module:
|
||||
# Annule les notes:
|
||||
sem_matrix_inscrits = np.where(modimpl_inscr, sem_matrix_no_nan, 0.0)
|
||||
# Annule les coefs des modules où l'étudiant n'est pas inscrit:
|
||||
modimpl_coefs_etuds = np.where(
|
||||
modimpl_inscr, np.stack([modimpl_coefs.T] * nb_etuds), 0.0
|
||||
)
|
||||
# Annule les coefs des modules NaN (nb_etuds x nb_mods)
|
||||
modimpl_coefs_etuds_no_nan = np.where(
|
||||
np.isnan(sem_matrix), 0.0, modimpl_coefs_etuds
|
||||
)
|
||||
# Calcul des moyennes générales:
|
||||
with np.errstate(invalid="ignore"): # ignore les 0/0 (-> NaN)
|
||||
etud_moy_gen = np.sum(
|
||||
modimpl_coefs_etuds_no_nan * sem_matrix_inscrits, axis=1
|
||||
) / np.sum(modimpl_coefs_etuds_no_nan, axis=1)
|
||||
etud_moy_gen_s = pd.Series(etud_moy_gen, index=modimpl_inscr_df.index)
|
||||
# Calcul des moyennes d'UE
|
||||
ue_modules = np.array(
|
||||
[[m.module.ue == ue for m in formsemestre.modimpls] for ue in ues]
|
||||
)[..., np.newaxis]
|
||||
modimpl_coefs_etuds_no_nan_stacked = np.stack(
|
||||
[modimpl_coefs_etuds_no_nan.T] * nb_ues
|
||||
)
|
||||
# nb_ue x nb_etuds x nb_mods : coefs prenant en compte NaN et inscriptions
|
||||
coefs = (modimpl_coefs_etuds_no_nan_stacked * ue_modules).swapaxes(1, 2)
|
||||
with np.errstate(invalid="ignore"): # ignore les 0/0 (-> NaN)
|
||||
etud_moy_ue = (
|
||||
np.sum(coefs * sem_matrix_inscrits, axis=2) / np.sum(coefs, axis=2)
|
||||
).T
|
||||
etud_moy_ue_df = pd.DataFrame(
|
||||
etud_moy_ue, index=modimpl_inscr_df.index, columns=[ue.id for ue in ues]
|
||||
)
|
||||
return etud_moy_gen_s, etud_moy_ue_df
|
||||
|
65
app/comp/res_but.py
Normal file
65
app/comp/res_but.py
Normal file
@ -0,0 +1,65 @@
|
||||
##############################################################################
|
||||
# ScoDoc
|
||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||
# See LICENSE
|
||||
##############################################################################
|
||||
|
||||
"""Résultats semestres BUT
|
||||
"""
|
||||
|
||||
from app.comp import moy_ue, moy_sem, inscr_mod
|
||||
from app.comp.res_sem import NotesTableCompat
|
||||
|
||||
|
||||
class ResultatsSemestreBUT(NotesTableCompat):
|
||||
"""Résultats BUT: organisation des calculs"""
|
||||
|
||||
_cached_attrs = NotesTableCompat._cached_attrs + (
|
||||
"modimpl_coefs_df",
|
||||
"modimpls_evals_poids",
|
||||
"sem_cube",
|
||||
)
|
||||
|
||||
def __init__(self, formsemestre):
|
||||
super().__init__(formsemestre)
|
||||
|
||||
if not self.load_cached():
|
||||
self.compute()
|
||||
self.store()
|
||||
|
||||
def compute(self):
|
||||
"Charge les notes et inscriptions et calcule les moyennes d'UE et gen."
|
||||
(
|
||||
self.sem_cube,
|
||||
self.modimpls_evals_poids,
|
||||
self.modimpls_results,
|
||||
) = moy_ue.notes_sem_load_cube(self.formsemestre)
|
||||
self.modimpl_inscr_df = inscr_mod.df_load_modimpl_inscr(self.formsemestre)
|
||||
self.modimpl_coefs_df, _, _ = moy_ue.df_load_modimpl_coefs(
|
||||
self.formsemestre, ues=self.ues, modimpls=self.modimpls
|
||||
)
|
||||
# l'idx de la colonne du mod modimpl.id est
|
||||
# modimpl_coefs_df.columns.get_loc(modimpl.id)
|
||||
# idx de l'UE: modimpl_coefs_df.index.get_loc(ue.id)
|
||||
self.etud_moy_ue = moy_ue.compute_ue_moys_apc(
|
||||
self.sem_cube,
|
||||
self.etuds,
|
||||
self.modimpls,
|
||||
self.ues,
|
||||
self.modimpl_inscr_df,
|
||||
self.modimpl_coefs_df,
|
||||
)
|
||||
self.etud_moy_gen = moy_sem.compute_sem_moys_apc(
|
||||
self.etud_moy_ue, self.modimpl_coefs_df
|
||||
)
|
||||
self.etud_moy_gen_ranks = moy_sem.comp_ranks_series(self.etud_moy_gen)
|
||||
|
||||
def get_etud_mod_moy(self, moduleimpl_id: int, etudid: int) -> float:
|
||||
"""La moyenne de l'étudiant dans le moduleimpl
|
||||
En APC, il s'agit d'une moyenne indicative sans valeur.
|
||||
Result: valeur float (peut être naN) ou chaîne "NI" (non inscrit ou DEM)
|
||||
"""
|
||||
mod_idx = self.modimpl_coefs_df.columns.get_loc(moduleimpl_id)
|
||||
etud_idx = self.etud_index[etudid]
|
||||
# moyenne sur les UE:
|
||||
return self.sem_cube[etud_idx, mod_idx].mean()
|
95
app/comp/res_classic.py
Normal file
95
app/comp/res_classic.py
Normal file
@ -0,0 +1,95 @@
|
||||
##############################################################################
|
||||
# ScoDoc
|
||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||
# See LICENSE
|
||||
##############################################################################
|
||||
|
||||
"""Résultats semestres classiques (non APC)
|
||||
"""
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
from app.comp import moy_mod, moy_ue, moy_sem, inscr_mod
|
||||
from app.comp.res_sem import NotesTableCompat
|
||||
from app.models.formsemestre import FormSemestre
|
||||
|
||||
|
||||
class ResultatsSemestreClassic(NotesTableCompat):
|
||||
"""Résultats du semestre (formation classique): organisation des calculs."""
|
||||
|
||||
_cached_attrs = NotesTableCompat._cached_attrs + (
|
||||
"modimpl_coefs",
|
||||
"modimpl_idx",
|
||||
"sem_matrix",
|
||||
)
|
||||
|
||||
def __init__(self, formsemestre):
|
||||
super().__init__(formsemestre)
|
||||
|
||||
if not self.load_cached():
|
||||
self.compute()
|
||||
self.store()
|
||||
# recalculé (aussi rapide que de les cacher)
|
||||
self.moy_min = self.etud_moy_gen.min()
|
||||
self.moy_max = self.etud_moy_gen.max()
|
||||
self.moy_moy = self.etud_moy_gen.mean()
|
||||
|
||||
def compute(self):
|
||||
"Charge les notes et inscriptions et calcule les moyennes d'UE et gen."
|
||||
self.sem_matrix, self.modimpls_results = notes_sem_load_matrix(
|
||||
self.formsemestre
|
||||
)
|
||||
self.modimpl_inscr_df = inscr_mod.df_load_modimpl_inscr(self.formsemestre)
|
||||
self.modimpl_coefs = np.array(
|
||||
[m.module.coefficient for m in self.formsemestre.modimpls]
|
||||
)
|
||||
self.modimpl_idx = {m.id: i for i, m in enumerate(self.formsemestre.modimpls)}
|
||||
"l'idx de la colonne du mod modimpl.id est modimpl_idx[modimpl.id]"
|
||||
|
||||
self.etud_moy_gen, self.etud_moy_ue = moy_ue.compute_ue_moys_classic(
|
||||
self.formsemestre,
|
||||
self.sem_matrix,
|
||||
self.ues,
|
||||
self.modimpl_inscr_df,
|
||||
self.modimpl_coefs,
|
||||
)
|
||||
self.etud_moy_gen_ranks = moy_sem.comp_ranks_series(self.etud_moy_gen)
|
||||
|
||||
def get_etud_mod_moy(self, moduleimpl_id: int, etudid: int) -> float:
|
||||
"""La moyenne de l'étudiant dans le moduleimpl
|
||||
Result: valeur float (peut être NaN) ou chaîne "NI" (non inscrit ou DEM)
|
||||
"""
|
||||
return self.modimpls_results[moduleimpl_id].etuds_moy_module.get(etudid, "NI")
|
||||
|
||||
|
||||
def notes_sem_load_matrix(formsemestre: FormSemestre) -> tuple:
|
||||
"""Calcule la matrice des notes du semestre
|
||||
(charge toutes les notes, calcule les moyenne des modules
|
||||
et assemble la matrice)
|
||||
Resultat:
|
||||
sem_matrix : 2d-array (etuds x modimpls)
|
||||
modimpls_results dict { modimpl.id : ModuleImplResultsClassic }
|
||||
"""
|
||||
modimpls_results = {}
|
||||
modimpls_notes = []
|
||||
for modimpl in formsemestre.modimpls:
|
||||
mod_results = moy_mod.ModuleImplResultsClassic(modimpl)
|
||||
etuds_moy_module = mod_results.compute_module_moy()
|
||||
modimpls_results[modimpl.id] = mod_results
|
||||
modimpls_notes.append(etuds_moy_module)
|
||||
return (
|
||||
notes_sem_assemble_matrix(modimpls_notes),
|
||||
modimpls_results,
|
||||
)
|
||||
|
||||
|
||||
def notes_sem_assemble_matrix(modimpls_notes: list[pd.Series]) -> np.ndarray:
|
||||
"""Réuni les notes moyennes des modules du semestre en une matrice
|
||||
|
||||
modimpls_notes : liste des moyennes de module
|
||||
(Series rendus par compute_module_moy, index: etud)
|
||||
Resultat: ndarray (etud x module)
|
||||
"""
|
||||
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)
|
||||
return modimpls_notes.T
|
337
app/comp/res_sem.py
Normal file
337
app/comp/res_sem.py
Normal file
@ -0,0 +1,337 @@
|
||||
##############################################################################
|
||||
# ScoDoc
|
||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||
# See LICENSE
|
||||
##############################################################################
|
||||
|
||||
from collections import defaultdict
|
||||
from functools import cached_property
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
from app.comp.aux import StatsMoyenne
|
||||
from app.models import FormSemestre, ModuleImpl
|
||||
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
|
||||
|
||||
# Il faut bien distinguer
|
||||
# - ce qui est caché de façon persistente (via redis):
|
||||
# ce sont les attributs listés dans `_cached_attrs`
|
||||
# le stockage et l'invalidation sont gérés dans sco_cache.py
|
||||
#
|
||||
# - les valeurs cachées durant le temps d'une requête
|
||||
# (durée de vie de l'instance de ResultatsSemestre)
|
||||
# qui sont notamment les attributs décorés par `@cached_property``
|
||||
#
|
||||
class ResultatsSemestre:
|
||||
_cached_attrs = (
|
||||
"etud_moy_gen_ranks",
|
||||
"etud_moy_gen",
|
||||
"etud_moy_ue",
|
||||
"modimpl_inscr_df",
|
||||
"modimpls_results",
|
||||
)
|
||||
|
||||
def __init__(self, formsemestre: FormSemestre):
|
||||
self.formsemestre = formsemestre
|
||||
# BUT ou standard ? (apc == "approche par compétences")
|
||||
self.is_apc = formsemestre.formation.is_apc()
|
||||
# Attributs "virtuels", définis pas les sous-classes
|
||||
# ResultatsSemestreBUT ou ResultatsSemestreStd
|
||||
self.etud_moy_ue = {}
|
||||
self.etud_moy_gen = {}
|
||||
self.etud_moy_gen_ranks = {}
|
||||
# TODO
|
||||
|
||||
def load_cached(self) -> bool:
|
||||
"Load cached dataframes, returns False si pas en cache"
|
||||
data = ResultatsSemestreCache.get(self.formsemestre.id)
|
||||
if not data:
|
||||
return False
|
||||
for attr in self._cached_attrs:
|
||||
setattr(self, attr, data[attr])
|
||||
return True
|
||||
|
||||
def store(self):
|
||||
"Cache our data"
|
||||
ResultatsSemestreCache.set(
|
||||
self.formsemestre.id,
|
||||
{attr: getattr(self, attr) for attr in self._cached_attrs},
|
||||
)
|
||||
|
||||
def compute(self):
|
||||
"Charge les notes et inscriptions et calcule toutes les moyennes"
|
||||
# voir ce qui est chargé / calculé ici et dans les sous-classes
|
||||
raise NotImplementedError()
|
||||
|
||||
@cached_property
|
||||
def etuds(self):
|
||||
"Liste des inscrits au semestre, sans les démissionnaires"
|
||||
# nb: si la liste des inscrits change, ResultatsSemestre devient invalide
|
||||
return self.formsemestre.get_inscrits(include_dem=False)
|
||||
|
||||
@cached_property
|
||||
def etud_index(self):
|
||||
"dict { etudid : indice dans les inscrits }"
|
||||
return {e.id: idx for idx, e in enumerate(self.etuds)}
|
||||
|
||||
@cached_property
|
||||
def ues(self):
|
||||
"Liste des UE du semestre"
|
||||
return self.formsemestre.query_ues().all()
|
||||
|
||||
@cached_property
|
||||
def modimpls(self):
|
||||
"""Liste des modimpls du semestre
|
||||
- triée par numéro de module en APC
|
||||
- triée par numéros d'UE/matières/modules pour les formations standard.
|
||||
"""
|
||||
modimpls = self.formsemestre.modimpls.all()
|
||||
if self.is_apc:
|
||||
modimpls.sort(key=lambda m: (m.module.numero, m.module.code))
|
||||
else:
|
||||
modimpls.sort(
|
||||
key=lambda m: (
|
||||
m.module.ue.numero,
|
||||
m.module.matiere.numero,
|
||||
m.module.numero,
|
||||
m.module.code,
|
||||
)
|
||||
)
|
||||
return modimpls
|
||||
|
||||
@cached_property
|
||||
def ressources(self):
|
||||
"Liste des ressources du semestre, triées par numéro de module"
|
||||
return [
|
||||
m for m in self.modimpls if m.module.module_type == scu.ModuleType.RESSOURCE
|
||||
]
|
||||
|
||||
@cached_property
|
||||
def saes(self):
|
||||
"Liste des SAÉs du semestre, triées par numéro de module"
|
||||
return [m for m in self.modimpls if m.module.module_type == scu.ModuleType.SAE]
|
||||
|
||||
|
||||
# Pour raccorder le code des anciens codes qui attendent une NoteTable
|
||||
class NotesTableCompat(ResultatsSemestre):
|
||||
"""Implementation partielle de NotesTable WIP TODO
|
||||
|
||||
Les méthodes définies dans cette classe sont là
|
||||
pour conserver la compatibilité abvec les codes anciens et
|
||||
il n'est pas recommandé de les utiliser dans de nouveaux
|
||||
développements (API malcommode et peu efficace).
|
||||
"""
|
||||
|
||||
_cached_attrs = ResultatsSemestre._cached_attrs + ()
|
||||
|
||||
def __init__(self, formsemestre: FormSemestre):
|
||||
super().__init__(formsemestre)
|
||||
nb_etuds = len(self.etuds)
|
||||
self.bonus = defaultdict(lambda: 0.0) # XXX TODO
|
||||
self.ue_rangs = {u.id: (defaultdict(lambda: 0.0), nb_etuds) for u in self.ues}
|
||||
self.mod_rangs = {
|
||||
m.id: (defaultdict(lambda: 0), nb_etuds) for m in self.modimpls
|
||||
}
|
||||
self.moy_min = "NA"
|
||||
self.moy_max = "NA"
|
||||
|
||||
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
|
||||
"""
|
||||
# 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 inscrlist(self) -> list[dict]: # utilisé par PE seulement
|
||||
"""Liste de dict etud, avec démissionnaires
|
||||
classée dans l'ordre alphabétique de noms.
|
||||
"""
|
||||
etuds = self.formsemestre.get_inscrits(include_dem=True)
|
||||
etuds.sort(key=lambda e: e.sort_key)
|
||||
return [e.to_dict_scodoc7() for e in etuds]
|
||||
|
||||
@cached_property
|
||||
def stats_moy_gen(self):
|
||||
"""Stats (moy/min/max) sur la moyenne générale"""
|
||||
return StatsMoyenne(self.etud_moy_gen)
|
||||
|
||||
def get_ues_stat_dict(self, filter_sport=False): # was get_ues()
|
||||
"""Liste des UEs, ordonnée par numero.
|
||||
Si filter_sport, retire les UE de type SPORT.
|
||||
Résultat: liste de dicts { champs UE U stats moyenne UE }
|
||||
"""
|
||||
ues = []
|
||||
for ue in self.ues:
|
||||
if filter_sport and ue.type == UE_SPORT:
|
||||
continue
|
||||
d = ue.to_dict()
|
||||
d.update(StatsMoyenne(self.etud_moy_ue[ue.id]).to_dict())
|
||||
ues.append(d)
|
||||
return ues
|
||||
|
||||
def get_modimpls_dict(self, ue_id=None):
|
||||
"""Liste des modules pour une UE (ou toutes si ue_id==None),
|
||||
triés par numéros (selon le type de formation)
|
||||
"""
|
||||
if ue_id is None:
|
||||
return [m.to_dict() for m in self.modimpls]
|
||||
else:
|
||||
return [m.to_dict() for m in self.modimpls if m.module.ue.id == ue_id]
|
||||
|
||||
def get_etud_decision_sem(self, etudid: int) -> dict:
|
||||
"""Decision du jury prise pour cet etudiant, ou None s'il n'y en pas eu.
|
||||
{ 'code' : None|ATT|..., 'assidu' : 0|1, 'event_date' : , compense_formsemestre_id }
|
||||
Si état défaillant, force le code a DEF
|
||||
"""
|
||||
if self.get_etud_etat(etudid) == DEF:
|
||||
return {
|
||||
"code": DEF,
|
||||
"assidu": False,
|
||||
"event_date": "",
|
||||
"compense_formsemestre_id": None,
|
||||
}
|
||||
else:
|
||||
return {
|
||||
"code": ATT, # XXX TODO
|
||||
"assidu": True, # XXX TODO
|
||||
"event_date": "",
|
||||
"compense_formsemestre_id": None,
|
||||
}
|
||||
|
||||
def get_etud_etat(self, etudid: int) -> str:
|
||||
"Etat de l'etudiant: 'I', 'D', DEF ou '' (si pas connu dans ce semestre)"
|
||||
ins = self.formsemestre.etuds_inscriptions.get(etudid, None)
|
||||
if ins is None:
|
||||
return ""
|
||||
return ins.etat
|
||||
|
||||
def get_etud_moy_gen(self, etudid): # -> float | str
|
||||
"""Moyenne générale de cet etudiant dans ce semestre.
|
||||
Prend en compte les UE capitalisées. (TODO)
|
||||
Si apc, moyenne indicative.
|
||||
Si pas de notes: 'NA'
|
||||
"""
|
||||
return self.etud_moy_gen[etudid]
|
||||
|
||||
def get_etud_mod_moy(self, moduleimpl_id: int, etudid: int) -> float:
|
||||
"""La moyenne de l'étudiant dans le moduleimpl
|
||||
En APC, il s'agira d'une moyenne indicative sans valeur.
|
||||
Result: valeur float (peut être naN) ou chaîne "NI" (non inscrit ou DEM)
|
||||
"""
|
||||
raise NotImplementedError() # virtual method
|
||||
|
||||
def get_etud_ue_status(self, etudid: int, ue_id: int):
|
||||
return {
|
||||
"cur_moy_ue": self.results.etud_moy_ue[ue_id][etudid],
|
||||
"is_capitalized": False, # XXX TODO
|
||||
}
|
||||
|
||||
def get_etud_rang(self, etudid: int):
|
||||
return self.etud_moy_gen_ranks.get(etudid, 99999) # XXX
|
||||
|
||||
def get_etud_rang_group(self, etudid: int, group_id: int):
|
||||
return (None, 0) # XXX unimplemented TODO
|
||||
|
||||
def get_evals_in_mod(self, moduleimpl_id: int) -> list[dict]:
|
||||
"liste des évaluations valides dans un module"
|
||||
mi = ModuleImpl.query.get(moduleimpl_id)
|
||||
evals_results = []
|
||||
for e in mi.evaluations:
|
||||
d = e.to_dict()
|
||||
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": self.results.modimpls_evals_notes[e.moduleimpl_id][e.id][
|
||||
etud.id
|
||||
],
|
||||
}
|
||||
for etud in self.results.etuds
|
||||
}
|
||||
evals_results.append(d)
|
||||
return evals_results
|
||||
|
||||
def get_moduleimpls_attente(self):
|
||||
return [] # XXX TODO
|
||||
|
||||
def get_mod_stats(self, moduleimpl_id):
|
||||
return {
|
||||
"moy": "-",
|
||||
"max": "-",
|
||||
"min": "-",
|
||||
"nb_notes": "-",
|
||||
"nb_missing": "-",
|
||||
"nb_valid_evals": "-",
|
||||
}
|
||||
|
||||
def get_nom_short(self, etudid):
|
||||
"formatte nom d'un etud (pour table recap)"
|
||||
etud = self.identdict[etudid]
|
||||
return (
|
||||
(etud["nom_usuel"] or etud["nom"]).upper()
|
||||
+ " "
|
||||
+ etud["prenom"].capitalize()[:2]
|
||||
+ "."
|
||||
)
|
||||
|
||||
@cached_property
|
||||
def T(self):
|
||||
return self.get_table_moyennes_triees()
|
||||
|
||||
def get_table_moyennes_triees(self) -> list:
|
||||
"""Result: liste de tuples
|
||||
moy_gen, moy_ue_0, ..., moy_ue_n, moy_mod1, ..., moy_mod_n, etudid
|
||||
"""
|
||||
table_moyennes = []
|
||||
etuds_inscriptions = self.formsemestre.etuds_inscriptions
|
||||
|
||||
for etudid in etuds_inscriptions:
|
||||
moy_gen = self.etud_moy_gen.get(etudid, False)
|
||||
if moy_gen is False:
|
||||
# pas de moyenne: démissionnaire ou def
|
||||
t = ["-"] + ["0.00"] * len(self.ues) + ["NI"] * len(self.modimpls)
|
||||
else:
|
||||
moy_ues = self.etud_moy_ue.loc[etudid]
|
||||
t = [moy_gen] + list(moy_ues)
|
||||
# TODO UE capitalisées: ne pas afficher moyennes modules
|
||||
for modimpl in self.modimpls:
|
||||
val = self.get_etud_mod_moy(modimpl.id, etudid)
|
||||
t.append(val)
|
||||
t.append(etudid)
|
||||
table_moyennes.append(t)
|
||||
# tri par moyennes décroissantes,
|
||||
# en laissant les démissionnaires à la fin, par ordre alphabetique
|
||||
etuds = [ins.etud for ins in etuds_inscriptions.values()]
|
||||
etuds.sort(key=lambda e: e.sort_key)
|
||||
self._rang_alpha = {e.id: i for i, e in enumerate(etuds)}
|
||||
table_moyennes.sort(key=self._row_key)
|
||||
return table_moyennes
|
||||
|
||||
def _row_key(self, x):
|
||||
"""clé de tri par moyennes décroissantes,
|
||||
en laissant les demissionnaires à la fin, par ordre alphabetique.
|
||||
(moy_gen, rang_alpha)
|
||||
"""
|
||||
try:
|
||||
moy = -float(x[0])
|
||||
except (ValueError, TypeError):
|
||||
moy = 1000.0
|
||||
return (moy, self._rang_alpha[x[-1]])
|
||||
|
||||
@cached_property
|
||||
def identdict(self) -> dict:
|
||||
"""{ etudid : etud_dict } pour tous les inscrits au semestre"""
|
||||
return {
|
||||
ins.etud.id: ins.etud.to_dict_scodoc7()
|
||||
for ins in self.formsemestre.inscriptions
|
||||
}
|
@ -1,15 +1,46 @@
|
||||
<<<<<<< HEAD
|
||||
=======
|
||||
# -*- mode: python -*-
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
##############################################################################
|
||||
#
|
||||
# Gestion scolarite IUT
|
||||
#
|
||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
import re
|
||||
import requests
|
||||
|
||||
>>>>>>> e0be0f8feef8cfb7f5da4fe516b33529ec1712c9
|
||||
from flask_wtf import FlaskForm
|
||||
from markupsafe import Markup
|
||||
import requests, re
|
||||
from wtforms import StringField, SubmitField, TextAreaField, SelectField, HiddenField
|
||||
from wtforms.fields.html5 import EmailField, DateField
|
||||
from flask_wtf.file import FileField, FileAllowed, FileRequired
|
||||
from markupsafe import Markup
|
||||
from sqlalchemy import text
|
||||
from wtforms import StringField, SubmitField, TextAreaField, SelectField, HiddenField
|
||||
from wtforms.fields import EmailField, DateField
|
||||
from wtforms.validators import ValidationError, DataRequired, Email
|
||||
|
||||
from app.entreprises.models import Entreprise, EntrepriseContact
|
||||
from app.models import Identite
|
||||
from app.auth.models import User
|
||||
from app.scodoc import sco_etud
|
||||
from sqlalchemy import text
|
||||
|
||||
CHAMP_REQUIS = "Ce champ est requis"
|
||||
|
||||
|
@ -5,7 +5,7 @@
|
||||
#
|
||||
# ScoDoc
|
||||
#
|
||||
# Copyright (c) 1999 - 2021 Emmanuel Viennet. All rights reserved.
|
||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
|
@ -5,7 +5,7 @@
|
||||
#
|
||||
# ScoDoc
|
||||
#
|
||||
# Copyright (c) 1999 - 2021 Emmanuel Viennet. All rights reserved.
|
||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
|
@ -4,9 +4,6 @@
|
||||
"""
|
||||
|
||||
from app import db
|
||||
from app.models import APO_CODE_STR_LEN
|
||||
from app.models import SHORT_STR_LEN
|
||||
from app.models import CODE_STR_LEN
|
||||
|
||||
|
||||
class Absence(db.Model):
|
||||
|
@ -1,6 +1,6 @@
|
||||
##############################################################################
|
||||
# ScoDoc
|
||||
# Copyright (c) 1999 - 2021 Emmanuel Viennet. All rights reserved.
|
||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||
# See LICENSE
|
||||
##############################################################################
|
||||
"""ScoDoc 9 models : Référentiel Compétence BUT 2021
|
||||
|
@ -4,13 +4,14 @@
|
||||
et données rattachées (adresses, annotations, ...)
|
||||
"""
|
||||
|
||||
from flask import g, url_for
|
||||
from functools import cached_property
|
||||
from flask import abort, url_for
|
||||
from flask import g, request
|
||||
|
||||
from app import db
|
||||
from app import models
|
||||
from app.models import APO_CODE_STR_LEN
|
||||
from app.models import SHORT_STR_LEN
|
||||
from app.models import CODE_STR_LEN
|
||||
|
||||
from app.scodoc import notesdb as ndb
|
||||
|
||||
|
||||
class Identite(db.Model):
|
||||
@ -53,14 +54,24 @@ class Identite(db.Model):
|
||||
def __repr__(self):
|
||||
return f"<Etud {self.id} {self.nom} {self.prenom}>"
|
||||
|
||||
@classmethod
|
||||
def from_request(cls, etudid=None, code_nip=None):
|
||||
"""Etudiant à partir de l'etudid ou du code_nip, soit
|
||||
passés en argument soit retrouvés directement dans la requête web.
|
||||
Erreur 404 si inexistant.
|
||||
"""
|
||||
args = make_etud_args(etudid=etudid, code_nip=code_nip)
|
||||
return Identite.query.filter_by(**args).first_or_404()
|
||||
|
||||
@property
|
||||
def civilite_str(self):
|
||||
"""returns 'M.' ou 'Mme' ou '' (pour le genre neutre,
|
||||
personnes ne souhaitant pas d'affichage).
|
||||
"""
|
||||
return {"M": "M.", "F": "Mme", "X": ""}[self.civilite]
|
||||
|
||||
def nom_disp(self):
|
||||
"nom à afficher"
|
||||
def nom_disp(self) -> str:
|
||||
"Nom à afficher"
|
||||
if self.nom_usuel:
|
||||
return (
|
||||
(self.nom_usuel + " (" + self.nom + ")") if self.nom else self.nom_usuel
|
||||
@ -68,10 +79,50 @@ class Identite(db.Model):
|
||||
else:
|
||||
return self.nom
|
||||
|
||||
@cached_property
|
||||
def nomprenom(self, reverse=False) -> str:
|
||||
"""Civilité/nom/prenom pour affichages: "M. Pierre Dupont"
|
||||
Si reverse, "Dupont Pierre", sans civilité.
|
||||
"""
|
||||
nom = self.nom_usuel or self.nom
|
||||
prenom = self.prenom_str
|
||||
if reverse:
|
||||
fields = (nom, prenom)
|
||||
else:
|
||||
fields = (self.civilite_str, prenom, nom)
|
||||
return " ".join([x for x in fields if x])
|
||||
|
||||
@property
|
||||
def prenom_str(self):
|
||||
"""Prénom à afficher. Par exemple: "Jean-Christophe" """
|
||||
if not self.prenom:
|
||||
return ""
|
||||
frags = self.prenom.split()
|
||||
r = []
|
||||
for frag in frags:
|
||||
fields = frag.split("-")
|
||||
r.append("-".join([x.lower().capitalize() for x in fields]))
|
||||
return " ".join(r)
|
||||
|
||||
@cached_property
|
||||
def sort_key(self) -> tuple:
|
||||
"clé pour tris par ordre alphabétique"
|
||||
return (self.nom_usuel or self.nom).lower(), self.prenom.lower()
|
||||
|
||||
def get_first_email(self, field="email") -> str:
|
||||
"le mail associé à la première adrese de l'étudiant, ou None"
|
||||
"Le mail associé à la première adrese de l'étudiant, ou None"
|
||||
return self.adresses[0].email or None if self.adresses.count() > 0 else None
|
||||
|
||||
def to_dict_scodoc7(self):
|
||||
"""Représentation dictionnaire,
|
||||
compatible ScoDoc7 mais sans infos admission
|
||||
"""
|
||||
e = dict(self.__dict__)
|
||||
e.pop("_sa_instance_state", None)
|
||||
# ScoDoc7 output_formators: (backward compat)
|
||||
e["date_naissance"] = ndb.DateISOtoDMY(e["date_naissance"])
|
||||
return {k: e[k] or "" for k in e} # convert_null_outputs_to_empty
|
||||
|
||||
def to_dict_bul(self, include_urls=True):
|
||||
"""Infos exportées dans les bulletins"""
|
||||
from app.scodoc import sco_photos
|
||||
@ -131,6 +182,42 @@ class Identite(db.Model):
|
||||
return False
|
||||
|
||||
|
||||
def make_etud_args(
|
||||
etudid=None, code_nip=None, use_request=True, raise_exc=False, abort_404=True
|
||||
) -> dict:
|
||||
"""forme args dict pour requete recherche etudiant
|
||||
On peut specifier etudid
|
||||
ou bien (si use_request) cherche dans la requete http: etudid, code_nip, code_ine
|
||||
(dans cet ordre).
|
||||
|
||||
Résultat: dict avec soit "etudid", soit "code_nip", soit "code_ine"
|
||||
"""
|
||||
args = None
|
||||
if etudid:
|
||||
args = {"etudid": etudid}
|
||||
elif code_nip:
|
||||
args = {"code_nip": code_nip}
|
||||
elif use_request: # use form from current request (Flask global)
|
||||
if request.method == "POST":
|
||||
vals = request.form
|
||||
elif request.method == "GET":
|
||||
vals = request.args
|
||||
else:
|
||||
vals = {}
|
||||
if "etudid" in vals:
|
||||
args = {"etudid": int(vals["etudid"])}
|
||||
elif "code_nip" in vals:
|
||||
args = {"code_nip": str(vals["code_nip"])}
|
||||
elif "code_ine" in vals:
|
||||
args = {"code_ine": str(vals["code_ine"])}
|
||||
if not args:
|
||||
if abort_404:
|
||||
abort(404, "pas d'étudiant sélectionné")
|
||||
elif raise_exc:
|
||||
raise ValueError("make_etud_args: pas d'étudiant sélectionné !")
|
||||
return args
|
||||
|
||||
|
||||
class Adresse(db.Model):
|
||||
"""Adresse d'un étudiant
|
||||
(le modèle permet plusieurs adresses, mais l'UI n'en gère qu'une seule)
|
||||
|
@ -4,9 +4,6 @@
|
||||
"""
|
||||
|
||||
from app import db
|
||||
from app.models import APO_CODE_STR_LEN
|
||||
from app.models import SHORT_STR_LEN
|
||||
from app.models import CODE_STR_LEN
|
||||
from app.models import UniteEns
|
||||
|
||||
import app.scodoc.notesdb as ndb
|
||||
|
@ -4,9 +4,7 @@
|
||||
"""
|
||||
|
||||
from app import db
|
||||
from app.models import APO_CODE_STR_LEN
|
||||
from app.models import SHORT_STR_LEN
|
||||
from app.models import CODE_STR_LEN
|
||||
|
||||
|
||||
class Scolog(db.Model):
|
||||
|
@ -3,6 +3,7 @@
|
||||
"""ScoDoc models: formsemestre
|
||||
"""
|
||||
import datetime
|
||||
from functools import cached_property
|
||||
|
||||
import flask_sqlalchemy
|
||||
|
||||
@ -84,7 +85,11 @@ class FormSemestre(db.Model):
|
||||
etapes = db.relationship(
|
||||
"FormSemestreEtape", cascade="all,delete", backref="formsemestre"
|
||||
)
|
||||
modimpls = db.relationship("ModuleImpl", backref="formsemestre", lazy="dynamic")
|
||||
modimpls = db.relationship(
|
||||
"ModuleImpl",
|
||||
backref="formsemestre",
|
||||
lazy="dynamic",
|
||||
)
|
||||
etuds = db.relationship(
|
||||
"Identite",
|
||||
secondary="notes_formsemestre_inscription",
|
||||
@ -247,7 +252,7 @@ class FormSemestre(db.Model):
|
||||
etudid, self.date_debut.isoformat(), self.date_fin.isoformat()
|
||||
)
|
||||
|
||||
def get_inscrits(self, include_dem=False) -> list:
|
||||
def get_inscrits(self, include_dem=False) -> list[Identite]:
|
||||
"""Liste des étudiants inscrits à ce semestre
|
||||
Si all, tous les étudiants, avec les démissionnaires.
|
||||
"""
|
||||
@ -256,6 +261,11 @@ class FormSemestre(db.Model):
|
||||
else:
|
||||
return [ins.etud for ins in self.inscriptions if ins.etat == scu.INSCRIT]
|
||||
|
||||
@cached_property
|
||||
def etuds_inscriptions(self) -> dict:
|
||||
"""Map { etudid : inscription }"""
|
||||
return {ins.etud.id: ins for ins in self.inscriptions}
|
||||
|
||||
|
||||
# Association id des utilisateurs responsables (aka directeurs des etudes) du semestre
|
||||
notes_formsemestre_responsables = db.Table(
|
||||
|
@ -5,9 +5,7 @@
|
||||
from typing import Any
|
||||
|
||||
from app import db
|
||||
from app.models import APO_CODE_STR_LEN
|
||||
from app.models import SHORT_STR_LEN
|
||||
from app.models import CODE_STR_LEN
|
||||
from app.models import GROUPNAME_STR_LEN
|
||||
|
||||
|
||||
|
@ -50,7 +50,7 @@ class ModuleImpl(db.Model):
|
||||
if evaluations_poids is None:
|
||||
from app.comp import moy_mod
|
||||
|
||||
evaluations_poids, _ = moy_mod.df_load_evaluations_poids(self.id)
|
||||
evaluations_poids, _ = moy_mod.load_evaluations_poids(self.id)
|
||||
df_cache.EvaluationsPoidsCache.set(self.id, evaluations_poids)
|
||||
return evaluations_poids
|
||||
|
||||
@ -69,7 +69,7 @@ class ModuleImpl(db.Model):
|
||||
return True
|
||||
from app.comp import moy_mod
|
||||
|
||||
return moy_mod.check_moduleimpl_conformity(
|
||||
return moy_mod.moduleimpl_is_conforme(
|
||||
self,
|
||||
self.get_evaluations_poids(),
|
||||
self.module.formation.get_module_coefs(self.module.semestre_id),
|
||||
|
@ -4,7 +4,6 @@
|
||||
"""
|
||||
|
||||
from app import db
|
||||
from app.models import APO_CODE_STR_LEN
|
||||
from app.models import SHORT_STR_LEN
|
||||
from app.models import CODE_STR_LEN
|
||||
|
||||
|
@ -5,7 +5,7 @@
|
||||
#
|
||||
# Gestion scolarite IUT
|
||||
#
|
||||
# Copyright (c) 1999 - 2021 Emmanuel Viennet. All rights reserved.
|
||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
|
@ -5,7 +5,7 @@
|
||||
#
|
||||
# Gestion scolarite IUT
|
||||
#
|
||||
# Copyright (c) 1999 - 2021 Emmanuel Viennet. All rights reserved.
|
||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
|
@ -5,7 +5,7 @@
|
||||
#
|
||||
# Gestion scolarite IUT
|
||||
#
|
||||
# Copyright (c) 1999 - 2021 Emmanuel Viennet. All rights reserved.
|
||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
|
@ -5,7 +5,7 @@
|
||||
#
|
||||
# Gestion scolarite IUT
|
||||
#
|
||||
# Copyright (c) 1999 - 2021 Emmanuel Viennet. All rights reserved.
|
||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
|
@ -5,7 +5,7 @@
|
||||
#
|
||||
# Gestion scolarite IUT
|
||||
#
|
||||
# Copyright (c) 1999 - 2021 Emmanuel Viennet. All rights reserved.
|
||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
@ -68,7 +68,7 @@ class TableTag(object):
|
||||
self.taglist = []
|
||||
|
||||
self.resultats = {}
|
||||
self.rangs = {}
|
||||
self.etud_moy_gen_ranks = {}
|
||||
self.statistiques = {}
|
||||
|
||||
# *****************************************************************************************************************
|
||||
@ -117,15 +117,15 @@ class TableTag(object):
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------
|
||||
def get_moy_from_stats(self, tag):
|
||||
""" Renvoie la moyenne des notes calculées pour d'un tag donné"""
|
||||
"""Renvoie la moyenne des notes calculées pour d'un tag donné"""
|
||||
return self.statistiques[tag][0] if tag in self.statistiques else None
|
||||
|
||||
def get_min_from_stats(self, tag):
|
||||
""" Renvoie la plus basse des notes calculées pour d'un tag donné"""
|
||||
"""Renvoie la plus basse des notes calculées pour d'un tag donné"""
|
||||
return self.statistiques[tag][1] if tag in self.statistiques else None
|
||||
|
||||
def get_max_from_stats(self, tag):
|
||||
""" Renvoie la plus haute des notes calculées pour d'un tag donné"""
|
||||
"""Renvoie la plus haute des notes calculées pour d'un tag donné"""
|
||||
return self.statistiques[tag][2] if tag in self.statistiques else None
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------
|
||||
@ -236,7 +236,7 @@ class TableTag(object):
|
||||
return moystr
|
||||
|
||||
def str_res_d_un_etudiant(self, etudid, delim=";"):
|
||||
"""Renvoie sur une ligne les résultats d'un étudiant à tous les tags (par ordre alphabétique). """
|
||||
"""Renvoie sur une ligne les résultats d'un étudiant à tous les tags (par ordre alphabétique)."""
|
||||
return delim.join(
|
||||
[self.str_resTag_d_un_etudiant(tag, etudid) for tag in self.get_all_tags()]
|
||||
)
|
||||
@ -256,7 +256,7 @@ class TableTag(object):
|
||||
# -----------------------------------------------------------------------
|
||||
|
||||
def str_tagtable(self, delim=";", decimal_sep=","):
|
||||
"""Renvoie une chaine de caractère listant toutes les moyennes, les rangs des étudiants pour tous les tags. """
|
||||
"""Renvoie une chaine de caractère listant toutes les moyennes, les rangs des étudiants pour tous les tags."""
|
||||
entete = ["etudid", "nom", "prenom"]
|
||||
for tag in self.get_all_tags():
|
||||
entete += [titre + "_" + tag for titre in ["note", "rang", "nb_inscrit"]]
|
||||
|
@ -5,7 +5,7 @@
|
||||
#
|
||||
# Gestion scolarite IUT
|
||||
#
|
||||
# Copyright (c) 1999 - 2021 Emmanuel Viennet. All rights reserved.
|
||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
|
@ -5,7 +5,7 @@
|
||||
#
|
||||
# Gestion scolarite IUT
|
||||
#
|
||||
# Copyright (c) 1999 - 2021 Emmanuel Viennet. All rights reserved.
|
||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
|
@ -5,7 +5,7 @@
|
||||
#
|
||||
# Gestion scolarite IUT
|
||||
#
|
||||
# Copyright (c) 1999 - 2021 Emmanuel Viennet. All rights reserved.
|
||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
|
@ -5,7 +5,7 @@
|
||||
#
|
||||
# Gestion scolarite IUT
|
||||
#
|
||||
# Copyright (c) 1999 - 2021 Emmanuel Viennet. All rights reserved.
|
||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
|
@ -5,7 +5,7 @@
|
||||
#
|
||||
# Gestion scolarite IUT
|
||||
#
|
||||
# Copyright (c) 1999 - 2021 Emmanuel Viennet. All rights reserved.
|
||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
|
@ -5,7 +5,7 @@
|
||||
#
|
||||
# Gestion scolarite IUT
|
||||
#
|
||||
# Copyright (c) 1999 - 2021 Emmanuel Viennet. All rights reserved.
|
||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
|
@ -5,7 +5,7 @@
|
||||
#
|
||||
# Gestion scolarite IUT
|
||||
#
|
||||
# Copyright (c) 1999 - 2021 Emmanuel Viennet. All rights reserved.
|
||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
|
@ -5,7 +5,7 @@
|
||||
#
|
||||
# Gestion scolarite IUT
|
||||
#
|
||||
# Copyright (c) 1999 - 2021 Emmanuel Viennet. All rights reserved.
|
||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
|
@ -5,7 +5,7 @@
|
||||
#
|
||||
# Gestion scolarite IUT
|
||||
#
|
||||
# Copyright (c) 1999 - 2021 Emmanuel Viennet. All rights reserved.
|
||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
@ -25,7 +25,9 @@
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
"""Calculs sur les notes et cache des resultats
|
||||
"""Calculs sur les notes et cache des résultats
|
||||
|
||||
Ancien code ScoDoc 7 en cours de rénovation
|
||||
"""
|
||||
|
||||
from operator import itemgetter
|
||||
@ -102,7 +104,7 @@ def comp_ranks(T):
|
||||
|
||||
def get_sem_ues_modimpls(formsemestre_id, modimpls=None):
|
||||
"""Get liste des UE du semestre (à partir des moduleimpls)
|
||||
(utilisé quand on ne peut pas construire nt et faire nt.get_ues())
|
||||
(utilisé quand on ne peut pas construire nt et faire nt.get_ues_stat_dict())
|
||||
"""
|
||||
if modimpls is None:
|
||||
modimpls = sco_moduleimpl.moduleimpl_list(formsemestre_id=formsemestre_id)
|
||||
@ -200,7 +202,7 @@ class NotesTable:
|
||||
self.inscrlist.sort(key=itemgetter("nomp"))
|
||||
|
||||
# { etudid : rang dans l'ordre alphabetique }
|
||||
self.rang_alpha = {e["etudid"]: i for i, e in enumerate(self.inscrlist)}
|
||||
self._rang_alpha = {e["etudid"]: i for i, e in enumerate(self.inscrlist)}
|
||||
|
||||
self.bonus = scu.DictDefault(defaultvalue=0)
|
||||
# Notes dans les modules { moduleimpl_id : { etudid: note_moyenne_dans_ce_module } }
|
||||
@ -294,7 +296,7 @@ class NotesTable:
|
||||
for ue in self._ues:
|
||||
is_cap[ue["ue_id"]] = ue_status[ue["ue_id"]]["is_capitalized"]
|
||||
|
||||
for modimpl in self.get_modimpls():
|
||||
for modimpl in self.get_modimpls_dict():
|
||||
val = self.get_etud_mod_moy(modimpl["moduleimpl_id"], etudid)
|
||||
if is_cap[modimpl["module"]["ue_id"]]:
|
||||
t.append("-c-")
|
||||
@ -316,7 +318,7 @@ class NotesTable:
|
||||
self.moy_min = self.moy_max = "NA"
|
||||
|
||||
# calcul rangs (/ moyenne generale)
|
||||
self.rangs = comp_ranks(T)
|
||||
self.etud_moy_gen_ranks = comp_ranks(T)
|
||||
|
||||
self.rangs_groupes = (
|
||||
{}
|
||||
@ -364,7 +366,7 @@ class NotesTable:
|
||||
moy = -float(x[0])
|
||||
except (ValueError, TypeError):
|
||||
moy = 1000.0
|
||||
return (moy, self.rang_alpha[x[-1]])
|
||||
return (moy, self._rang_alpha[x[-1]])
|
||||
|
||||
def get_etudids(self, sorted=False):
|
||||
if sorted:
|
||||
@ -417,46 +419,17 @@ class NotesTable:
|
||||
else:
|
||||
return ' <font color="red">(%s)</font> ' % etat
|
||||
|
||||
def get_ues(self, filter_sport=False, filter_non_inscrit=False, etudid=None):
|
||||
"""liste des ue, ordonnée par numero.
|
||||
Si filter_non_inscrit, retire les UE dans lesquelles l'etudiant n'est
|
||||
inscrit à aucun module.
|
||||
def get_ues_stat_dict(self, filter_sport=False): # was get_ues()
|
||||
"""Liste des UEs, ordonnée par numero.
|
||||
Si filter_sport, retire les UE de type SPORT
|
||||
"""
|
||||
if not filter_sport and not filter_non_inscrit:
|
||||
if not filter_sport:
|
||||
return self._ues
|
||||
|
||||
if filter_sport:
|
||||
ues_src = [ue for ue in self._ues if ue["type"] != UE_SPORT]
|
||||
else:
|
||||
ues_src = self._ues
|
||||
if not filter_non_inscrit:
|
||||
return ues_src
|
||||
ues = []
|
||||
for ue in ues_src:
|
||||
if self.get_etud_ue_status(etudid, ue["ue_id"])["is_capitalized"]:
|
||||
# garde toujours les UE capitalisees
|
||||
has_note = True
|
||||
else:
|
||||
has_note = False
|
||||
# verifie que l'etud. est inscrit a au moins un module de l'UE
|
||||
# (en fait verifie qu'il a une note)
|
||||
modimpls = self.get_modimpls(ue["ue_id"])
|
||||
return [ue for ue in self._ues if ue["type"] != UE_SPORT]
|
||||
|
||||
for modi in modimpls:
|
||||
moy = self.get_etud_mod_moy(modi["moduleimpl_id"], etudid)
|
||||
try:
|
||||
float(moy)
|
||||
has_note = True
|
||||
break
|
||||
except:
|
||||
pass
|
||||
if has_note:
|
||||
ues.append(ue)
|
||||
return ues
|
||||
|
||||
def get_modimpls(self, ue_id=None):
|
||||
"liste des modules pour une UE (ou toutes si ue_id==None), triés par matières."
|
||||
def get_modimpls_dict(self, ue_id=None):
|
||||
"Liste des modules pour une UE (ou toutes si ue_id==None), triés par matières."
|
||||
if ue_id is None:
|
||||
r = self._modimpls
|
||||
else:
|
||||
@ -522,7 +495,7 @@ class NotesTable:
|
||||
|
||||
Les moyennes d'UE ne tiennent pas compte des capitalisations.
|
||||
"""
|
||||
ues = self.get_ues()
|
||||
ues = self.get_ues_stat_dict()
|
||||
sum_moy = 0 # la somme des moyennes générales valides
|
||||
nb_moy = 0 # le nombre de moyennes générales valides
|
||||
for ue in ues:
|
||||
@ -561,9 +534,9 @@ class NotesTable:
|
||||
i = 0
|
||||
for ue in ues:
|
||||
i += 1
|
||||
ue["nb_moy"] = len(ue["_notes"])
|
||||
if ue["nb_moy"] > 0:
|
||||
ue["moy"] = sum(ue["_notes"]) / ue["nb_moy"]
|
||||
ue["nb_vals"] = len(ue["_notes"])
|
||||
if ue["nb_vals"] > 0:
|
||||
ue["moy"] = sum(ue["_notes"]) / ue["nb_vals"]
|
||||
ue["max"] = max(ue["_notes"])
|
||||
ue["min"] = min(ue["_notes"])
|
||||
else:
|
||||
@ -591,7 +564,7 @@ class NotesTable:
|
||||
Si non inscrit, moy == 'NI' et sum_coefs==0
|
||||
"""
|
||||
assert ue_id
|
||||
modimpls = self.get_modimpls(ue_id)
|
||||
modimpls = self.get_modimpls_dict(ue_id)
|
||||
nb_notes = 0 # dans cette UE
|
||||
sum_notes = 0.0
|
||||
sum_coefs = 0.0
|
||||
@ -767,7 +740,7 @@ class NotesTable:
|
||||
sem_ects_pot_fond = 0.0
|
||||
sem_ects_pot_pro = 0.0
|
||||
|
||||
for ue in self.get_ues():
|
||||
for ue in self.get_ues_stat_dict():
|
||||
# - On calcule la moyenne d'UE courante:
|
||||
if not block_computation:
|
||||
mu = self.comp_etud_moy_ue(etudid, ue_id=ue["ue_id"], cnx=cnx)
|
||||
@ -948,7 +921,7 @@ class NotesTable:
|
||||
|
||||
return infos
|
||||
|
||||
def get_etud_moy_gen(self, etudid):
|
||||
def get_etud_moy_gen(self, etudid): # -> float | str
|
||||
"""Moyenne generale de cet etudiant dans ce semestre.
|
||||
Prend en compte les UE capitalisées.
|
||||
Si pas de notes: 'NA'
|
||||
@ -981,7 +954,7 @@ class NotesTable:
|
||||
return self.T
|
||||
|
||||
def get_etud_rang(self, etudid) -> str:
|
||||
return self.rangs.get(etudid, "999")
|
||||
return self.etud_moy_gen_ranks.get(etudid, "999")
|
||||
|
||||
def get_etud_rang_group(self, etudid, group_id):
|
||||
"""Returns rank of etud in this group and number of etuds in group.
|
||||
@ -1347,7 +1320,7 @@ class NotesTable:
|
||||
|
||||
# Rappel des épisodes précédents: T est une liste de liste
|
||||
# Colonnes: 0 moy_gen, moy_ue1, ..., moy_ue_n, moy_mod1, ..., moy_mod_n, etudid
|
||||
ues = self.get_ues() # incluant le(s) UE de sport
|
||||
ues = self.get_ues_stat_dict() # incluant le(s) UE de sport
|
||||
for t in self.T:
|
||||
etudid = t[-1]
|
||||
if etudid in results.etud_moy_gen: # evite les démissionnaires
|
||||
@ -1358,4 +1331,4 @@ class NotesTable:
|
||||
# re-trie selon la nouvelle moyenne générale:
|
||||
self.T.sort(key=self._row_key)
|
||||
# Remplace aussi le rang:
|
||||
self.rangs = results.etud_moy_gen_ranks
|
||||
self.etud_moy_gen_ranks = results.etud_moy_gen_ranks
|
||||
|
@ -5,7 +5,7 @@
|
||||
#
|
||||
# Gestion scolarite IUT
|
||||
#
|
||||
# Copyright (c) 1999 - 2021 Emmanuel Viennet. All rights reserved.
|
||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
|
@ -265,8 +265,11 @@ def DBUpdateArgs(cnx, table, vals, where=None, commit=False, convert_empty_to_nu
|
||||
cursor.execute(req, vals)
|
||||
# log('req=%s\n'%req)
|
||||
# log('vals=%s\n'%vals)
|
||||
except psycopg2.errors.StringDataRightTruncation:
|
||||
cnx.rollback()
|
||||
raise ScoValueError("champs de texte trop long !")
|
||||
except:
|
||||
cnx.commit() # get rid of this transaction
|
||||
cnx.rollback() # get rid of this transaction
|
||||
log('Exception in DBUpdateArgs:\n\treq="%s"\n\tvals="%s"\n' % (req, vals))
|
||||
raise # and re-raise exception
|
||||
if commit:
|
||||
|
@ -5,7 +5,7 @@
|
||||
#
|
||||
# Gestion scolarite IUT
|
||||
#
|
||||
# Copyright (c) 1999 - 2021 Emmanuel Viennet. All rights reserved.
|
||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
|
@ -5,7 +5,7 @@
|
||||
#
|
||||
# Gestion scolarite IUT
|
||||
#
|
||||
# Copyright (c) 1999 - 2021 Emmanuel Viennet. All rights reserved.
|
||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
|
@ -5,7 +5,7 @@
|
||||
#
|
||||
# Gestion scolarite IUT
|
||||
#
|
||||
# Copyright (c) 1999 - 2021 Emmanuel Viennet. All rights reserved.
|
||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
|
@ -5,7 +5,7 @@
|
||||
#
|
||||
# Gestion scolarite IUT
|
||||
#
|
||||
# Copyright (c) 1999 - 2021 Emmanuel Viennet. All rights reserved.
|
||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
@ -32,6 +32,8 @@ import datetime
|
||||
|
||||
from flask import url_for, g, request, abort
|
||||
|
||||
from app import log
|
||||
from app.models import Identite
|
||||
import app.scodoc.sco_utils as scu
|
||||
from app.scodoc import notesdb as ndb
|
||||
from app.scodoc.scolog import logdb
|
||||
@ -46,7 +48,6 @@ from app.scodoc import sco_groups
|
||||
from app.scodoc import sco_moduleimpl
|
||||
from app.scodoc import sco_photos
|
||||
from app.scodoc import sco_preferences
|
||||
from app import log
|
||||
from app.scodoc.sco_exceptions import ScoValueError
|
||||
|
||||
|
||||
@ -71,8 +72,8 @@ def doSignaleAbsence(
|
||||
etudid: etudiant concerné. Si non spécifié, cherche dans
|
||||
les paramètres de la requête courante.
|
||||
"""
|
||||
etud = sco_etud.get_etud_info(filled=True, etudid=etudid)[0]
|
||||
etudid = etud["etudid"]
|
||||
etud = Identite.from_request(etudid)
|
||||
|
||||
if not moduleimpl_id:
|
||||
moduleimpl_id = None
|
||||
description_abs = description
|
||||
@ -82,7 +83,7 @@ def doSignaleAbsence(
|
||||
for jour in dates:
|
||||
if demijournee == 2:
|
||||
sco_abs.add_absence(
|
||||
etudid,
|
||||
etud.id,
|
||||
jour,
|
||||
False,
|
||||
estjust,
|
||||
@ -90,7 +91,7 @@ def doSignaleAbsence(
|
||||
moduleimpl_id,
|
||||
)
|
||||
sco_abs.add_absence(
|
||||
etudid,
|
||||
etud.id,
|
||||
jour,
|
||||
True,
|
||||
estjust,
|
||||
@ -100,7 +101,7 @@ def doSignaleAbsence(
|
||||
nbadded += 2
|
||||
else:
|
||||
sco_abs.add_absence(
|
||||
etudid,
|
||||
etud.id,
|
||||
jour,
|
||||
demijournee,
|
||||
estjust,
|
||||
@ -113,27 +114,27 @@ def doSignaleAbsence(
|
||||
J = ""
|
||||
else:
|
||||
J = "NON "
|
||||
M = ""
|
||||
indication_module = ""
|
||||
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)
|
||||
ues = nt.get_ues(etudid=etudid)
|
||||
ues = nt.get_ues_stat_dict()
|
||||
for ue in ues:
|
||||
modimpls = nt.get_modimpls(ue_id=ue["ue_id"])
|
||||
modimpls = nt.get_modimpls_dict(ue_id=ue["ue_id"])
|
||||
for modimpl in modimpls:
|
||||
if modimpl["moduleimpl_id"] == moduleimpl_id:
|
||||
M = "dans le module %s" % modimpl["module"]["code"]
|
||||
indication_module = "dans le module %s" % modimpl["module"]["code"]
|
||||
H = [
|
||||
html_sco_header.sco_header(
|
||||
page_title="Signalement d'une absence pour %(nomprenom)s" % etud,
|
||||
page_title=f"Signalement d'une absence pour {etud.nomprenom}",
|
||||
),
|
||||
"""<h2>Signalement d'absences</h2>""",
|
||||
]
|
||||
if dates:
|
||||
H.append(
|
||||
"""<p>Ajout de %d absences <b>%sjustifiées</b> du %s au %s %s</p>"""
|
||||
% (nbadded, J, datedebut, datefin, M)
|
||||
% (nbadded, J, datedebut, datefin, indication_module)
|
||||
)
|
||||
else:
|
||||
H.append(
|
||||
@ -142,11 +143,18 @@ def doSignaleAbsence(
|
||||
)
|
||||
|
||||
H.append(
|
||||
"""<ul><li><a href="SignaleAbsenceEtud?etudid=%(etudid)s">Autre absence pour <b>%(nomprenom)s</b></a></li>
|
||||
<li><a href="CalAbs?etudid=%(etudid)s">Calendrier de ses absences</a></li>
|
||||
f"""<ul>
|
||||
<li><a href="{url_for("absences.SignaleAbsenceEtud",
|
||||
scodoc_dept=g.scodoc_dept, etudid=etud.id
|
||||
)}">Autre absence pour <b>{etud.nomprenom}</b></a>
|
||||
</li>
|
||||
<li><a href="{url_for("absences.CalAbs",
|
||||
scodoc_dept=g.scodoc_dept, etudid=etud.id
|
||||
)}">Calendrier de ses absences</a>
|
||||
</li>
|
||||
</ul>
|
||||
<hr>"""
|
||||
% etud
|
||||
<hr>
|
||||
"""
|
||||
)
|
||||
H.append(sco_find_etud.form_search_etud())
|
||||
H.append(html_sco_header.sco_footer())
|
||||
@ -175,7 +183,7 @@ def SignaleAbsenceEtud(): # etudid implied
|
||||
"abs_require_module", formsemestre_id
|
||||
)
|
||||
nt = sco_cache.NotesTableCache.get(formsemestre_id)
|
||||
ues = nt.get_ues(etudid=etudid)
|
||||
ues = nt.get_ues_stat_dict()
|
||||
if require_module:
|
||||
menu_module = """
|
||||
<script type="text/javascript">
|
||||
@ -200,7 +208,7 @@ def SignaleAbsenceEtud(): # etudid implied
|
||||
menu_module += """<option value="" selected>(Module)</option>"""
|
||||
|
||||
for ue in ues:
|
||||
modimpls = nt.get_modimpls(ue_id=ue["ue_id"])
|
||||
modimpls = nt.get_modimpls_dict(ue_id=ue["ue_id"])
|
||||
for modimpl in modimpls:
|
||||
menu_module += (
|
||||
"""<option value="%(modimpl_id)s">%(modname)s</option>\n"""
|
||||
|
@ -5,7 +5,7 @@
|
||||
#
|
||||
# Gestion scolarite IUT
|
||||
#
|
||||
# Copyright (c) 1999 - 2021 Emmanuel Viennet. All rights reserved.
|
||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
|
@ -5,7 +5,7 @@
|
||||
#
|
||||
# Gestion scolarite IUT
|
||||
#
|
||||
# Copyright (c) 1999 - 2021 Emmanuel Viennet. All rights reserved.
|
||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
@ -437,7 +437,7 @@ class ApoEtud(dict):
|
||||
|
||||
# Elements UE
|
||||
decisions_ue = nt.get_etud_decision_ues(etudid)
|
||||
for ue in nt.get_ues():
|
||||
for ue in nt.get_ues_stat_dict():
|
||||
if code in ue["code_apogee"].split(","):
|
||||
if self.export_res_ues:
|
||||
if decisions_ue and ue["ue_id"] in decisions_ue:
|
||||
@ -456,7 +456,7 @@ class ApoEtud(dict):
|
||||
return VOID_APO_RES
|
||||
|
||||
# Elements Modules
|
||||
modimpls = nt.get_modimpls()
|
||||
modimpls = nt.get_modimpls_dict()
|
||||
module_code_found = False
|
||||
for modimpl in modimpls:
|
||||
if code in modimpl["module"]["code_apogee"].split(","):
|
||||
@ -973,12 +973,12 @@ class ApoData(object):
|
||||
continue
|
||||
# associé à une UE:
|
||||
nt = sco_cache.NotesTableCache.get(sem["formsemestre_id"])
|
||||
for ue in nt.get_ues():
|
||||
for ue in nt.get_ues_stat_dict():
|
||||
if code in ue["code_apogee"].split(","):
|
||||
s.add(code)
|
||||
continue
|
||||
# associé à un module:
|
||||
modimpls = nt.get_modimpls()
|
||||
modimpls = nt.get_modimpls_dict()
|
||||
for modimpl in modimpls:
|
||||
if code in modimpl["module"]["code_apogee"].split(","):
|
||||
s.add(code)
|
||||
|
@ -5,7 +5,7 @@
|
||||
#
|
||||
# Gestion scolarite IUT
|
||||
#
|
||||
# Copyright (c) 1999 - 2021 Emmanuel Viennet. All rights reserved.
|
||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
|
@ -5,7 +5,7 @@
|
||||
#
|
||||
# Gestion scolarite IUT
|
||||
#
|
||||
# Copyright (c) 1999 - 2021 Emmanuel Viennet. All rights reserved.
|
||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
|
@ -5,7 +5,7 @@
|
||||
#
|
||||
# Gestion scolarite IUT
|
||||
#
|
||||
# Copyright (c) 1999 - 2021 Emmanuel Viennet. All rights reserved.
|
||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
|
@ -5,7 +5,7 @@
|
||||
#
|
||||
# Gestion scolarite IUT
|
||||
#
|
||||
# Copyright (c) 1999 - 2021 Emmanuel Viennet. All rights reserved.
|
||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
@ -218,10 +218,10 @@ def formsemestre_bulletinetud_dict(formsemestre_id, etudid, version="long"):
|
||||
] # deprecated / keep it for backward compat in templates
|
||||
|
||||
# --- Notes
|
||||
ues = nt.get_ues()
|
||||
modimpls = nt.get_modimpls()
|
||||
ues = nt.get_ues_stat_dict()
|
||||
modimpls = nt.get_modimpls_dict()
|
||||
moy_gen = nt.get_etud_moy_gen(etudid)
|
||||
I["nb_inscrits"] = len(nt.rangs)
|
||||
I["nb_inscrits"] = len(nt.etud_moy_gen_ranks)
|
||||
I["moy_gen"] = scu.fmt_note(moy_gen)
|
||||
I["moy_min"] = scu.fmt_note(nt.moy_min)
|
||||
I["moy_max"] = scu.fmt_note(nt.moy_max)
|
||||
@ -265,7 +265,7 @@ def formsemestre_bulletinetud_dict(formsemestre_id, etudid, version="long"):
|
||||
I["rang_gr"] = rang_gr
|
||||
I["gr_name"] = gr_name
|
||||
I["ninscrits_gr"] = ninscrits_gr
|
||||
I["nbetuds"] = len(nt.rangs)
|
||||
I["nbetuds"] = len(nt.etud_moy_gen_ranks)
|
||||
I["nb_demissions"] = nt.nb_demissions
|
||||
I["nb_defaillants"] = nt.nb_defaillants
|
||||
if prefs["bul_show_rangs"]:
|
||||
@ -352,7 +352,7 @@ def formsemestre_bulletinetud_dict(formsemestre_id, etudid, version="long"):
|
||||
etudid,
|
||||
formsemestre_id,
|
||||
ue_status["capitalized_ue_id"],
|
||||
nt_cap.get_modimpls(),
|
||||
nt_cap.get_modimpls_dict(),
|
||||
nt_cap,
|
||||
version,
|
||||
)
|
||||
|
@ -5,7 +5,7 @@
|
||||
#
|
||||
# Gestion scolarite IUT
|
||||
#
|
||||
# Copyright (c) 1999 - 2021 Emmanuel Viennet. All rights reserved.
|
||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
|
@ -5,7 +5,7 @@
|
||||
#
|
||||
# Gestion scolarite IUT
|
||||
#
|
||||
# Copyright (c) 1999 - 2021 Emmanuel Viennet. All rights reserved.
|
||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
|
@ -5,7 +5,7 @@
|
||||
#
|
||||
# Gestion scolarite IUT
|
||||
#
|
||||
# Copyright (c) 1999 - 2021 Emmanuel Viennet. All rights reserved.
|
||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
@ -153,9 +153,9 @@ def formsemestre_bulletinetud_published_dict(
|
||||
pid = partition["partition_id"]
|
||||
partitions_etud_groups[pid] = sco_groups.get_etud_groups_in_partition(pid)
|
||||
|
||||
ues = nt.get_ues()
|
||||
modimpls = nt.get_modimpls()
|
||||
nbetuds = len(nt.rangs)
|
||||
ues = nt.get_ues_stat_dict()
|
||||
modimpls = nt.get_modimpls_dict()
|
||||
nbetuds = len(nt.etud_moy_gen_ranks)
|
||||
mg = scu.fmt_note(nt.get_etud_moy_gen(etudid))
|
||||
if (
|
||||
nt.get_moduleimpls_attente()
|
||||
|
@ -5,7 +5,7 @@
|
||||
#
|
||||
# Gestion scolarite IUT
|
||||
#
|
||||
# Copyright (c) 1999 - 2021 Emmanuel Viennet. All rights reserved.
|
||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
|
@ -5,7 +5,7 @@
|
||||
#
|
||||
# Gestion scolarite IUT
|
||||
#
|
||||
# Copyright (c) 1999 - 2021 Emmanuel Viennet. All rights reserved.
|
||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
|
@ -5,7 +5,7 @@
|
||||
#
|
||||
# Gestion scolarite IUT
|
||||
#
|
||||
# Copyright (c) 1999 - 2021 Emmanuel Viennet. All rights reserved.
|
||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
|
@ -5,7 +5,7 @@
|
||||
#
|
||||
# Gestion scolarite IUT
|
||||
#
|
||||
# Copyright (c) 1999 - 2021 Emmanuel Viennet. All rights reserved.
|
||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
|
@ -5,7 +5,7 @@
|
||||
#
|
||||
# Gestion scolarite IUT
|
||||
#
|
||||
# Copyright (c) 1999 - 2021 Emmanuel Viennet. All rights reserved.
|
||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
|
@ -5,7 +5,7 @@
|
||||
#
|
||||
# Gestion scolarite IUT
|
||||
#
|
||||
# Copyright (c) 1999 - 2021 Emmanuel Viennet. All rights reserved.
|
||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
@ -151,9 +151,9 @@ def make_xml_formsemestre_bulletinetud(
|
||||
partitions_etud_groups[pid] = sco_groups.get_etud_groups_in_partition(pid)
|
||||
|
||||
nt = sco_cache.NotesTableCache.get(formsemestre_id) # > toutes notes
|
||||
ues = nt.get_ues()
|
||||
modimpls = nt.get_modimpls()
|
||||
nbetuds = len(nt.rangs)
|
||||
ues = nt.get_ues_stat_dict()
|
||||
modimpls = nt.get_modimpls_dict()
|
||||
nbetuds = len(nt.etud_moy_gen_ranks)
|
||||
mg = scu.fmt_note(nt.get_etud_moy_gen(etudid))
|
||||
if (
|
||||
nt.get_moduleimpls_attente()
|
||||
|
@ -5,7 +5,7 @@
|
||||
#
|
||||
# Gestion scolarite IUT
|
||||
#
|
||||
# Copyright (c) 1999 - 2021 Emmanuel Viennet. All rights reserved.
|
||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
@ -155,14 +155,14 @@ class EvaluationCache(ScoDocCache):
|
||||
cls.delete_many(evaluation_ids)
|
||||
|
||||
|
||||
class ResultatsSemestreBUTCache(ScoDocCache):
|
||||
"""Cache pour les résultats ResultatsSemestreBUT.
|
||||
class ResultatsSemestreCache(ScoDocCache):
|
||||
"""Cache pour les résultats ResultatsSemestre.
|
||||
Clé: formsemestre_id
|
||||
Valeur: { un paquet de dataframes }
|
||||
"""
|
||||
|
||||
prefix = "RBUT"
|
||||
timeout = 1 * 60 # ttl 1 minutes (en phase de mise au point)
|
||||
prefix = "RSEM"
|
||||
timeout = 60 * 60 # ttl 1 heure (en phase de mise au point)
|
||||
|
||||
|
||||
class AbsSemEtudCache(ScoDocCache):
|
||||
@ -299,7 +299,7 @@ def invalidate_formsemestre( # was inval_cache(formsemestre_id=None, pdfonly=Fa
|
||||
SemInscriptionsCache.delete_many(formsemestre_ids)
|
||||
|
||||
SemBulletinsPDFCache.invalidate_sems(formsemestre_ids)
|
||||
ResultatsSemestreBUTCache.delete_many(formsemestre_ids)
|
||||
ResultatsSemestreCache.delete_many(formsemestre_ids)
|
||||
|
||||
|
||||
class DefferedSemCacheManager:
|
||||
|
@ -5,7 +5,7 @@
|
||||
#
|
||||
# Gestion scolarite IUT
|
||||
#
|
||||
# Copyright (c) 1999 - 2021 Emmanuel Viennet. All rights reserved.
|
||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
|
@ -5,7 +5,7 @@
|
||||
#
|
||||
# Gestion scolarite IUT
|
||||
#
|
||||
# Copyright (c) 1999 - 2021 Emmanuel Viennet. All rights reserved.
|
||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
|
@ -5,7 +5,7 @@
|
||||
#
|
||||
# ScoDoc
|
||||
#
|
||||
# Copyright (c) 1999 - 2021 Emmanuel Viennet. All rights reserved.
|
||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
|
@ -5,7 +5,7 @@
|
||||
#
|
||||
# Gestion scolarite IUT
|
||||
#
|
||||
# Copyright (c) 1999 - 2021 Emmanuel Viennet. All rights reserved.
|
||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
|
@ -5,7 +5,7 @@
|
||||
#
|
||||
# Gestion scolarite IUT
|
||||
#
|
||||
# Copyright (c) 1999 - 2021 Emmanuel Viennet. All rights reserved.
|
||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
|
@ -5,7 +5,7 @@
|
||||
#
|
||||
# Gestion scolarite IUT
|
||||
#
|
||||
# Copyright (c) 1999 - 2021 Emmanuel Viennet. All rights reserved.
|
||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
|
@ -5,7 +5,7 @@
|
||||
#
|
||||
# Gestion scolarite IUT
|
||||
#
|
||||
# Copyright (c) 1999 - 2021 Emmanuel Viennet. All rights reserved.
|
||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
|
@ -2,7 +2,7 @@
|
||||
#
|
||||
# ScoDoc
|
||||
#
|
||||
# Copyright (c) 1999 - 2021 Emmanuel Viennet. All rights reserved.
|
||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
|
@ -5,7 +5,7 @@
|
||||
#
|
||||
# Gestion scolarite IUT
|
||||
#
|
||||
# Copyright (c) 1999 - 2021 Emmanuel Viennet. All rights reserved.
|
||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
@ -33,6 +33,7 @@ from flask import g, url_for, request
|
||||
|
||||
from app import db
|
||||
from app import log
|
||||
from app.models import SHORT_STR_LEN
|
||||
from app.models.formations import Formation
|
||||
from app.models.modules import Module
|
||||
|
||||
@ -209,6 +210,7 @@ def formation_edit(formation_id=None, create=False):
|
||||
"size": 12,
|
||||
"title": "Code formation",
|
||||
"explanation": "code interne. Toutes les formations partageant le même code sont compatibles (compensation de semestres, capitalisation d'UE). Laisser vide si vous ne savez pas, ou entrer le code d'une formation existante.",
|
||||
"validator": lambda val, _: len(val) < SHORT_STR_LEN,
|
||||
},
|
||||
),
|
||||
(
|
||||
|
@ -5,7 +5,7 @@
|
||||
#
|
||||
# Gestion scolarite IUT
|
||||
#
|
||||
# Copyright (c) 1999 - 2021 Emmanuel Viennet. All rights reserved.
|
||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
|
@ -5,7 +5,7 @@
|
||||
#
|
||||
# Gestion scolarite IUT
|
||||
#
|
||||
# Copyright (c) 1999 - 2021 Emmanuel Viennet. All rights reserved.
|
||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
@ -32,6 +32,7 @@ import flask
|
||||
from flask import url_for, render_template
|
||||
from flask import g, request
|
||||
from flask_login import current_user
|
||||
from app.models import APO_CODE_STR_LEN
|
||||
from app.models import Matiere, Module, UniteEns
|
||||
|
||||
import app.scodoc.notesdb as ndb
|
||||
@ -397,21 +398,21 @@ def module_delete(module_id=None):
|
||||
return flask.redirect(dest_url)
|
||||
|
||||
|
||||
def do_module_edit(val):
|
||||
def do_module_edit(vals: dict) -> None:
|
||||
"edit a module"
|
||||
from app.scodoc import sco_edit_formation
|
||||
|
||||
# check
|
||||
mod = module_list({"module_id": val["module_id"]})[0]
|
||||
mod = module_list({"module_id": vals["module_id"]})[0]
|
||||
if module_is_locked(mod["module_id"]):
|
||||
# formation verrouillée: empeche de modifier certains champs:
|
||||
protected_fields = ("coefficient", "ue_id", "matiere_id", "semestre_id")
|
||||
for f in protected_fields:
|
||||
if f in val:
|
||||
del val[f]
|
||||
if f in vals:
|
||||
del vals[f]
|
||||
# edit
|
||||
cnx = ndb.GetDBConnexion()
|
||||
_moduleEditor.edit(cnx, val)
|
||||
_moduleEditor.edit(cnx, vals)
|
||||
Formation.query.get(mod["formation_id"]).invalidate_cached_sems()
|
||||
|
||||
|
||||
@ -604,6 +605,7 @@ def module_edit(module_id=None):
|
||||
"title": "Code Apogée",
|
||||
"size": 25,
|
||||
"explanation": "(optionnel) code élément pédagogique Apogée ou liste de codes ELP séparés par des virgules",
|
||||
"validator": lambda val, _: len(val) < APO_CODE_STR_LEN,
|
||||
},
|
||||
),
|
||||
(
|
||||
|
@ -5,7 +5,7 @@
|
||||
#
|
||||
# Gestion scolarite IUT
|
||||
#
|
||||
# Copyright (c) 1999 - 2021 Emmanuel Viennet. All rights reserved.
|
||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
@ -33,6 +33,7 @@ from flask import url_for, render_template
|
||||
from flask import g, request
|
||||
from flask_login import current_user
|
||||
|
||||
from app.models import APO_CODE_STR_LEN
|
||||
from app.models import Formation, UniteEns, ModuleImpl, Module
|
||||
import app.scodoc.notesdb as ndb
|
||||
import app.scodoc.sco_utils as scu
|
||||
@ -324,6 +325,7 @@ def ue_edit(ue_id=None, create=False, formation_id=None):
|
||||
"title": "Code Apogée",
|
||||
"size": 25,
|
||||
"explanation": "(optionnel) code élément pédagogique Apogée ou liste de codes ELP séparés par des virgules",
|
||||
"validator": lambda val, _: len(val) < APO_CODE_STR_LEN,
|
||||
},
|
||||
),
|
||||
(
|
||||
|
@ -5,7 +5,7 @@
|
||||
#
|
||||
# Gestion scolarite IUT
|
||||
#
|
||||
# Copyright (c) 1999 - 2021 Emmanuel Viennet. All rights reserved.
|
||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
|
@ -5,7 +5,7 @@
|
||||
#
|
||||
# Gestion scolarite IUT
|
||||
#
|
||||
# Copyright (c) 1999 - 2021 Emmanuel Viennet. All rights reserved.
|
||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
|
@ -5,7 +5,7 @@
|
||||
#
|
||||
# Gestion scolarite IUT
|
||||
#
|
||||
# Copyright (c) 1999 - 2021 Emmanuel Viennet. All rights reserved.
|
||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
|
@ -5,7 +5,7 @@
|
||||
#
|
||||
# Gestion scolarite IUT
|
||||
#
|
||||
# Copyright (c) 1999 - 2021 Emmanuel Viennet. All rights reserved.
|
||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
|
@ -5,7 +5,7 @@
|
||||
#
|
||||
# Gestion scolarite IUT
|
||||
#
|
||||
# Copyright (c) 1999 - 2021 Emmanuel Viennet. All rights reserved.
|
||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
|
@ -5,7 +5,7 @@
|
||||
#
|
||||
# Gestion scolarite IUT
|
||||
#
|
||||
# Copyright (c) 1999 - 2021 Emmanuel Viennet. All rights reserved.
|
||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
@ -38,7 +38,7 @@ from flask_mail import Message
|
||||
|
||||
from app import email
|
||||
from app import log
|
||||
|
||||
from app.models.etudiants import make_etud_args
|
||||
import app.scodoc.sco_utils as scu
|
||||
import app.scodoc.notesdb as ndb
|
||||
from app.scodoc.sco_exceptions import ScoGenError, ScoValueError
|
||||
@ -87,6 +87,8 @@ def force_uppercase(s):
|
||||
def format_nomprenom(etud, reverse=False):
|
||||
"""Formatte civilité/nom/prenom pour affichages: "M. Pierre Dupont"
|
||||
Si reverse, "Dupont Pierre", sans civilité.
|
||||
|
||||
DEPRECATED: utiliser Identite.nomprenom
|
||||
"""
|
||||
nom = etud.get("nom_disp", "") or etud.get("nom_usuel", "") or etud["nom"]
|
||||
prenom = format_prenom(etud["prenom"])
|
||||
@ -99,7 +101,9 @@ def format_nomprenom(etud, reverse=False):
|
||||
|
||||
|
||||
def format_prenom(s):
|
||||
"Formatte prenom etudiant pour affichage"
|
||||
"""Formatte prenom etudiant pour affichage
|
||||
DEPRECATED: utiliser Identite.prenom_str
|
||||
"""
|
||||
if not s:
|
||||
return ""
|
||||
frags = s.split()
|
||||
@ -590,35 +594,6 @@ etudident_edit = _etudidentEditor.edit
|
||||
etudident_create = _etudidentEditor.create
|
||||
|
||||
|
||||
def make_etud_args(etudid=None, code_nip=None, use_request=True, raise_exc=True):
|
||||
"""forme args dict pour requete recherche etudiant
|
||||
On peut specifier etudid
|
||||
ou bien (si use_request) cherche dans la requete http: etudid, code_nip, code_ine
|
||||
(dans cet ordre).
|
||||
"""
|
||||
args = None
|
||||
if etudid:
|
||||
args = {"etudid": etudid}
|
||||
elif code_nip:
|
||||
args = {"code_nip": code_nip}
|
||||
elif use_request: # use form from current request (Flask global)
|
||||
if request.method == "POST":
|
||||
vals = request.form
|
||||
elif request.method == "GET":
|
||||
vals = request.args
|
||||
else:
|
||||
vals = {}
|
||||
if "etudid" in vals:
|
||||
args = {"etudid": int(vals["etudid"])}
|
||||
elif "code_nip" in vals:
|
||||
args = {"code_nip": str(vals["code_nip"])}
|
||||
elif "code_ine" in vals:
|
||||
args = {"code_ine": str(vals["code_ine"])}
|
||||
if not args and raise_exc:
|
||||
raise ValueError("getEtudInfo: no parameter !")
|
||||
return args
|
||||
|
||||
|
||||
def log_unknown_etud():
|
||||
"""Log request: cas ou getEtudInfo n'a pas ramene de resultat"""
|
||||
etud_args = make_etud_args(raise_exc=False)
|
||||
|
@ -5,7 +5,7 @@
|
||||
#
|
||||
# Gestion scolarite IUT
|
||||
#
|
||||
# Copyright (c) 1999 - 2021 Emmanuel Viennet. All rights reserved.
|
||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
|
@ -5,7 +5,7 @@
|
||||
#
|
||||
# Gestion scolarite IUT
|
||||
#
|
||||
# Copyright (c) 1999 - 2021 Emmanuel Viennet. All rights reserved.
|
||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
|
@ -5,7 +5,7 @@
|
||||
#
|
||||
# Gestion scolarite IUT
|
||||
#
|
||||
# Copyright (c) 1999 - 2021 Emmanuel Viennet. All rights reserved.
|
||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
|
@ -5,7 +5,7 @@
|
||||
#
|
||||
# Gestion scolarite IUT
|
||||
#
|
||||
# Copyright (c) 1999 - 2021 Emmanuel Viennet. All rights reserved.
|
||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
|
@ -5,7 +5,7 @@
|
||||
#
|
||||
# Gestion scolarite IUT
|
||||
#
|
||||
# Copyright (c) 1999 - 2021 Emmanuel Viennet. All rights reserved.
|
||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
|
@ -5,7 +5,7 @@
|
||||
#
|
||||
# Gestion scolarite IUT
|
||||
#
|
||||
# Copyright (c) 1999 - 2021 Emmanuel Viennet. All rights reserved.
|
||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
|
@ -5,7 +5,7 @@
|
||||
#
|
||||
# Gestion scolarite IUT
|
||||
#
|
||||
# Copyright (c) 1999 - 2021 Emmanuel Viennet. All rights reserved.
|
||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
|
@ -5,7 +5,7 @@
|
||||
#
|
||||
# Gestion scolarite IUT
|
||||
#
|
||||
# Copyright (c) 1999 - 2021 Emmanuel Viennet. All rights reserved.
|
||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
|
@ -5,7 +5,7 @@
|
||||
#
|
||||
# Gestion scolarite IUT
|
||||
#
|
||||
# Copyright (c) 1999 - 2021 Emmanuel Viennet. All rights reserved.
|
||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
|
@ -5,7 +5,7 @@
|
||||
#
|
||||
# Gestion scolarite IUT
|
||||
#
|
||||
# Copyright (c) 1999 - 2021 Emmanuel Viennet. All rights reserved.
|
||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
|
@ -5,7 +5,7 @@
|
||||
#
|
||||
# Gestion scolarite IUT
|
||||
#
|
||||
# Copyright (c) 1999 - 2021 Emmanuel Viennet. All rights reserved.
|
||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
|
@ -5,7 +5,7 @@
|
||||
#
|
||||
# Gestion scolarite IUT
|
||||
#
|
||||
# Copyright (c) 1999 - 2021 Emmanuel Viennet. All rights reserved.
|
||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
@ -33,6 +33,7 @@ from flask_login import current_user
|
||||
|
||||
from app import db
|
||||
from app.auth.models import User
|
||||
from app.models import APO_CODE_STR_LEN, SHORT_STR_LEN
|
||||
from app.models import ModuleImpl, Evaluation, EvaluationUEPoids
|
||||
import app.scodoc.notesdb as ndb
|
||||
import app.scodoc.sco_utils as scu
|
||||
@ -359,6 +360,7 @@ def do_formsemestre_createwithmodules(edit=False):
|
||||
mf_manual = {
|
||||
"size": 12,
|
||||
"template": '<tr%(item_dom_attr)s><td class="tf-fieldlabel">%(label)s</td><td class="tf-field">%(elem)s',
|
||||
"validator": lambda val, _: len(val) < APO_CODE_STR_LEN,
|
||||
}
|
||||
if etapes:
|
||||
mf = {
|
||||
@ -495,6 +497,7 @@ def do_formsemestre_createwithmodules(edit=False):
|
||||
"size": 8,
|
||||
"title": "Couleur fond des bulletins",
|
||||
"explanation": "version web seulement (ex: #ffeeee)",
|
||||
"validator": lambda val, _: len(val) < SHORT_STR_LEN,
|
||||
},
|
||||
),
|
||||
(
|
||||
|
@ -5,7 +5,7 @@
|
||||
#
|
||||
# Gestion scolarite IUT
|
||||
#
|
||||
# Copyright (c) 1999 - 2021 Emmanuel Viennet. All rights reserved.
|
||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
|
@ -5,7 +5,7 @@
|
||||
#
|
||||
# Gestion scolarite IUT
|
||||
#
|
||||
# Copyright (c) 1999 - 2021 Emmanuel Viennet. All rights reserved.
|
||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
|
@ -5,7 +5,7 @@
|
||||
#
|
||||
# Gestion scolarite IUT
|
||||
#
|
||||
# Copyright (c) 1999 - 2021 Emmanuel Viennet. All rights reserved.
|
||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
|
@ -5,7 +5,7 @@
|
||||
#
|
||||
# Gestion scolarite IUT
|
||||
#
|
||||
# Copyright (c) 1999 - 2021 Emmanuel Viennet. All rights reserved.
|
||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
@ -51,6 +51,7 @@ 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
|
||||
from app.scodoc import sco_preferences
|
||||
from app.scodoc import sco_pvjury
|
||||
@ -543,7 +544,7 @@ def formsemestre_recap_parcours_table(
|
||||
|
||||
nt = sco_cache.NotesTableCache.get(
|
||||
sem["formsemestre_id"]
|
||||
) # > get_ues, get_etud_moy_gen, get_etud_ue_status
|
||||
) # > get_ues_stat_dict, get_etud_moy_gen, get_etud_ue_status
|
||||
if is_cur:
|
||||
type_sem = "*" # now unused
|
||||
class_sem = "sem_courant"
|
||||
@ -582,8 +583,17 @@ def formsemestre_recap_parcours_table(
|
||||
else:
|
||||
H.append('<td colspan="%d"><em>en cours</em></td>')
|
||||
H.append('<td class="rcp_nonass">%s</td>' % ass) # abs
|
||||
# acronymes UEs
|
||||
ues = nt.get_ues(filter_sport=True, filter_non_inscrit=True, etudid=etudid)
|
||||
# acronymes UEs auxquelles l'étudiant est inscrit:
|
||||
# XXX il est probable que l'on doive ici ajouter les
|
||||
# XXX UE capitalisées
|
||||
ues = nt.get_ues_stat_dict(filter_sport=True)
|
||||
cnx = ndb.GetDBConnexion()
|
||||
ues = [
|
||||
ue
|
||||
for ue in ues
|
||||
if etud_est_inscrit_ue(cnx, etudid, sem["formsemestre_id"], ue["ue_id"])
|
||||
]
|
||||
|
||||
for ue in ues:
|
||||
H.append('<td class="ue_acro"><span>%s</span></td>' % ue["acronyme"])
|
||||
if len(ues) < Se.nb_max_ue:
|
||||
|
@ -5,7 +5,7 @@
|
||||
#
|
||||
# Gestion scolarite IUT
|
||||
#
|
||||
# Copyright (c) 1999 - 2021 Emmanuel Viennet. All rights reserved.
|
||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
|
@ -5,7 +5,7 @@
|
||||
#
|
||||
# Gestion scolarite IUT
|
||||
#
|
||||
# Copyright (c) 1999 - 2021 Emmanuel Viennet. All rights reserved.
|
||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
@ -45,6 +45,7 @@ from flask import g, request
|
||||
from flask import url_for, make_response
|
||||
|
||||
from app import db
|
||||
from app.models import GROUPNAME_STR_LEN, SHORT_STR_LEN
|
||||
from app.models.groups import Partition
|
||||
import app.scodoc.sco_utils as scu
|
||||
import app.scodoc.notesdb as ndb
|
||||
@ -870,10 +871,11 @@ def editPartitionForm(formsemestre_id=None):
|
||||
page_title="Partitions...",
|
||||
javascripts=["js/editPartitionForm.js"],
|
||||
),
|
||||
# limite à SHORT_STR_LEN
|
||||
r"""<script type="text/javascript">
|
||||
function checkname() {
|
||||
var val = document.editpart.partition_name.value.replace(/^\s+/, "").replace(/\s+$/, "");
|
||||
if (val.length > 0) {
|
||||
if ((val.length > 0)&&(val.length < 32)) {
|
||||
document.editpart.ok.disabled = false;
|
||||
} else {
|
||||
document.editpart.ok.disabled = true;
|
||||
@ -1124,6 +1126,7 @@ def partition_rename(partition_id):
|
||||
"default": partition["partition_name"],
|
||||
"allow_null": False,
|
||||
"size": 12,
|
||||
"validator": lambda val, _: len(val) < SHORT_STR_LEN,
|
||||
},
|
||||
),
|
||||
),
|
||||
@ -1231,6 +1234,7 @@ def group_rename(group_id):
|
||||
"default": group["group_name"],
|
||||
"size": 12,
|
||||
"allow_null": False,
|
||||
"validator": lambda val, _: len(val) < GROUPNAME_STR_LEN,
|
||||
},
|
||||
),
|
||||
),
|
||||
|
@ -5,7 +5,7 @@
|
||||
#
|
||||
# Gestion scolarite IUT
|
||||
#
|
||||
# Copyright (c) 1999 - 2021 Emmanuel Viennet. All rights reserved.
|
||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user