forked from ScoDoc/ScoDoc
Update opolka/ScoDoc from ScoDoc/ScoDoc #2
@ -56,6 +56,7 @@ class EvaluationEtat:
|
|||||||
|
|
||||||
evaluation_id: int
|
evaluation_id: int
|
||||||
nb_attente: int
|
nb_attente: int
|
||||||
|
nb_notes: int # nb notes d'étudiants inscrits au semestre et au modimpl
|
||||||
is_complete: bool
|
is_complete: bool
|
||||||
|
|
||||||
def to_dict(self):
|
def to_dict(self):
|
||||||
@ -168,13 +169,15 @@ class ModuleImplResults:
|
|||||||
# NULL en base => ABS (= -999)
|
# NULL en base => ABS (= -999)
|
||||||
eval_df.fillna(scu.NOTES_ABSENCE, inplace=True)
|
eval_df.fillna(scu.NOTES_ABSENCE, inplace=True)
|
||||||
# Ce merge ne garde que les étudiants inscrits au module
|
# Ce merge ne garde que les étudiants inscrits au module
|
||||||
# et met à NULL les notes non présentes
|
# et met à NULL (NaN) les notes non présentes
|
||||||
# (notes non saisies ou etuds non inscrits au module):
|
# (notes non saisies ou etuds non inscrits au module):
|
||||||
evals_notes = evals_notes.merge(
|
evals_notes = evals_notes.merge(
|
||||||
eval_df, how="left", left_index=True, right_index=True
|
eval_df, how="left", left_index=True, right_index=True
|
||||||
)
|
)
|
||||||
# Notes en attente: (ne prend en compte que les inscrits, non démissionnaires)
|
# Notes en attente: (ne prend en compte que les inscrits, non démissionnaires)
|
||||||
eval_notes_inscr = evals_notes[str(evaluation.id)][list(inscrits_module)]
|
eval_notes_inscr = evals_notes[str(evaluation.id)][list(inscrits_module)]
|
||||||
|
# Nombre de notes (non vides, incluant ATT etc) des inscrits:
|
||||||
|
nb_notes = eval_notes_inscr.notna().sum()
|
||||||
eval_etudids_attente = set(
|
eval_etudids_attente = set(
|
||||||
eval_notes_inscr.iloc[
|
eval_notes_inscr.iloc[
|
||||||
(eval_notes_inscr == scu.NOTES_ATTENTE).to_numpy()
|
(eval_notes_inscr == scu.NOTES_ATTENTE).to_numpy()
|
||||||
@ -184,6 +187,7 @@ class ModuleImplResults:
|
|||||||
self.evaluations_etat[evaluation.id] = EvaluationEtat(
|
self.evaluations_etat[evaluation.id] = EvaluationEtat(
|
||||||
evaluation_id=evaluation.id,
|
evaluation_id=evaluation.id,
|
||||||
nb_attente=len(eval_etudids_attente),
|
nb_attente=len(eval_etudids_attente),
|
||||||
|
nb_notes=nb_notes,
|
||||||
is_complete=is_complete,
|
is_complete=is_complete,
|
||||||
)
|
)
|
||||||
# au moins une note en ATT dans ce modimpl:
|
# au moins une note en ATT dans ce modimpl:
|
||||||
|
@ -9,12 +9,13 @@
|
|||||||
|
|
||||||
from collections import Counter, defaultdict
|
from collections import Counter, defaultdict
|
||||||
from collections.abc import Generator
|
from collections.abc import Generator
|
||||||
|
import datetime
|
||||||
from functools import cached_property
|
from functools import cached_property
|
||||||
from operator import attrgetter
|
from operator import attrgetter
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
|
import sqlalchemy as sa
|
||||||
from flask import g, url_for
|
from flask import g, url_for
|
||||||
|
|
||||||
from app import db
|
from app import db
|
||||||
@ -22,14 +23,19 @@ from app.comp import res_sem
|
|||||||
from app.comp.res_cache import ResultatsCache
|
from app.comp.res_cache import ResultatsCache
|
||||||
from app.comp.jury import ValidationsSemestre
|
from app.comp.jury import ValidationsSemestre
|
||||||
from app.comp.moy_mod import ModuleImplResults
|
from app.comp.moy_mod import ModuleImplResults
|
||||||
from app.models import FormSemestre, FormSemestreUECoef
|
from app.models import (
|
||||||
from app.models import Identite
|
Evaluation,
|
||||||
from app.models import ModuleImpl, ModuleImplInscription
|
FormSemestre,
|
||||||
from app.models import ScolarAutorisationInscription
|
FormSemestreUECoef,
|
||||||
from app.models.ues import UniteEns
|
Identite,
|
||||||
|
ModuleImpl,
|
||||||
|
ModuleImplInscription,
|
||||||
|
ScolarAutorisationInscription,
|
||||||
|
UniteEns,
|
||||||
|
)
|
||||||
from app.scodoc.sco_cache import ResultatsSemestreCache
|
from app.scodoc.sco_cache import ResultatsSemestreCache
|
||||||
from app.scodoc.codes_cursus import UE_SPORT
|
from app.scodoc.codes_cursus import UE_SPORT
|
||||||
from app.scodoc.sco_exceptions import ScoValueError
|
from app.scodoc.sco_exceptions import ScoValueError, ScoTemporaryError
|
||||||
from app.scodoc import sco_utils as scu
|
from app.scodoc import sco_utils as scu
|
||||||
|
|
||||||
|
|
||||||
@ -192,16 +198,80 @@ class ResultatsSemestre(ResultatsCache):
|
|||||||
*[mr.etudids_attente for mr in self.modimpls_results.values()]
|
*[mr.etudids_attente for mr in self.modimpls_results.values()]
|
||||||
)
|
)
|
||||||
|
|
||||||
# # Etat des évaluations
|
# Etat des évaluations
|
||||||
# # (se substitue à do_evaluation_etat, sans les moyennes par groupes)
|
def get_evaluation_etat(self, evaluation: Evaluation) -> dict:
|
||||||
# def get_evaluations_etats(evaluation_id: int) -> dict:
|
"""État d'une évaluation
|
||||||
# """Renvoie dict avec les clés:
|
{
|
||||||
# last_modif
|
"coefficient" : float, # 0 si None
|
||||||
# nb_evals_completes
|
"description" : str, # de l'évaluation, "" si None
|
||||||
# nb_evals_en_cours
|
"etat" {
|
||||||
# nb_evals_vides
|
"evalcomplete" : bool,
|
||||||
# attente
|
"last_modif" : datetime.datetime | None, # saisie de note la plus récente
|
||||||
# """
|
"nb_notes" : int, # nb notes d'étudiants inscrits
|
||||||
|
},
|
||||||
|
"jour" : datetime.datetime, # e.date_debut or datetime.datetime(1900, 1, 1)
|
||||||
|
"publish_incomplete" : bool,
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
mod_results = self.modimpls_results.get(evaluation.moduleimpl_id)
|
||||||
|
if mod_results is None:
|
||||||
|
raise ScoTemporaryError() # argh !
|
||||||
|
etat = mod_results.evaluations_etat.get(evaluation.id)
|
||||||
|
if etat is None:
|
||||||
|
raise ScoTemporaryError() # argh !
|
||||||
|
# Date de dernière saisie de note
|
||||||
|
cursor = db.session.execute(
|
||||||
|
sa.text(
|
||||||
|
"SELECT MAX(date) FROM notes_notes WHERE evaluation_id = :evaluation_id"
|
||||||
|
),
|
||||||
|
{"evaluation_id": evaluation.id},
|
||||||
|
)
|
||||||
|
date_modif = cursor.one_or_none()
|
||||||
|
last_modif = date_modif[0] if date_modif else None
|
||||||
|
return {
|
||||||
|
"coefficient": evaluation.coefficient or 0.0,
|
||||||
|
"description": evaluation.description or "",
|
||||||
|
"jour": evaluation.date_debut or datetime.datetime(1900, 1, 1),
|
||||||
|
"publish_incomplete": evaluation.publish_incomplete,
|
||||||
|
"etat": {
|
||||||
|
"evalcomplete": etat.is_complete,
|
||||||
|
"nb_notes": etat.nb_notes,
|
||||||
|
"last_modif": last_modif,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_mod_evaluation_etat_list(self, modimpl: ModuleImpl) -> list[dict]:
|
||||||
|
"""Liste des états des évaluations de ce module
|
||||||
|
[ evaluation_etat, ... ] (voir get_evaluation_etat)
|
||||||
|
trié par (numero desc, date_debut desc)
|
||||||
|
"""
|
||||||
|
# nouvelle version 2024-02-02
|
||||||
|
return list(
|
||||||
|
reversed(
|
||||||
|
[
|
||||||
|
self.get_evaluation_etat(evaluation)
|
||||||
|
for evaluation in modimpl.evaluations
|
||||||
|
]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# modernisation de get_mod_evaluation_etat_list
|
||||||
|
# utilisé par:
|
||||||
|
# sco_evaluations.do_evaluation_etat_in_mod
|
||||||
|
# e["etat"]["evalcomplete"]
|
||||||
|
# e["etat"]["nb_notes"]
|
||||||
|
# e["etat"]["last_modif"]
|
||||||
|
#
|
||||||
|
# sco_formsemestre_status.formsemestre_description_table
|
||||||
|
# "jour" (qui est e.date_debut or datetime.date(1900, 1, 1))
|
||||||
|
# "description"
|
||||||
|
# "coefficient"
|
||||||
|
# e["etat"]["evalcomplete"]
|
||||||
|
# publish_incomplete
|
||||||
|
#
|
||||||
|
# sco_formsemestre_status.formsemestre_tableau_modules
|
||||||
|
# e["etat"]["nb_notes"]
|
||||||
|
#
|
||||||
|
|
||||||
# --- JURY...
|
# --- JURY...
|
||||||
def get_formsemestre_validations(self) -> ValidationsSemestre:
|
def get_formsemestre_validations(self) -> ValidationsSemestre:
|
||||||
|
@ -423,30 +423,37 @@ class NotesTableCompat(ResultatsSemestre):
|
|||||||
)
|
)
|
||||||
return evaluations
|
return evaluations
|
||||||
|
|
||||||
def get_evaluations_etats(self) -> list[dict]:
|
def get_evaluations_etats(self) -> dict[int, dict]:
|
||||||
"""Liste de toutes les évaluations du semestre
|
""" "état" de chaque évaluation du semestre
|
||||||
[ {...evaluation et son etat...} ]"""
|
{
|
||||||
# TODO: à moderniser (voir dans ResultatsSemestre)
|
evaluation_id : {
|
||||||
# utilisé par
|
"evalcomplete" : bool,
|
||||||
# do_evaluation_etat_in_sem
|
"last_modif" : datetime | None
|
||||||
|
"nb_notes" : int,
|
||||||
|
}, ...
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
# utilisé par do_evaluation_etat_in_sem
|
||||||
|
evaluations_etats = {}
|
||||||
|
for modimpl in self.formsemestre.modimpls_sorted:
|
||||||
|
for evaluation in modimpl.evaluations:
|
||||||
|
evaluation_etat = self.get_evaluation_etat(evaluation)
|
||||||
|
evaluations_etats[evaluation.id] = evaluation_etat["etat"]
|
||||||
|
return evaluations_etats
|
||||||
|
|
||||||
from app.scodoc import sco_evaluations
|
# ancienne version < 2024-02-02
|
||||||
|
# def get_mod_evaluation_etat_list(self, moduleimpl_id) -> list[dict]:
|
||||||
if not hasattr(self, "_evaluations_etats"):
|
# """Liste des états des évaluations de ce module
|
||||||
self._evaluations_etats = sco_evaluations.do_evaluation_list_in_sem(
|
# ordonnée selon (numero desc, date_debut desc)
|
||||||
self.formsemestre.id
|
# """
|
||||||
)
|
# # à moderniser: lent, recharge des données que l'on a déjà...
|
||||||
|
# # remplacemé par ResultatsSemestre.get_mod_evaluation_etat_list
|
||||||
return self._evaluations_etats
|
# #
|
||||||
|
# return [
|
||||||
def get_mod_evaluation_etat_list(self, moduleimpl_id) -> list[dict]:
|
# e
|
||||||
"""Liste des états des évaluations de ce module"""
|
# for e in self.get_evaluations_etats()
|
||||||
# XXX TODO à moderniser: lent, recharge des données que l'on a déjà...
|
# if e["moduleimpl_id"] == moduleimpl_id
|
||||||
return [
|
# ]
|
||||||
e
|
|
||||||
for e in self.get_evaluations_etats()
|
|
||||||
if e["moduleimpl_id"] == moduleimpl_id
|
|
||||||
]
|
|
||||||
|
|
||||||
def get_moduleimpls_attente(self):
|
def get_moduleimpls_attente(self):
|
||||||
"""Liste des modimpls du semestre ayant des notes en attente"""
|
"""Liste des modimpls du semestre ayant des notes en attente"""
|
||||||
|
@ -40,7 +40,7 @@ from app import db
|
|||||||
from app.auth.models import User
|
from app.auth.models import User
|
||||||
from app.comp import res_sem
|
from app.comp import res_sem
|
||||||
from app.comp.res_compat import NotesTableCompat
|
from app.comp.res_compat import NotesTableCompat
|
||||||
from app.models import Evaluation, FormSemestre
|
from app.models import Evaluation, FormSemestre, ModuleImpl
|
||||||
|
|
||||||
import app.scodoc.sco_utils as scu
|
import app.scodoc.sco_utils as scu
|
||||||
from app.scodoc.sco_utils import ModuleType
|
from app.scodoc.sco_utils import ModuleType
|
||||||
@ -280,82 +280,14 @@ def do_evaluation_etat(
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def do_evaluation_list_in_sem(formsemestre_id, with_etat=True):
|
def _summarize_evals_etats(evals: list[dict]) -> dict:
|
||||||
"""Liste les évaluations de tous les modules de ce semestre.
|
"""Synthétise les états d'une liste d'évaluations
|
||||||
Triée par module, numero desc, date_debut desc
|
evals: list of mappings (etats),
|
||||||
Donne pour chaque eval son état (voir do_evaluation_etat)
|
utilise e["etat"]["evalcomplete"], e["etat"]["nb_notes"], e["etat"]["last_modif"]
|
||||||
{ evaluation_id,nb_inscrits, nb_notes, nb_abs, nb_neutre, moy, median, last_modif ... }
|
|
||||||
|
|
||||||
Exemple:
|
|
||||||
[ {
|
|
||||||
'coefficient': 1.0,
|
|
||||||
'description': 'QCM et cas pratiques',
|
|
||||||
'etat': {
|
|
||||||
'evalattente': False,
|
|
||||||
'evalcomplete': True,
|
|
||||||
'evaluation_id': 'GEAEVAL82883',
|
|
||||||
'gr_incomplets': [],
|
|
||||||
'gr_moyennes': [{
|
|
||||||
'gr_median': '12.00', # sur 20
|
|
||||||
'gr_moy': '11.88',
|
|
||||||
'gr_nb_att': 0,
|
|
||||||
'gr_nb_notes': 166,
|
|
||||||
'group_id': 'GEAG266762',
|
|
||||||
'group_name': None
|
|
||||||
}],
|
|
||||||
'groups': {'GEAG266762': {'etudid': 'GEAEID80603',
|
|
||||||
'group_id': 'GEAG266762',
|
|
||||||
'group_name': None,
|
|
||||||
'partition_id': 'GEAP266761'}
|
|
||||||
},
|
|
||||||
'last_modif': datetime.datetime(2015, 12, 3, 15, 15, 16),
|
|
||||||
'median': '12.00',
|
|
||||||
'moy': '11.84',
|
|
||||||
'nb_abs': 2,
|
|
||||||
'nb_att': 0,
|
|
||||||
'nb_inscrits': 166,
|
|
||||||
'nb_neutre': 0,
|
|
||||||
'nb_notes': 168,
|
|
||||||
'nb_notes_total': 169
|
|
||||||
},
|
|
||||||
'evaluation_id': 'GEAEVAL82883',
|
|
||||||
'evaluation_type': 0,
|
|
||||||
'heure_debut': datetime.time(8, 0),
|
|
||||||
'heure_fin': datetime.time(9, 30),
|
|
||||||
'jour': datetime.date(2015, 11, 3), // vide => 1/1/1900
|
|
||||||
'moduleimpl_id': 'GEAMIP80490',
|
|
||||||
'note_max': 20.0,
|
|
||||||
'numero': 0,
|
|
||||||
'publish_incomplete': 0,
|
|
||||||
'visibulletin': 1} ]
|
|
||||||
|
|
||||||
"""
|
|
||||||
req = """SELECT E.id AS evaluation_id, E.*
|
|
||||||
FROM notes_evaluation E, notes_moduleimpl MI
|
|
||||||
WHERE MI.formsemestre_id = %(formsemestre_id)s
|
|
||||||
and MI.id = E.moduleimpl_id
|
|
||||||
ORDER BY MI.id, numero desc, date_debut desc
|
|
||||||
"""
|
|
||||||
cnx = ndb.GetDBConnexion()
|
|
||||||
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
|
|
||||||
cursor.execute(req, {"formsemestre_id": formsemestre_id})
|
|
||||||
res = cursor.dictfetchall()
|
|
||||||
# etat de chaque evaluation:
|
|
||||||
for r in res:
|
|
||||||
if with_etat:
|
|
||||||
r["etat"] = do_evaluation_etat(r["evaluation_id"])
|
|
||||||
r["jour"] = r["date_debut"] or datetime.date(1900, 1, 1)
|
|
||||||
|
|
||||||
return res
|
|
||||||
|
|
||||||
|
|
||||||
def _eval_etat(evals):
|
|
||||||
"""evals: list of mappings (etats)
|
|
||||||
-> nb_eval_completes, nb_evals_en_cours,
|
-> nb_eval_completes, nb_evals_en_cours,
|
||||||
nb_evals_vides, date derniere modif
|
nb_evals_vides, date derniere modif
|
||||||
|
|
||||||
Une eval est "complete" ssi tous les etudiants *inscrits* ont une note.
|
Une eval est "complete" ssi tous les etudiants *inscrits* ont une note.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
nb_evals_completes, nb_evals_en_cours, nb_evals_vides = 0, 0, 0
|
nb_evals_completes, nb_evals_en_cours, nb_evals_vides = 0, 0, 0
|
||||||
dates = []
|
dates = []
|
||||||
@ -370,11 +302,8 @@ def _eval_etat(evals):
|
|||||||
if last_modif is not None:
|
if last_modif is not None:
|
||||||
dates.append(e["etat"]["last_modif"])
|
dates.append(e["etat"]["last_modif"])
|
||||||
|
|
||||||
if dates:
|
# date de derniere modif d'une note dans un module
|
||||||
dates = scu.sort_dates(dates)
|
last_modif = sorted(dates)[-1] if dates else ""
|
||||||
last_modif = dates[-1] # date de derniere modif d'une note dans un module
|
|
||||||
else:
|
|
||||||
last_modif = ""
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"nb_evals_completes": nb_evals_completes,
|
"nb_evals_completes": nb_evals_completes,
|
||||||
@ -384,37 +313,42 @@ def _eval_etat(evals):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def do_evaluation_etat_in_sem(formsemestre_id):
|
def do_evaluation_etat_in_sem(formsemestre: FormSemestre) -> dict:
|
||||||
"""-> nb_eval_completes, nb_evals_en_cours, nb_evals_vides,
|
"""-> { nb_eval_completes, nb_evals_en_cours, nb_evals_vides,
|
||||||
date derniere modif, attente
|
date derniere modif, attente }
|
||||||
|
|
||||||
XXX utilisé par
|
|
||||||
- formsemestre_status_head
|
|
||||||
- gen_formsemestre_recapcomplet_xml
|
|
||||||
- gen_formsemestre_recapcomplet_json
|
|
||||||
|
|
||||||
"nb_evals_completes"
|
|
||||||
"nb_evals_en_cours"
|
|
||||||
"nb_evals_vides"
|
|
||||||
"date_derniere_note"
|
|
||||||
"last_modif"
|
|
||||||
"attente"
|
|
||||||
"""
|
"""
|
||||||
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
# Note: utilisé par
|
||||||
|
# - formsemestre_status_head
|
||||||
|
# nb_evals_completes, nb_evals_en_cours, nb_evals_vides, last_modif
|
||||||
|
# pour la ligne
|
||||||
|
# Évaluations: 20 ok, 8 en cours, 5 vides (dernière note saisie le 11/01/2024 à 19h49)
|
||||||
|
# attente
|
||||||
|
#
|
||||||
|
# - gen_formsemestre_recapcomplet_xml
|
||||||
|
# - gen_formsemestre_recapcomplet_json
|
||||||
|
# nb_evals_completes, nb_evals_en_cours, nb_evals_vides, last_modif
|
||||||
|
#
|
||||||
|
# "nb_evals_completes"
|
||||||
|
# "nb_evals_en_cours"
|
||||||
|
# "nb_evals_vides"
|
||||||
|
# "last_modif"
|
||||||
|
# "attente"
|
||||||
|
|
||||||
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
||||||
evals = nt.get_evaluations_etats()
|
evaluations_etats = nt.get_evaluations_etats()
|
||||||
etat = _eval_etat(evals)
|
# raccordement moche...
|
||||||
|
etat = _summarize_evals_etats([{"etat": v} for v in evaluations_etats.values()])
|
||||||
# Ajoute information sur notes en attente
|
# Ajoute information sur notes en attente
|
||||||
etat["attente"] = len(nt.get_moduleimpls_attente()) > 0
|
etat["attente"] = len(nt.get_moduleimpls_attente()) > 0
|
||||||
return etat
|
return etat
|
||||||
|
|
||||||
|
|
||||||
def do_evaluation_etat_in_mod(nt, moduleimpl_id):
|
def do_evaluation_etat_in_mod(nt, modimpl: ModuleImpl):
|
||||||
"""état des évaluations dans ce module"""
|
"""état des évaluations dans ce module"""
|
||||||
evals = nt.get_mod_evaluation_etat_list(moduleimpl_id)
|
evals = nt.get_mod_evaluation_etat_list(modimpl)
|
||||||
etat = _eval_etat(evals)
|
etat = _summarize_evals_etats(evals)
|
||||||
# Il y a-t-il des notes en attente dans ce module ?
|
# Il y a-t-il des notes en attente dans ce module ?
|
||||||
etat["attente"] = nt.modimpls_results[moduleimpl_id].en_attente
|
etat["attente"] = nt.modimpls_results[modimpl.id].en_attente
|
||||||
return etat
|
return etat
|
||||||
|
|
||||||
|
|
||||||
|
@ -230,3 +230,15 @@ class APIInvalidParams(Exception):
|
|||||||
|
|
||||||
class ScoFormationConflict(Exception):
|
class ScoFormationConflict(Exception):
|
||||||
"""Conflit cohérence formation (APC)"""
|
"""Conflit cohérence formation (APC)"""
|
||||||
|
|
||||||
|
|
||||||
|
class ScoTemporaryError(ScoValueError):
|
||||||
|
"""Erreurs temporaires rarissimes (caches ?)"""
|
||||||
|
|
||||||
|
def __init__(self, msg: str = ""):
|
||||||
|
msg = """
|
||||||
|
<p>"Erreur temporaire</p>
|
||||||
|
<p>Veuillez ré-essayer. Si le problème persiste, merci de contacter l'assistance ScoDoc
|
||||||
|
</p>
|
||||||
|
"""
|
||||||
|
super().__init__(msg)
|
||||||
|
@ -627,9 +627,7 @@ def formsemestre_description_table(
|
|||||||
# car l'UE de rattachement n'a pas d'intérêt en BUT
|
# car l'UE de rattachement n'a pas d'intérêt en BUT
|
||||||
rows.append(ue_info)
|
rows.append(ue_info)
|
||||||
|
|
||||||
mod_inscrits = sco_moduleimpl.do_moduleimpl_inscription_list(
|
mod_nb_inscrits = nt.modimpls_results[modimpl.id].nb_inscrits_module
|
||||||
moduleimpl_id=modimpl.id
|
|
||||||
)
|
|
||||||
enseignants = ", ".join(ens.get_prenomnom() for ens in modimpl.enseignants)
|
enseignants = ", ".join(ens.get_prenomnom() for ens in modimpl.enseignants)
|
||||||
|
|
||||||
row = {
|
row = {
|
||||||
@ -638,7 +636,7 @@ def formsemestre_description_table(
|
|||||||
"Code": modimpl.module.code or "",
|
"Code": modimpl.module.code or "",
|
||||||
"Module": modimpl.module.abbrev or modimpl.module.titre,
|
"Module": modimpl.module.abbrev or modimpl.module.titre,
|
||||||
"_Module_class": "scotext",
|
"_Module_class": "scotext",
|
||||||
"Inscrits": len(mod_inscrits),
|
"Inscrits": mod_nb_inscrits,
|
||||||
"Responsable": sco_users.user_info(modimpl.responsable_id)["nomprenom"],
|
"Responsable": sco_users.user_info(modimpl.responsable_id)["nomprenom"],
|
||||||
"_Responsable_class": "scotext",
|
"_Responsable_class": "scotext",
|
||||||
"Enseignants": enseignants,
|
"Enseignants": enseignants,
|
||||||
@ -680,7 +678,7 @@ def formsemestre_description_table(
|
|||||||
|
|
||||||
if with_evals:
|
if with_evals:
|
||||||
# Ajoute lignes pour evaluations
|
# Ajoute lignes pour evaluations
|
||||||
evals = nt.get_mod_evaluation_etat_list(modimpl.id)
|
evals = nt.get_mod_evaluation_etat_list(modimpl)
|
||||||
evals.reverse() # ordre chronologique
|
evals.reverse() # ordre chronologique
|
||||||
# Ajoute etat:
|
# Ajoute etat:
|
||||||
eval_rows = []
|
eval_rows = []
|
||||||
@ -942,10 +940,10 @@ def html_expr_diagnostic(diagnostics):
|
|||||||
|
|
||||||
def formsemestre_status_head(formsemestre_id: int = None, page_title: str = None):
|
def formsemestre_status_head(formsemestre_id: int = None, page_title: str = None):
|
||||||
"""En-tête HTML des pages "semestre" """
|
"""En-tête HTML des pages "semestre" """
|
||||||
sem: FormSemestre = db.session.get(FormSemestre, formsemestre_id)
|
formsemestre: FormSemestre = db.session.get(FormSemestre, formsemestre_id)
|
||||||
if not sem:
|
if not formsemestre:
|
||||||
raise ScoValueError("Semestre inexistant (il a peut être été supprimé ?)")
|
raise ScoValueError("Semestre inexistant (il a peut être été supprimé ?)")
|
||||||
formation: Formation = sem.formation
|
formation: Formation = formsemestre.formation
|
||||||
parcours = formation.get_cursus()
|
parcours = formation.get_cursus()
|
||||||
|
|
||||||
page_title = page_title or "Modules de "
|
page_title = page_title or "Modules de "
|
||||||
@ -957,25 +955,25 @@ def formsemestre_status_head(formsemestre_id: int = None, page_title: str = None
|
|||||||
f"""<table>
|
f"""<table>
|
||||||
<tr><td class="fichetitre2">Formation: </td><td>
|
<tr><td class="fichetitre2">Formation: </td><td>
|
||||||
<a href="{url_for('notes.ue_table',
|
<a href="{url_for('notes.ue_table',
|
||||||
scodoc_dept=g.scodoc_dept, formation_id=sem.formation.id)}"
|
scodoc_dept=g.scodoc_dept, formation_id=formsemestre.formation.id)}"
|
||||||
class="discretelink" title="Formation {
|
class="discretelink" title="Formation {
|
||||||
formation.acronyme}, v{formation.version}">{formation.titre}</a>
|
formation.acronyme}, v{formation.version}">{formation.titre}</a>
|
||||||
""",
|
""",
|
||||||
]
|
]
|
||||||
if sem.semestre_id >= 0:
|
if formsemestre.semestre_id >= 0:
|
||||||
H.append(f", {parcours.SESSION_NAME} {sem.semestre_id}")
|
H.append(f", {parcours.SESSION_NAME} {formsemestre.semestre_id}")
|
||||||
if sem.modalite:
|
if formsemestre.modalite:
|
||||||
H.append(f" en {sem.modalite}")
|
H.append(f" en {formsemestre.modalite}")
|
||||||
if sem.etapes:
|
if formsemestre.etapes:
|
||||||
H.append(
|
H.append(
|
||||||
f""" (étape <b><tt>{
|
f""" (étape <b><tt>{
|
||||||
sem.etapes_apo_str() or "-"
|
formsemestre.etapes_apo_str() or "-"
|
||||||
}</tt></b>)"""
|
}</tt></b>)"""
|
||||||
)
|
)
|
||||||
H.append("</td></tr>")
|
H.append("</td></tr>")
|
||||||
if formation.is_apc():
|
if formation.is_apc():
|
||||||
# Affiche les parcours BUT cochés. Si aucun, tous ceux du référentiel.
|
# Affiche les parcours BUT cochés. Si aucun, tous ceux du référentiel.
|
||||||
sem_parcours = sem.get_parcours_apc()
|
sem_parcours = formsemestre.get_parcours_apc()
|
||||||
H.append(
|
H.append(
|
||||||
f"""
|
f"""
|
||||||
<tr><td class="fichetitre2">Parcours: </td>
|
<tr><td class="fichetitre2">Parcours: </td>
|
||||||
@ -984,7 +982,7 @@ def formsemestre_status_head(formsemestre_id: int = None, page_title: str = None
|
|||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
|
||||||
evals = sco_evaluations.do_evaluation_etat_in_sem(formsemestre_id)
|
evals = sco_evaluations.do_evaluation_etat_in_sem(formsemestre)
|
||||||
H.append(
|
H.append(
|
||||||
'<tr><td class="fichetitre2">Évaluations: </td><td> %(nb_evals_completes)s ok, %(nb_evals_en_cours)s en cours, %(nb_evals_vides)s vides'
|
'<tr><td class="fichetitre2">Évaluations: </td><td> %(nb_evals_completes)s ok, %(nb_evals_en_cours)s en cours, %(nb_evals_vides)s vides'
|
||||||
% evals
|
% evals
|
||||||
@ -1002,11 +1000,11 @@ def formsemestre_status_head(formsemestre_id: int = None, page_title: str = None
|
|||||||
"""<span class="fontred">Il y a des notes en attente !</span>
|
"""<span class="fontred">Il y a des notes en attente !</span>
|
||||||
Le classement des étudiants n'a qu'une valeur indicative."""
|
Le classement des étudiants n'a qu'une valeur indicative."""
|
||||||
)
|
)
|
||||||
if sem.bul_hide_xml:
|
if formsemestre.bul_hide_xml:
|
||||||
warnings.append("""Bulletins non publiés sur la passerelle.""")
|
warnings.append("""Bulletins non publiés sur la passerelle.""")
|
||||||
if sem.block_moyennes:
|
if formsemestre.block_moyennes:
|
||||||
warnings.append("Calcul des moyennes bloqué !")
|
warnings.append("Calcul des moyennes bloqué !")
|
||||||
if sem.semestre_id >= 0 and not sem.est_sur_une_annee():
|
if formsemestre.semestre_id >= 0 and not formsemestre.est_sur_une_annee():
|
||||||
warnings.append("""<em>Ce semestre couvre plusieurs années scolaires !</em>""")
|
warnings.append("""<em>Ce semestre couvre plusieurs années scolaires !</em>""")
|
||||||
if warnings:
|
if warnings:
|
||||||
H += [
|
H += [
|
||||||
@ -1028,18 +1026,14 @@ def formsemestre_status(formsemestre_id=None, check_parcours=True):
|
|||||||
# S'assure que les groupes de parcours sont à jour:
|
# S'assure que les groupes de parcours sont à jour:
|
||||||
if int(check_parcours):
|
if int(check_parcours):
|
||||||
formsemestre.setup_parcours_groups()
|
formsemestre.setup_parcours_groups()
|
||||||
modimpls = sco_moduleimpl.moduleimpl_withmodule_list(
|
modimpls = formsemestre.modimpls_sorted
|
||||||
formsemestre_id=formsemestre_id
|
|
||||||
)
|
|
||||||
nt = res_sem.load_formsemestre_results(formsemestre)
|
nt = res_sem.load_formsemestre_results(formsemestre)
|
||||||
|
|
||||||
# Construit la liste de tous les enseignants de ce semestre:
|
# Construit la liste de tous les enseignants de ce semestre:
|
||||||
mails_enseignants = set(u.email for u in formsemestre.responsables)
|
mails_enseignants = set(u.email for u in formsemestre.responsables)
|
||||||
for modimpl in modimpls:
|
for modimpl in modimpls:
|
||||||
mails_enseignants.add(sco_users.user_info(modimpl["responsable_id"])["email"])
|
mails_enseignants.add(sco_users.user_info(modimpl.responsable_id)["email"])
|
||||||
mails_enseignants |= set(
|
mails_enseignants |= {u.email for u in modimpl.enseignants if u.email}
|
||||||
[sco_users.user_info(m["ens_id"])["email"] for m in modimpl["ens"]]
|
|
||||||
)
|
|
||||||
|
|
||||||
can_edit = formsemestre.can_be_edited_by(current_user)
|
can_edit = formsemestre.can_be_edited_by(current_user)
|
||||||
can_change_all_notes = current_user.has_permission(Permission.EditAllNotes) or (
|
can_change_all_notes = current_user.has_permission(Permission.EditAllNotes) or (
|
||||||
@ -1089,13 +1083,13 @@ def formsemestre_status(formsemestre_id=None, check_parcours=True):
|
|||||||
if nt.parcours.APC_SAE:
|
if nt.parcours.APC_SAE:
|
||||||
# BUT: tableau ressources puis SAE
|
# BUT: tableau ressources puis SAE
|
||||||
ressources = [
|
ressources = [
|
||||||
m for m in modimpls if m["module"]["module_type"] == ModuleType.RESSOURCE
|
m for m in modimpls if m.module.module_type == ModuleType.RESSOURCE
|
||||||
]
|
]
|
||||||
saes = [m for m in modimpls if m["module"]["module_type"] == ModuleType.SAE]
|
saes = [m for m in modimpls if m.module.module_type == ModuleType.SAE]
|
||||||
autres = [
|
autres = [
|
||||||
m
|
m
|
||||||
for m in modimpls
|
for m in modimpls
|
||||||
if m["module"]["module_type"] not in (ModuleType.RESSOURCE, ModuleType.SAE)
|
if m.module.module_type not in (ModuleType.RESSOURCE, ModuleType.SAE)
|
||||||
]
|
]
|
||||||
H += [
|
H += [
|
||||||
f"""
|
f"""
|
||||||
@ -1136,7 +1130,7 @@ def formsemestre_status(formsemestre_id=None, check_parcours=True):
|
|||||||
modimpls_classic = [
|
modimpls_classic = [
|
||||||
m
|
m
|
||||||
for m in modimpls
|
for m in modimpls
|
||||||
if m["module"]["module_type"] not in (ModuleType.RESSOURCE, ModuleType.SAE)
|
if m.module.module_type not in (ModuleType.RESSOURCE, ModuleType.SAE)
|
||||||
]
|
]
|
||||||
H += [
|
H += [
|
||||||
"<p>",
|
"<p>",
|
||||||
@ -1168,8 +1162,10 @@ def formsemestre_status(formsemestre_id=None, check_parcours=True):
|
|||||||
adrlist = list(mails_enseignants - {None, ""})
|
adrlist = list(mails_enseignants - {None, ""})
|
||||||
if adrlist:
|
if adrlist:
|
||||||
H.append(
|
H.append(
|
||||||
'<p><a class="stdlink" href="mailto:?cc=%s">Courrier aux %d enseignants du semestre</a></p>'
|
f"""<p>
|
||||||
% (",".join(adrlist), len(adrlist))
|
<a class="stdlink" href="mailto:?cc={','.join(adrlist)}">Courrier aux {
|
||||||
|
len(adrlist)} enseignants du semestre</a>
|
||||||
|
</p>"""
|
||||||
)
|
)
|
||||||
return "".join(H) + html_sco_header.sco_footer()
|
return "".join(H) + html_sco_header.sco_footer()
|
||||||
|
|
||||||
@ -1189,7 +1185,7 @@ _TABLEAU_MODULES_FOOT = """</table>"""
|
|||||||
|
|
||||||
|
|
||||||
def formsemestre_tableau_modules(
|
def formsemestre_tableau_modules(
|
||||||
modimpls: list[dict],
|
modimpls: list[ModuleImpl],
|
||||||
nt,
|
nt,
|
||||||
formsemestre: FormSemestre,
|
formsemestre: FormSemestre,
|
||||||
can_edit=True,
|
can_edit=True,
|
||||||
@ -1200,11 +1196,11 @@ def formsemestre_tableau_modules(
|
|||||||
H = []
|
H = []
|
||||||
prev_ue_id = None
|
prev_ue_id = None
|
||||||
for modimpl in modimpls:
|
for modimpl in modimpls:
|
||||||
mod: Module = db.session.get(Module, modimpl["module_id"])
|
mod: Module = modimpl.module
|
||||||
moduleimpl_status_url = url_for(
|
moduleimpl_status_url = url_for(
|
||||||
"notes.moduleimpl_status",
|
"notes.moduleimpl_status",
|
||||||
scodoc_dept=g.scodoc_dept,
|
scodoc_dept=g.scodoc_dept,
|
||||||
moduleimpl_id=modimpl["moduleimpl_id"],
|
moduleimpl_id=modimpl.id,
|
||||||
)
|
)
|
||||||
mod_descr = "Module " + (mod.titre or "")
|
mod_descr = "Module " + (mod.titre or "")
|
||||||
if mod.is_apc():
|
if mod.is_apc():
|
||||||
@ -1221,48 +1217,45 @@ def formsemestre_tableau_modules(
|
|||||||
mod_descr += " (pas de coefficients) "
|
mod_descr += " (pas de coefficients) "
|
||||||
else:
|
else:
|
||||||
mod_descr += ", coef. " + str(mod.coefficient)
|
mod_descr += ", coef. " + str(mod.coefficient)
|
||||||
mod_ens = sco_users.user_info(modimpl["responsable_id"])["nomcomplet"]
|
mod_ens = sco_users.user_info(modimpl.responsable_id)["nomcomplet"]
|
||||||
if modimpl["ens"]:
|
if modimpl.enseignants.count():
|
||||||
mod_ens += " (resp.), " + ", ".join(
|
mod_ens += " (resp.), " + ", ".join(
|
||||||
[sco_users.user_info(e["ens_id"])["nomcomplet"] for e in modimpl["ens"]]
|
[u.get_nomcomplet() for u in modimpl.enseignants]
|
||||||
)
|
)
|
||||||
mod_inscrits = sco_moduleimpl.do_moduleimpl_inscription_list(
|
mod_nb_inscrits = nt.modimpls_results[modimpl.id].nb_inscrits_module
|
||||||
moduleimpl_id=modimpl["moduleimpl_id"]
|
ue = modimpl.module.ue
|
||||||
)
|
if show_ues and (prev_ue_id != ue.id):
|
||||||
|
prev_ue_id = ue.id
|
||||||
ue = modimpl["ue"]
|
titre = ue.titre
|
||||||
if show_ues and (prev_ue_id != ue["ue_id"]):
|
|
||||||
prev_ue_id = ue["ue_id"]
|
|
||||||
titre = ue["titre"]
|
|
||||||
if use_ue_coefs:
|
if use_ue_coefs:
|
||||||
titre += f""" <b>(coef. {ue["coefficient"] or 0.0})</b>"""
|
titre += f""" <b>(coef. {ue.coefficient or 0.0})</b>"""
|
||||||
H.append(
|
H.append(
|
||||||
f"""<tr class="formsemestre_status_ue"><td colspan="4">
|
f"""<tr class="formsemestre_status_ue"><td colspan="4">
|
||||||
<span class="status_ue_acro">{ue["acronyme"]}</span>
|
<span class="status_ue_acro">{ue.acronyme}</span>
|
||||||
<span class="status_ue_title">{titre}</span>
|
<span class="status_ue_title">{titre}</span>
|
||||||
</td><td colspan="2">"""
|
</td><td colspan="2">"""
|
||||||
)
|
)
|
||||||
|
|
||||||
expr = sco_compute_moy.get_ue_expression(
|
expr = sco_compute_moy.get_ue_expression(
|
||||||
formsemestre.id, ue["ue_id"], html_quote=True
|
formsemestre.id, ue.id, html_quote=True
|
||||||
)
|
)
|
||||||
if expr:
|
if expr:
|
||||||
H.append(
|
H.append(
|
||||||
f""" <span class="formula" title="mode de calcul de la moyenne d'UE">{expr}</span>
|
f""" <span class="formula" title="mode de calcul de la moyenne d'UE">{expr}</span>
|
||||||
<span class="warning">formule inutilisée en 9.2: <a href="{
|
<span class="warning">formule inutilisée en 9.2: <a href="{
|
||||||
url_for("notes.delete_ue_expr", scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre.id, ue_id=ue["ue_id"] )
|
url_for("notes.delete_ue_expr", scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre.id, ue_id=ue.id )
|
||||||
}
|
}
|
||||||
">supprimer</a></span>"""
|
">supprimer</a></span>"""
|
||||||
)
|
)
|
||||||
|
|
||||||
H.append("</td></tr>")
|
H.append("</td></tr>")
|
||||||
|
|
||||||
if modimpl["ue"]["type"] != codes_cursus.UE_STANDARD:
|
if ue.type != codes_cursus.UE_STANDARD:
|
||||||
fontorange = " fontorange" # style css additionnel
|
fontorange = " fontorange" # style css additionnel
|
||||||
else:
|
else:
|
||||||
fontorange = ""
|
fontorange = ""
|
||||||
|
|
||||||
etat = sco_evaluations.do_evaluation_etat_in_mod(nt, modimpl["moduleimpl_id"])
|
etat = sco_evaluations.do_evaluation_etat_in_mod(nt, modimpl)
|
||||||
# if nt.parcours.APC_SAE:
|
# if nt.parcours.APC_SAE:
|
||||||
# tbd style si module non conforme
|
# tbd style si module non conforme
|
||||||
if (
|
if (
|
||||||
@ -1282,10 +1275,10 @@ def formsemestre_tableau_modules(
|
|||||||
<td class="scotext"><a href="{moduleimpl_status_url}" title="{mod_descr}"
|
<td class="scotext"><a href="{moduleimpl_status_url}" title="{mod_descr}"
|
||||||
class="formsemestre_status_link">{mod.abbrev or mod.titre or ""}</a>
|
class="formsemestre_status_link">{mod.abbrev or mod.titre or ""}</a>
|
||||||
</td>
|
</td>
|
||||||
<td class="formsemestre_status_inscrits">{len(mod_inscrits)}</td>
|
<td class="formsemestre_status_inscrits">{mod_nb_inscrits}</td>
|
||||||
<td class="resp scotext">
|
<td class="resp scotext">
|
||||||
<a class="discretelink" href="{moduleimpl_status_url}" title="{mod_ens}">{
|
<a class="discretelink" href="{moduleimpl_status_url}" title="{mod_ens}">{
|
||||||
sco_users.user_info(modimpl["responsable_id"])["prenomnom"]
|
sco_users.user_info(modimpl.responsable_id)["prenomnom"]
|
||||||
}</a>
|
}</a>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
@ -1339,10 +1332,7 @@ def formsemestre_tableau_modules(
|
|||||||
)
|
)
|
||||||
elif mod.module_type == ModuleType.MALUS:
|
elif mod.module_type == ModuleType.MALUS:
|
||||||
nb_malus_notes = sum(
|
nb_malus_notes = sum(
|
||||||
[
|
e["etat"]["nb_notes"] for e in nt.get_mod_evaluation_etat_list(modimpl)
|
||||||
e["etat"]["nb_notes"]
|
|
||||||
for e in nt.get_mod_evaluation_etat_list(modimpl["moduleimpl_id"])
|
|
||||||
]
|
|
||||||
)
|
)
|
||||||
H.append(
|
H.append(
|
||||||
f"""<td class="malus">
|
f"""<td class="malus">
|
||||||
|
@ -367,7 +367,7 @@ def gen_formsemestre_recapcomplet_xml(
|
|||||||
doc = ElementTree.Element(
|
doc = ElementTree.Element(
|
||||||
"recapsemestre", formsemestre_id=str(formsemestre_id), date=docdate
|
"recapsemestre", formsemestre_id=str(formsemestre_id), date=docdate
|
||||||
)
|
)
|
||||||
evals = sco_evaluations.do_evaluation_etat_in_sem(formsemestre_id)
|
evals = sco_evaluations.do_evaluation_etat_in_sem(formsemestre)
|
||||||
doc.append(
|
doc.append(
|
||||||
ElementTree.Element(
|
ElementTree.Element(
|
||||||
"evals_info",
|
"evals_info",
|
||||||
@ -408,7 +408,7 @@ def gen_formsemestre_recapcomplet_json(
|
|||||||
docdate = ""
|
docdate = ""
|
||||||
else:
|
else:
|
||||||
docdate = datetime.datetime.now().isoformat()
|
docdate = datetime.datetime.now().isoformat()
|
||||||
evals = sco_evaluations.do_evaluation_etat_in_sem(formsemestre_id)
|
evals = sco_evaluations.do_evaluation_etat_in_sem(formsemestre)
|
||||||
js_data = {
|
js_data = {
|
||||||
"docdate": docdate,
|
"docdate": docdate,
|
||||||
"formsemestre_id": formsemestre_id,
|
"formsemestre_id": formsemestre_id,
|
||||||
|
@ -1440,17 +1440,6 @@ EMO_PREV_ARROW = "❮"
|
|||||||
EMO_NEXT_ARROW = "❯"
|
EMO_NEXT_ARROW = "❯"
|
||||||
|
|
||||||
|
|
||||||
def sort_dates(L, reverse=False):
|
|
||||||
"""Return sorted list of dates, allowing None items (they are put at the beginning)"""
|
|
||||||
mindate = datetime.datetime(datetime.MINYEAR, 1, 1)
|
|
||||||
try:
|
|
||||||
return sorted(L, key=lambda x: x or mindate, reverse=reverse)
|
|
||||||
except:
|
|
||||||
# Helps debugging
|
|
||||||
log("sort_dates( %s )" % L)
|
|
||||||
raise
|
|
||||||
|
|
||||||
|
|
||||||
def heterogeneous_sorting_key(x):
|
def heterogeneous_sorting_key(x):
|
||||||
"key to sort non homogeneous sequences"
|
"key to sort non homogeneous sequences"
|
||||||
return (float(x), "") if isinstance(x, (bool, float, int)) else (-1e34, str(x))
|
return (float(x), "") if isinstance(x, (bool, float, int)) else (-1e34, str(x))
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
# -*- mode: python -*-
|
# -*- mode: python -*-
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
SCOVERSION = "9.6.90"
|
SCOVERSION = "9.6.91"
|
||||||
|
|
||||||
SCONAME = "ScoDoc"
|
SCONAME = "ScoDoc"
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user