1
0
forked from ScoDoc/ScoDoc

Modernisation code état évaluations / tableau de bord formsemestre

This commit is contained in:
Emmanuel Viennet 2024-02-02 15:16:55 +01:00
parent 952132695f
commit 0bf3c22cd0
9 changed files with 220 additions and 214 deletions

View File

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

View File

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

View File

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

View File

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

View File

@ -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 -essayer. Si le problème persiste, merci de contacter l'assistance ScoDoc
</p>
"""
super().__init__(msg)

View File

@ -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"&nbsp;en {sem.modalite}") H.append(f"&nbsp;en {formsemestre.modalite}")
if sem.etapes: if formsemestre.etapes:
H.append( H.append(
f"""&nbsp;&nbsp;&nbsp;(étape <b><tt>{ f"""&nbsp;&nbsp;&nbsp;(é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">

View File

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

View File

@ -1440,17 +1440,6 @@ EMO_PREV_ARROW = "&#10094;"
EMO_NEXT_ARROW = "&#10095;" EMO_NEXT_ARROW = "&#10095;"
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))

View File

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