1
0
forked from ScoDoc/ScoDoc

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

This commit is contained in:
Arthur ZHU 2022-01-01 19:39:40 +01:00
commit 6acba813c1
193 changed files with 1852 additions and 1068 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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
View 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,
}

View File

@ -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"

View File

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

View File

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

View File

@ -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
View 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
View 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
View 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
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
}

View File

@ -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"

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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(

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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"]]

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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:

View File

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

View File

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

View File

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

View File

@ -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"""

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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:

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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,
},
),
(

View File

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

View File

@ -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,
},
),
(

View File

@ -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,
},
),
(

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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,
},
),
(

View File

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

View File

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

View File

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

View File

@ -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:

View File

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

View File

@ -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,
},
),
),

View File

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