From 0bf3c22cd0b22f3257da2a6458c10c83ab46e64b Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Fri, 2 Feb 2024 15:16:55 +0100 Subject: [PATCH] =?UTF-8?q?Modernisation=20code=20=C3=A9tat=20=C3=A9valuat?= =?UTF-8?q?ions=20/=20tableau=20de=20bord=20formsemestre?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/comp/moy_mod.py | 6 +- app/comp/res_common.py | 104 ++++++++++++++++---- app/comp/res_compat.py | 53 +++++----- app/scodoc/sco_evaluations.py | 134 +++++++------------------- app/scodoc/sco_exceptions.py | 12 +++ app/scodoc/sco_formsemestre_status.py | 108 ++++++++++----------- app/scodoc/sco_recapcomplet.py | 4 +- app/scodoc/sco_utils.py | 11 --- sco_version.py | 2 +- 9 files changed, 220 insertions(+), 214 deletions(-) diff --git a/app/comp/moy_mod.py b/app/comp/moy_mod.py index ad7c45940..ec9c450a9 100644 --- a/app/comp/moy_mod.py +++ b/app/comp/moy_mod.py @@ -56,6 +56,7 @@ class EvaluationEtat: evaluation_id: int nb_attente: int + nb_notes: int # nb notes d'étudiants inscrits au semestre et au modimpl is_complete: bool def to_dict(self): @@ -168,13 +169,15 @@ class ModuleImplResults: # 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 + # et met à NULL (NaN) 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: (ne prend en compte que les inscrits, non démissionnaires) 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_notes_inscr.iloc[ (eval_notes_inscr == scu.NOTES_ATTENTE).to_numpy() @@ -184,6 +187,7 @@ class ModuleImplResults: self.evaluations_etat[evaluation.id] = EvaluationEtat( evaluation_id=evaluation.id, nb_attente=len(eval_etudids_attente), + nb_notes=nb_notes, is_complete=is_complete, ) # au moins une note en ATT dans ce modimpl: diff --git a/app/comp/res_common.py b/app/comp/res_common.py index c6985ff56..9553381b3 100644 --- a/app/comp/res_common.py +++ b/app/comp/res_common.py @@ -9,12 +9,13 @@ from collections import Counter, defaultdict from collections.abc import Generator +import datetime from functools import cached_property from operator import attrgetter import numpy as np import pandas as pd - +import sqlalchemy as sa from flask import g, url_for from app import db @@ -22,14 +23,19 @@ from app.comp import res_sem from app.comp.res_cache import ResultatsCache from app.comp.jury import ValidationsSemestre from app.comp.moy_mod import ModuleImplResults -from app.models import FormSemestre, FormSemestreUECoef -from app.models import Identite -from app.models import ModuleImpl, ModuleImplInscription -from app.models import ScolarAutorisationInscription -from app.models.ues import UniteEns +from app.models import ( + Evaluation, + FormSemestre, + FormSemestreUECoef, + Identite, + ModuleImpl, + ModuleImplInscription, + ScolarAutorisationInscription, + UniteEns, +) from app.scodoc.sco_cache import ResultatsSemestreCache 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 @@ -192,16 +198,80 @@ class ResultatsSemestre(ResultatsCache): *[mr.etudids_attente for mr in self.modimpls_results.values()] ) - # # Etat des évaluations - # # (se substitue à do_evaluation_etat, sans les moyennes par groupes) - # def get_evaluations_etats(evaluation_id: int) -> dict: - # """Renvoie dict avec les clés: - # last_modif - # nb_evals_completes - # nb_evals_en_cours - # nb_evals_vides - # attente - # """ + # Etat des évaluations + def get_evaluation_etat(self, evaluation: Evaluation) -> dict: + """État d'une évaluation + { + "coefficient" : float, # 0 si None + "description" : str, # de l'évaluation, "" si None + "etat" { + "evalcomplete" : bool, + "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... def get_formsemestre_validations(self) -> ValidationsSemestre: diff --git a/app/comp/res_compat.py b/app/comp/res_compat.py index a9b605aa4..0e84538fb 100644 --- a/app/comp/res_compat.py +++ b/app/comp/res_compat.py @@ -423,30 +423,37 @@ class NotesTableCompat(ResultatsSemestre): ) return evaluations - def get_evaluations_etats(self) -> list[dict]: - """Liste de toutes les évaluations du semestre - [ {...evaluation et son etat...} ]""" - # TODO: à moderniser (voir dans ResultatsSemestre) - # utilisé par - # do_evaluation_etat_in_sem + def get_evaluations_etats(self) -> dict[int, dict]: + """ "état" de chaque évaluation du semestre + { + evaluation_id : { + "evalcomplete" : bool, + "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 - - if not hasattr(self, "_evaluations_etats"): - self._evaluations_etats = sco_evaluations.do_evaluation_list_in_sem( - self.formsemestre.id - ) - - return self._evaluations_etats - - def get_mod_evaluation_etat_list(self, moduleimpl_id) -> list[dict]: - """Liste des états des évaluations de ce module""" - # XXX TODO à moderniser: lent, recharge des données que l'on a déjà... - return [ - e - for e in self.get_evaluations_etats() - if e["moduleimpl_id"] == moduleimpl_id - ] + # ancienne version < 2024-02-02 + # def get_mod_evaluation_etat_list(self, moduleimpl_id) -> list[dict]: + # """Liste des états des évaluations de ce module + # ordonnée selon (numero desc, date_debut desc) + # """ + # # à moderniser: lent, recharge des données que l'on a déjà... + # # remplacemé par ResultatsSemestre.get_mod_evaluation_etat_list + # # + # return [ + # e + # for e in self.get_evaluations_etats() + # if e["moduleimpl_id"] == moduleimpl_id + # ] def get_moduleimpls_attente(self): """Liste des modimpls du semestre ayant des notes en attente""" diff --git a/app/scodoc/sco_evaluations.py b/app/scodoc/sco_evaluations.py index 382f883dd..487cf9ea4 100644 --- a/app/scodoc/sco_evaluations.py +++ b/app/scodoc/sco_evaluations.py @@ -40,7 +40,7 @@ from app import db from app.auth.models import User from app.comp import res_sem 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 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): - """Liste les évaluations de tous les modules de ce semestre. - Triée par module, numero desc, date_debut desc - Donne pour chaque eval son état (voir do_evaluation_etat) - { 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) +def _summarize_evals_etats(evals: list[dict]) -> dict: + """Synthétise les états d'une liste d'évaluations + evals: list of mappings (etats), + utilise e["etat"]["evalcomplete"], e["etat"]["nb_notes"], e["etat"]["last_modif"] -> nb_eval_completes, nb_evals_en_cours, nb_evals_vides, date derniere modif 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 dates = [] @@ -370,11 +302,8 @@ def _eval_etat(evals): if last_modif is not None: dates.append(e["etat"]["last_modif"]) - if dates: - dates = scu.sort_dates(dates) - last_modif = dates[-1] # date de derniere modif d'une note dans un module - else: - last_modif = "" + # date de derniere modif d'une note dans un module + last_modif = sorted(dates)[-1] if dates else "" return { "nb_evals_completes": nb_evals_completes, @@ -384,37 +313,42 @@ def _eval_etat(evals): } -def do_evaluation_etat_in_sem(formsemestre_id): - """-> nb_eval_completes, nb_evals_en_cours, nb_evals_vides, - 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" +def do_evaluation_etat_in_sem(formsemestre: FormSemestre) -> dict: + """-> { nb_eval_completes, nb_evals_en_cours, nb_evals_vides, + date derniere 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) - evals = nt.get_evaluations_etats() - etat = _eval_etat(evals) + evaluations_etats = nt.get_evaluations_etats() + # raccordement moche... + etat = _summarize_evals_etats([{"etat": v} for v in evaluations_etats.values()]) # Ajoute information sur notes en attente etat["attente"] = len(nt.get_moduleimpls_attente()) > 0 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""" - evals = nt.get_mod_evaluation_etat_list(moduleimpl_id) - etat = _eval_etat(evals) + evals = nt.get_mod_evaluation_etat_list(modimpl) + etat = _summarize_evals_etats(evals) # 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 diff --git a/app/scodoc/sco_exceptions.py b/app/scodoc/sco_exceptions.py index 776dd98d6..82339db95 100644 --- a/app/scodoc/sco_exceptions.py +++ b/app/scodoc/sco_exceptions.py @@ -230,3 +230,15 @@ class APIInvalidParams(Exception): class ScoFormationConflict(Exception): """Conflit cohérence formation (APC)""" + + +class ScoTemporaryError(ScoValueError): + """Erreurs temporaires rarissimes (caches ?)""" + + def __init__(self, msg: str = ""): + msg = """ +

"Erreur temporaire

+

Veuillez ré-essayer. Si le problème persiste, merci de contacter l'assistance ScoDoc +

+ """ + super().__init__(msg) diff --git a/app/scodoc/sco_formsemestre_status.py b/app/scodoc/sco_formsemestre_status.py index 21b8adf4e..5e42ac75f 100755 --- a/app/scodoc/sco_formsemestre_status.py +++ b/app/scodoc/sco_formsemestre_status.py @@ -627,9 +627,7 @@ def formsemestre_description_table( # car l'UE de rattachement n'a pas d'intérêt en BUT rows.append(ue_info) - mod_inscrits = sco_moduleimpl.do_moduleimpl_inscription_list( - moduleimpl_id=modimpl.id - ) + mod_nb_inscrits = nt.modimpls_results[modimpl.id].nb_inscrits_module enseignants = ", ".join(ens.get_prenomnom() for ens in modimpl.enseignants) row = { @@ -638,7 +636,7 @@ def formsemestre_description_table( "Code": modimpl.module.code or "", "Module": modimpl.module.abbrev or modimpl.module.titre, "_Module_class": "scotext", - "Inscrits": len(mod_inscrits), + "Inscrits": mod_nb_inscrits, "Responsable": sco_users.user_info(modimpl.responsable_id)["nomprenom"], "_Responsable_class": "scotext", "Enseignants": enseignants, @@ -680,7 +678,7 @@ def formsemestre_description_table( if with_evals: # 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 # Ajoute etat: eval_rows = [] @@ -942,10 +940,10 @@ def html_expr_diagnostic(diagnostics): def formsemestre_status_head(formsemestre_id: int = None, page_title: str = None): """En-tête HTML des pages "semestre" """ - sem: FormSemestre = db.session.get(FormSemestre, formsemestre_id) - if not sem: + formsemestre: FormSemestre = db.session.get(FormSemestre, formsemestre_id) + if not formsemestre: raise ScoValueError("Semestre inexistant (il a peut être été supprimé ?)") - formation: Formation = sem.formation + formation: Formation = formsemestre.formation parcours = formation.get_cursus() page_title = page_title or "Modules de " @@ -957,25 +955,25 @@ def formsemestre_status_head(formsemestre_id: int = None, page_title: str = None f"""") if formation.is_apc(): # 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( f""" @@ -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( '
Formation: {formation.titre} """, ] - if sem.semestre_id >= 0: - H.append(f", {parcours.SESSION_NAME} {sem.semestre_id}") - if sem.modalite: - H.append(f" en {sem.modalite}") - if sem.etapes: + if formsemestre.semestre_id >= 0: + H.append(f", {parcours.SESSION_NAME} {formsemestre.semestre_id}") + if formsemestre.modalite: + H.append(f" en {formsemestre.modalite}") + if formsemestre.etapes: H.append( f"""   (étape { - sem.etapes_apo_str() or "-" + formsemestre.etapes_apo_str() or "-" })""" ) H.append("
Parcours:
Évaluations: %(nb_evals_completes)s ok, %(nb_evals_en_cours)s en cours, %(nb_evals_vides)s vides' % evals @@ -1002,11 +1000,11 @@ def formsemestre_status_head(formsemestre_id: int = None, page_title: str = None """Il y a des notes en attente ! 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.""") - if sem.block_moyennes: + if formsemestre.block_moyennes: 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("""Ce semestre couvre plusieurs années scolaires !""") if warnings: H += [ @@ -1028,18 +1026,14 @@ def formsemestre_status(formsemestre_id=None, check_parcours=True): # S'assure que les groupes de parcours sont à jour: if int(check_parcours): formsemestre.setup_parcours_groups() - modimpls = sco_moduleimpl.moduleimpl_withmodule_list( - formsemestre_id=formsemestre_id - ) + modimpls = formsemestre.modimpls_sorted nt = res_sem.load_formsemestre_results(formsemestre) # Construit la liste de tous les enseignants de ce semestre: mails_enseignants = set(u.email for u in formsemestre.responsables) for modimpl in modimpls: - mails_enseignants.add(sco_users.user_info(modimpl["responsable_id"])["email"]) - mails_enseignants |= set( - [sco_users.user_info(m["ens_id"])["email"] for m in modimpl["ens"]] - ) + mails_enseignants.add(sco_users.user_info(modimpl.responsable_id)["email"]) + mails_enseignants |= {u.email for u in modimpl.enseignants if u.email} can_edit = formsemestre.can_be_edited_by(current_user) 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: # BUT: tableau ressources puis SAE 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 = [ m 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 += [ f""" @@ -1136,7 +1130,7 @@ def formsemestre_status(formsemestre_id=None, check_parcours=True): modimpls_classic = [ m 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 += [ "

", @@ -1168,8 +1162,10 @@ def formsemestre_status(formsemestre_id=None, check_parcours=True): adrlist = list(mails_enseignants - {None, ""}) if adrlist: H.append( - '

Courrier aux %d enseignants du semestre

' - % (",".join(adrlist), len(adrlist)) + f"""

+ Courrier aux { + len(adrlist)} enseignants du semestre +

""" ) return "".join(H) + html_sco_header.sco_footer() @@ -1189,7 +1185,7 @@ _TABLEAU_MODULES_FOOT = """
""" def formsemestre_tableau_modules( - modimpls: list[dict], + modimpls: list[ModuleImpl], nt, formsemestre: FormSemestre, can_edit=True, @@ -1200,11 +1196,11 @@ def formsemestre_tableau_modules( H = [] prev_ue_id = None for modimpl in modimpls: - mod: Module = db.session.get(Module, modimpl["module_id"]) + mod: Module = modimpl.module moduleimpl_status_url = url_for( "notes.moduleimpl_status", scodoc_dept=g.scodoc_dept, - moduleimpl_id=modimpl["moduleimpl_id"], + moduleimpl_id=modimpl.id, ) mod_descr = "Module " + (mod.titre or "") if mod.is_apc(): @@ -1221,48 +1217,45 @@ def formsemestre_tableau_modules( mod_descr += " (pas de coefficients) " else: mod_descr += ", coef. " + str(mod.coefficient) - mod_ens = sco_users.user_info(modimpl["responsable_id"])["nomcomplet"] - if modimpl["ens"]: + mod_ens = sco_users.user_info(modimpl.responsable_id)["nomcomplet"] + if modimpl.enseignants.count(): 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( - moduleimpl_id=modimpl["moduleimpl_id"] - ) - - ue = modimpl["ue"] - if show_ues and (prev_ue_id != ue["ue_id"]): - prev_ue_id = ue["ue_id"] - titre = ue["titre"] + mod_nb_inscrits = nt.modimpls_results[modimpl.id].nb_inscrits_module + ue = modimpl.module.ue + if show_ues and (prev_ue_id != ue.id): + prev_ue_id = ue.id + titre = ue.titre if use_ue_coefs: - titre += f""" (coef. {ue["coefficient"] or 0.0})""" + titre += f""" (coef. {ue.coefficient or 0.0})""" H.append( f""" - {ue["acronyme"]} + {ue.acronyme} {titre} """ ) expr = sco_compute_moy.get_ue_expression( - formsemestre.id, ue["ue_id"], html_quote=True + formsemestre.id, ue.id, html_quote=True ) if expr: H.append( f""" {expr} formule inutilisée en 9.2: supprimer""" ) H.append("") - if modimpl["ue"]["type"] != codes_cursus.UE_STANDARD: + if ue.type != codes_cursus.UE_STANDARD: fontorange = " fontorange" # style css additionnel else: 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: # tbd style si module non conforme if ( @@ -1282,10 +1275,10 @@ def formsemestre_tableau_modules( {mod.abbrev or mod.titre or ""} - {len(mod_inscrits)} + {mod_nb_inscrits} { - sco_users.user_info(modimpl["responsable_id"])["prenomnom"] + sco_users.user_info(modimpl.responsable_id)["prenomnom"] } @@ -1339,10 +1332,7 @@ def formsemestre_tableau_modules( ) elif mod.module_type == ModuleType.MALUS: nb_malus_notes = sum( - [ - e["etat"]["nb_notes"] - for e in nt.get_mod_evaluation_etat_list(modimpl["moduleimpl_id"]) - ] + e["etat"]["nb_notes"] for e in nt.get_mod_evaluation_etat_list(modimpl) ) H.append( f""" diff --git a/app/scodoc/sco_recapcomplet.py b/app/scodoc/sco_recapcomplet.py index d17f37c79..5f1b9c79e 100644 --- a/app/scodoc/sco_recapcomplet.py +++ b/app/scodoc/sco_recapcomplet.py @@ -367,7 +367,7 @@ def gen_formsemestre_recapcomplet_xml( doc = ElementTree.Element( "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( ElementTree.Element( "evals_info", @@ -408,7 +408,7 @@ def gen_formsemestre_recapcomplet_json( docdate = "" else: 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 = { "docdate": docdate, "formsemestre_id": formsemestre_id, diff --git a/app/scodoc/sco_utils.py b/app/scodoc/sco_utils.py index 88592561d..52ed9b8ed 100644 --- a/app/scodoc/sco_utils.py +++ b/app/scodoc/sco_utils.py @@ -1440,17 +1440,6 @@ EMO_PREV_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): "key to sort non homogeneous sequences" return (float(x), "") if isinstance(x, (bool, float, int)) else (-1e34, str(x)) diff --git a/sco_version.py b/sco_version.py index 66bec19bd..5f1bebf2b 100644 --- a/sco_version.py +++ b/sco_version.py @@ -1,7 +1,7 @@ # -*- mode: python -*- # -*- coding: utf-8 -*- -SCOVERSION = "9.6.90" +SCOVERSION = "9.6.91" SCONAME = "ScoDoc"