From acd1b36d72d760980dacc23a215b1f1673842f5c Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Sun, 18 Sep 2022 16:53:00 +0200 Subject: [PATCH] Bulletins: evals rat. et sessions 2. Fix #492. --- app/but/bulletin_but.py | 13 +++++---- app/but/bulletin_but_pdf.py | 8 ++++-- app/but/jury_but_pv.py | 4 ++- app/comp/moy_mod.py | 13 +++++---- app/scodoc/sco_bulletins.py | 40 ++++++++++++++++----------- app/scodoc/sco_exceptions.py | 2 +- app/scodoc/sco_formsemestre_status.py | 13 ++++++--- app/scodoc/sco_import_etuds.py | 17 ++++++------ app/static/js/releve-but.js | 4 +-- app/views/notes.py | 4 +-- 10 files changed, 70 insertions(+), 48 deletions(-) diff --git a/app/but/bulletin_but.py b/app/but/bulletin_but.py index 506f8754..af33fac9 100644 --- a/app/but/bulletin_but.py +++ b/app/but/bulletin_but.py @@ -13,7 +13,7 @@ import numpy as np from flask import g, has_request_context, url_for from app.comp.res_but import ResultatsSemestreBUT -from app.models import FormSemestre, Identite +from app.models import Evaluation, FormSemestre, Identite from app.models.groups import GroupDescr from app.models.ues import UniteEns from app.scodoc import sco_bulletins, sco_utils as scu @@ -249,7 +249,7 @@ class BulletinBUT: } return d - def etud_eval_results(self, etud, e) -> dict: + def etud_eval_results(self, etud, e: Evaluation) -> dict: "dict resultats d'un étudiant à une évaluation" # eval_notes est une pd.Series avec toutes les notes des étudiants inscrits eval_notes = self.res.modimpls_results[e.moduleimpl_id].evals_notes[e.id] @@ -265,12 +265,14 @@ class BulletinBUT: poids = collections.defaultdict(lambda: 0.0) d = { "id": e.id, - "description": e.description, + "coef": fmt_note(e.coefficient) + if e.evaluation_type == scu.EVALUATION_NORMALE + else None, "date": e.jour.isoformat() if e.jour else None, + "description": e.description, + "evaluation_type": e.evaluation_type, "heure_debut": e.heure_debut.strftime("%H:%M") if e.heure_debut else None, "heure_fin": e.heure_fin.strftime("%H:%M") if e.heure_debut else None, - "coef": fmt_note(e.coefficient), - "poids": poids, "note": { "value": fmt_note( eval_notes[etud.id], @@ -280,6 +282,7 @@ class BulletinBUT: "max": fmt_note(notes_ok.max()), "moy": fmt_note(notes_ok.mean()), }, + "poids": poids, "url": url_for( "notes.evaluation_listenotes", scodoc_dept=g.scodoc_dept, diff --git a/app/but/bulletin_but_pdf.py b/app/but/bulletin_but_pdf.py index e3e40c51..3954c818 100644 --- a/app/but/bulletin_but_pdf.py +++ b/app/but/bulletin_but_pdf.py @@ -13,6 +13,7 @@ from reportlab.platypus import Paragraph, Spacer from app.scodoc.sco_bulletins_standard import BulletinGeneratorStandard from app.scodoc import gen_tables from app.scodoc.sco_codes_parcours import UE_SPORT +from app.scodoc import sco_utils as scu class BulletinGeneratorStandardBUT(BulletinGeneratorStandard): @@ -312,18 +313,19 @@ class BulletinGeneratorStandardBUT(BulletinGeneratorStandard): ] return col_keys, rows, pdf_style, col_widths - def evaluations_rows(self, rows, evaluations, ue_acros=()): + def evaluations_rows(self, rows, evaluations: list[dict], ue_acros=()): "lignes des évaluations" for e in evaluations: + coef = e["coef"] if e["evaluation_type"] == scu.EVALUATION_NORMALE else "*" t = { "titre": f"{e['description'] or ''}", "moyenne": e["note"]["value"], "_moyenne_pdf": Paragraph( f"""{e["note"]["value"]}""" ), - "coef": e["coef"], + "coef": coef, "_coef_pdf": Paragraph( - f"{e['coef']}" + f"{coef}" ), "_pdf_style": [ ( diff --git a/app/but/jury_but_pv.py b/app/but/jury_but_pv.py index ca7f33b8..78d8d4de 100644 --- a/app/but/jury_but_pv.py +++ b/app/but/jury_but_pv.py @@ -90,7 +90,9 @@ def pvjury_table_but(formsemestre_id: int, format="html"): if deca else "-", "decision_but": deca.code_valide if deca else "", - "devenir": ", ".join([f"S{i}" for i in deca.get_autorisations_passage()]), + "devenir": ", ".join([f"S{i}" for i in deca.get_autorisations_passage()]) + if deca + else "", } rows.append(row) diff --git a/app/comp/moy_mod.py b/app/comp/moy_mod.py index 673e356c..425f2b4e 100644 --- a/app/comp/moy_mod.py +++ b/app/comp/moy_mod.py @@ -124,7 +124,7 @@ class ModuleImplResults: Évaluation "complete" (prise en compte dans les calculs) si: - soit tous les étudiants inscrits au module ont des notes - soit elle a été déclarée "à prise en compte immédiate" (publish_incomplete) - + ou est une évaluation de rattrapage ou de session 2 Évaluation "attente" (prise en compte dans les calculs, mais il y manque des notes) ssi il y a des étudiants inscrits au semestre et au module qui ont des notes ATT. @@ -148,11 +148,14 @@ class ModuleImplResults: eval_df = self._load_evaluation_notes(evaluation) # is_complete ssi tous les inscrits (non dem) au semestre ont une note # ou évaluation déclarée "à prise en compte immédiate" - # Les évaluations de rattrapage et 2eme session sont toujours incomplètes - # car on calcule leur moyenne à part. + # Les évaluations de rattrapage et 2eme session sont toujours complètes + etudids_sans_note = inscrits_module - set(eval_df.index) # sans les dem. - is_complete = (evaluation.evaluation_type == scu.EVALUATION_NORMALE) and ( - evaluation.publish_incomplete or (not etudids_sans_note) + is_complete = ( + (evaluation.evaluation_type == scu.EVALUATION_RATTRAPAGE) + or (evaluation.evaluation_type == scu.EVALUATION_SESSION2) + or (evaluation.publish_incomplete) + or (not etudids_sans_note) ) self.evaluations_completes.append(is_complete) self.evaluations_completes_dict[evaluation.id] = is_complete diff --git a/app/scodoc/sco_bulletins.py b/app/scodoc/sco_bulletins.py index ceb1a8c3..22734932 100644 --- a/app/scodoc/sco_bulletins.py +++ b/app/scodoc/sco_bulletins.py @@ -30,6 +30,7 @@ """ import email import time +import numpy as np from flask import g, request from flask import flash, jsonify, render_template, url_for @@ -575,18 +576,20 @@ def _ue_mod_bulletin( else: e["name"] = "Points de bonus sur cette UE" else: - e["name"] = e["description"] or "le %s" % e["jour"] - e["target_html"] = ( - "evaluation_listenotes?evaluation_id=%s&format=html&tf_submitted=1" - % e["evaluation_id"] + e["name"] = e["description"] or f"le {e['jour']}" + e["target_html"] = url_for( + "notes.evaluation_listenotes", + scodoc_dept=g.scodoc_dept, + evaluation_id=e["evaluation_id"], + format="html", + tf_submitted=1, ) - e["name_html"] = '%s' % ( - e["target_html"], - e["name"], - ) - val = e["notes"].get(etudid, {"value": "NP"})[ - "value" - ] # NA si etud demissionnaire + e[ + "name_html" + ] = f"""{e['name']}""" + val = e["notes"].get(etudid, {"value": "NP"})["value"] + # val est NP si etud demissionnaire if val == "NP": e["note_txt"] = "nd" e["note_html"] = 'nd' @@ -604,13 +607,18 @@ def _ue_mod_bulletin( if e["evaluation_type"] == scu.EVALUATION_RATTRAPAGE: e["coef_txt"] = "rat." elif e["evaluation_type"] == scu.EVALUATION_SESSION2: - e["coef_txt"] = "sess. 2" + e["coef_txt"] = "Ses. 2" if e["etat"]["evalattente"]: mod_attente = True # une eval en attente dans ce module - if (not is_malus) or (val != "NP"): - mod["evaluations"].append( - e - ) # ne liste pas les eval malus sans notes + if ((not is_malus) or (val != "NP")) and ( + ( + e["evaluation_type"] == scu.EVALUATION_NORMALE + or not np.isnan(val) + ) + ): + # ne liste pas les eval malus sans notes + # ni les rattrapages et sessions 2 si pas de note + mod["evaluations"].append(e) # Evaluations incomplètes ou futures: mod["evaluations_incompletes"] = [] diff --git a/app/scodoc/sco_exceptions.py b/app/scodoc/sco_exceptions.py index 967b26f7..77e60c91 100644 --- a/app/scodoc/sco_exceptions.py +++ b/app/scodoc/sco_exceptions.py @@ -150,7 +150,7 @@ class ScoInvalidIdType(ScoValueError):

Si le problème persiste, merci de contacter l'assistance via la liste de diffusion Notes - ou le salon Discord. + ou de préférence le salon Discord.

Message serveur: {msg}

""" diff --git a/app/scodoc/sco_formsemestre_status.py b/app/scodoc/sco_formsemestre_status.py index cfe24135..89748250 100644 --- a/app/scodoc/sco_formsemestre_status.py +++ b/app/scodoc/sco_formsemestre_status.py @@ -40,12 +40,14 @@ from app.comp import res_sem from app.comp.res_compat import NotesTableCompat from app.models import Module from app.models.formsemestre import FormSemestre -from app.models.moduleimpls import ModuleImpl import app.scodoc.sco_utils as scu from app.scodoc.sco_utils import ModuleType -import app.scodoc.notesdb as ndb from app.scodoc.sco_permissions import Permission -from app.scodoc.sco_exceptions import ScoValueError, ScoInvalidDateError +from app.scodoc.sco_exceptions import ( + ScoValueError, + ScoInvalidDateError, + ScoInvalidIdType, +) from app.scodoc import html_sco_header from app.scodoc import htmlutils from app.scodoc import sco_abs @@ -58,7 +60,6 @@ from app.scodoc import sco_evaluations from app.scodoc import sco_evaluation_db from app.scodoc import sco_formations from app.scodoc import sco_formsemestre -from app.scodoc import sco_formsemestre_edit from app.scodoc import sco_formsemestre_inscriptions from app.scodoc import sco_groups from app.scodoc import sco_moduleimpl @@ -1008,6 +1009,10 @@ Il y a des notes en attente ! Le classement des étudiants n'a qu'une valeur ind def formsemestre_status(formsemestre_id=None): """Tableau de bord semestre HTML""" # porté du DTML + if formsemestre_id is not None and not isinstance(formsemestre_id, int): + raise ScoInvalidIdType( + "formsemestre_bulletinetud: formsemestre_id must be an integer !" + ) formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id) modimpls = sco_moduleimpl.moduleimpl_withmodule_list( formsemestre_id=formsemestre_id diff --git a/app/scodoc/sco_import_etuds.py b/app/scodoc/sco_import_etuds.py index 629dec35..53b4563d 100644 --- a/app/scodoc/sco_import_etuds.py +++ b/app/scodoc/sco_import_etuds.py @@ -740,7 +740,8 @@ def scolars_import_admission(datafile, formsemestre_id=None, type_admission=None _ADM_PATTERN = re.compile(r"[\W]+", re.UNICODE) # supprime tout sauf alphanum -def adm_normalize_string(s): # normalize unicode title +def adm_normalize_string(s): + "normalize unicode title" return scu.suppress_accents(_ADM_PATTERN.sub("", s.strip().lower())).replace( "_", "" ) @@ -750,16 +751,15 @@ def adm_get_fields(titles, formsemestre_id): """Cherche les colonnes importables dans les titres (ligne 1) du fichier excel return: { idx : (field_name, convertor) } """ - # log('adm_get_fields: titles=%s' % titles) - Fmt = sco_import_format_dict() + format_dict = sco_import_format_dict() fields = {} idx = 0 for title in titles: title_n = adm_normalize_string(title) - for k in Fmt: - for v in Fmt[k]["aliases"]: + for k in format_dict: + for v in format_dict[k]["aliases"]: if adm_normalize_string(v) == title_n: - typ = Fmt[k]["type"] + typ = format_dict[k]["type"] if typ == "real": convertor = adm_convert_real elif typ == "integer" or typ == "int": @@ -769,10 +769,9 @@ def adm_get_fields(titles, formsemestre_id): # doublons ? if k in [x[0] for x in fields.values()]: raise ScoFormatError( - 'scolars_import_admission: titre "%s" en double (ligne 1)' - % (title), + f"""scolars_import_admission: titre "{title}" en double (ligne 1)""", dest_url=url_for( - "scolar.form_students_import_infos_admissions_apb", + "scolar.form_students_import_infos_admissions", scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre_id, ), diff --git a/app/static/js/releve-but.js b/app/static/js/releve-but.js index a84e0dac..3b421338 100644 --- a/app/static/js/releve-but.js +++ b/app/static/js/releve-but.js @@ -399,7 +399,7 @@ class releveBUT extends HTMLElement {
${module.titre} - ${evaluation.description || "Note"}
${evaluation.note.value ?? "-"} - Coef. ${evaluation.coef} + Coef. ${evaluation.coef ?? "*"}
`; @@ -450,7 +450,7 @@ class releveBUT extends HTMLElement {
${this.URL(evaluation.url, evaluation.description || "Évaluation")}
${evaluation.note.value} - Coef. ${evaluation.coef} + Coef. ${evaluation.coef ?? "*"}
Coef
${evaluation.coef}
diff --git a/app/views/notes.py b/app/views/notes.py index 82348c3b..79b1dde8 100644 --- a/app/views/notes.py +++ b/app/views/notes.py @@ -265,7 +265,7 @@ def formsemestre_bulletinetud( ): format = format or "html" - if not isinstance(formsemestre_id, int): + if formsemestre_id is not None and not isinstance(formsemestre_id, int): raise ScoInvalidIdType( "formsemestre_bulletinetud: formsemestre_id must be an integer !" ) @@ -1775,7 +1775,7 @@ def evaluation_listenotes(): if "moduleimpl_id" in vals and vals["moduleimpl_id"]: moduleimpl_id = int(vals["moduleimpl_id"]) except ValueError as exc: - raise ScoValueError("adresse invalide !") from exc + raise ScoValueError("evaluation_listenotes: id invalides !") from exc format = vals.get("format", "html") html_content, page_title = sco_liste_notes.do_evaluation_listenotes(