From 7edd0511835ca58d8bcb551541632808ac56b38a Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Tue, 1 Mar 2022 09:34:05 +0100 Subject: [PATCH 01/12] Fix: bonus St Quentin / Ville d'Avray --- app/comp/bonus_spo.py | 17 +++++++++++------ sco_version.py | 2 +- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/app/comp/bonus_spo.py b/app/comp/bonus_spo.py index 99738336e..cb3d93e4f 100644 --- a/app/comp/bonus_spo.py +++ b/app/comp/bonus_spo.py @@ -338,9 +338,12 @@ class BonusAisneStQuentin(BonusSportAdditif): # pas d'étudiants ou pas d'UE ou pas de module... return # Calcule moyenne pondérée des notes de sport: - bonus_moy_arr = np.sum( - sem_modimpl_moys_inscrits * modimpl_coefs_etuds_no_nan, axis=1 - ) / np.sum(modimpl_coefs_etuds_no_nan, axis=1) + with np.errstate(invalid="ignore"): # ignore les 0/0 (-> NaN) + bonus_moy_arr = np.sum( + sem_modimpl_moys_inscrits * modimpl_coefs_etuds_no_nan, axis=1 + ) / np.sum(modimpl_coefs_etuds_no_nan, axis=1) + np.nan_to_num(bonus_moy_arr, nan=0.0, copy=False) + bonus_moy_arr[bonus_moy_arr < 10.0] = 0.0 bonus_moy_arr[bonus_moy_arr >= 18.1] = 0.5 bonus_moy_arr[bonus_moy_arr >= 16.1] = 0.4 @@ -823,9 +826,11 @@ class BonusVilleAvray(BonusSport): # pas d'étudiants ou pas d'UE ou pas de module... return # Calcule moyenne pondérée des notes de sport: - bonus_moy_arr = np.sum( - sem_modimpl_moys_inscrits * modimpl_coefs_etuds_no_nan, axis=1 - ) / np.sum(modimpl_coefs_etuds_no_nan, axis=1) + with np.errstate(invalid="ignore"): # ignore les 0/0 (-> NaN) + bonus_moy_arr = np.sum( + sem_modimpl_moys_inscrits * modimpl_coefs_etuds_no_nan, axis=1 + ) / np.sum(modimpl_coefs_etuds_no_nan, axis=1) + np.nan_to_num(bonus_moy_arr, nan=0.0, copy=False) bonus_moy_arr[bonus_moy_arr < 10.0] = 0.0 bonus_moy_arr[bonus_moy_arr >= 16.0] = 0.3 bonus_moy_arr[bonus_moy_arr >= 12.0] = 0.2 diff --git a/sco_version.py b/sco_version.py index b47a266d7..fbbf5bc82 100644 --- a/sco_version.py +++ b/sco_version.py @@ -1,7 +1,7 @@ # -*- mode: python -*- # -*- coding: utf-8 -*- -SCOVERSION = "9.1.66" +SCOVERSION = "9.1.67" SCONAME = "ScoDoc" From c5c0b510ec599a2486d4d39d9bc4519c7c51d41d Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Tue, 1 Mar 2022 09:48:37 +0100 Subject: [PATCH 02/12] filename export formations --- app/scodoc/sco_formations.py | 8 +++++++- app/scodoc/sco_utils.py | 21 ++++++++++++++++----- 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/app/scodoc/sco_formations.py b/app/scodoc/sco_formations.py index caeeb707b..573c95dfb 100644 --- a/app/scodoc/sco_formations.py +++ b/app/scodoc/sco_formations.py @@ -151,8 +151,14 @@ def formation_export( if mod["ects"] is None: del mod["ects"] + filename = f"scodoc_formation_{formation.departement.acronym}_{formation.acronyme or ''}_v{formation.version}" return scu.sendResult( - F, name="formation", format=format, force_outer_xml_tag=False, attached=True + F, + name="formation", + format=format, + force_outer_xml_tag=False, + attached=True, + filename=filename, ) diff --git a/app/scodoc/sco_utils.py b/app/scodoc/sco_utils.py index b3d99ac3e..7af19bc19 100644 --- a/app/scodoc/sco_utils.py +++ b/app/scodoc/sco_utils.py @@ -645,21 +645,30 @@ class ScoDocJSONEncoder(json.JSONEncoder): return json.JSONEncoder.default(self, o) -def sendJSON(data, attached=False): +def sendJSON(data, attached=False, filename=None): js = json.dumps(data, indent=1, cls=ScoDocJSONEncoder) return send_file( - js, filename="sco_data.json", mime=JSON_MIMETYPE, attached=attached + js, filename=filename or "sco_data.json", mime=JSON_MIMETYPE, attached=attached ) -def sendXML(data, tagname=None, force_outer_xml_tag=True, attached=False, quote=True): +def sendXML( + data, + tagname=None, + force_outer_xml_tag=True, + attached=False, + quote=True, + filename=None, +): if type(data) != list: data = [data] # always list-of-dicts if force_outer_xml_tag: data = [{tagname: data}] tagname += "_list" doc = sco_xml.simple_dictlist2xml(data, tagname=tagname, quote=quote) - return send_file(doc, filename="sco_data.xml", mime=XML_MIMETYPE, attached=attached) + return send_file( + doc, filename=filename or "sco_data.xml", mime=XML_MIMETYPE, attached=attached + ) def sendResult( @@ -669,6 +678,7 @@ def sendResult( force_outer_xml_tag=True, attached=False, quote_xml=True, + filename=None, ): if (format is None) or (format == "html"): return data @@ -679,9 +689,10 @@ def sendResult( force_outer_xml_tag=force_outer_xml_tag, attached=attached, quote=quote_xml, + filename=filename, ) elif format == "json": - return sendJSON(data, attached=attached) + return sendJSON(data, attached=attached, filename=filename) else: raise ValueError("invalid format: %s" % format) From 6943ccb8729823ad9f73e8b76e6f03ae5db2b8ad Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Tue, 1 Mar 2022 10:16:34 +0100 Subject: [PATCH 03/12] typo (sel. modules BUT) --- app/scodoc/sco_formsemestre_edit.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/scodoc/sco_formsemestre_edit.py b/app/scodoc/sco_formsemestre_edit.py index f056a3e62..7174cfc3d 100644 --- a/app/scodoc/sco_formsemestre_edit.py +++ b/app/scodoc/sco_formsemestre_edit.py @@ -545,7 +545,7 @@ def do_formsemestre_createwithmodules(edit=False): for semestre_id in semestre_ids: if formation.is_apc(): # pour restreindre l'édition aux module du semestre sélectionné - tr_class = 'class="sem{semestre_id}"' + tr_class = f'class="sem{semestre_id}"' else: tr_class = "" if edit: From 10c96ad683fa0ca1a899f160588e1e6b769023c8 Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Tue, 1 Mar 2022 10:21:15 +0100 Subject: [PATCH 04/12] PE: check submitted template (utf8) --- app/pe/pe_view.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/app/pe/pe_view.py b/app/pe/pe_view.py index 5a98d375f..5af1a5754 100644 --- a/app/pe/pe_view.py +++ b/app/pe/pe_view.py @@ -36,6 +36,7 @@ """ from flask import send_file, request +from app.scodoc.sco_exceptions import ScoValueError import app.scodoc.sco_utils as scu from app.scodoc import sco_formsemestre @@ -97,8 +98,12 @@ def pe_view_sem_recap( template_latex = "" # template fourni via le formulaire Web if avis_tmpl_file: - template_latex = avis_tmpl_file.read().decode('utf-8') - template_latex = template_latex + try: + template_latex = avis_tmpl_file.read().decode("utf-8") + except UnicodeDecodeError as e: + raise ScoValueError( + "Données (template) invalides (caractères non UTF8 ?)" + ) from e else: # template indiqué dans préférences ScoDoc ? template_latex = pe_avislatex.get_code_latex_from_scodoc_preference( @@ -114,7 +119,7 @@ def pe_view_sem_recap( footer_latex = "" # template fourni via le formulaire Web if footer_tmpl_file: - footer_latex = footer_tmpl_file.read().decode('utf-8') + footer_latex = footer_tmpl_file.read().decode("utf-8") footer_latex = footer_latex else: footer_latex = pe_avislatex.get_code_latex_from_scodoc_preference( From f0e731d151fbb2a28e0e65a3a55650f0065ec27a Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Tue, 1 Mar 2022 10:33:53 +0100 Subject: [PATCH 05/12] Fix: bulletin classique quand coef UE None --- app/scodoc/sco_bulletins.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/app/scodoc/sco_bulletins.py b/app/scodoc/sco_bulletins.py index a5d84cf4f..47947bd36 100644 --- a/app/scodoc/sco_bulletins.py +++ b/app/scodoc/sco_bulletins.py @@ -323,9 +323,7 @@ def formsemestre_bulletinetud_dict(formsemestre_id, etudid, version="long"): if ue_status["coef_ue"] != None: u["coef_ue_txt"] = scu.fmt_coef(ue_status["coef_ue"]) else: - # C'est un bug: - log("u=" + pprint.pformat(u)) - raise Exception("invalid None coef for ue") + u["coef_ue_txt"] = "-" if ( dpv From 523ad7ad2a19caed963f9a5170ba6930a35afa67 Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Tue, 1 Mar 2022 10:40:38 +0100 Subject: [PATCH 06/12] Modif bonus La Rochelle --- app/comp/bonus_spo.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/app/comp/bonus_spo.py b/app/comp/bonus_spo.py index cb3d93e4f..aa7697d5a 100644 --- a/app/comp/bonus_spo.py +++ b/app/comp/bonus_spo.py @@ -198,7 +198,10 @@ class BonusSportAdditif(BonusSport): à la moyenne générale du semestre déjà obtenue par l'étudiant. """ - seuil_moy_gen = 10.0 # seuls les points au dessus du seuil sont comptés + seuil_moy_gen = 10.0 # seuls les bonus au dessus du seuil sont pris en compte + seuil_comptage = ( + None # les points au dessus du seuil sont comptés (defaut: seuil_moy_gen) + ) proportion_point = 0.05 # multiplie les points au dessus du seuil def compute_bonus(self, sem_modimpl_moys_inscrits, modimpl_coefs_etuds_no_nan): @@ -211,10 +214,13 @@ class BonusSportAdditif(BonusSport): if 0 in sem_modimpl_moys_inscrits.shape: # pas d'étudiants ou pas d'UE ou pas de module... return + seuil_comptage = ( + self.seuil_moy_gen if self.seuil_comptage is None else self.seuil_comptage + ) bonus_moy_arr = np.sum( np.where( sem_modimpl_moys_inscrits > self.seuil_moy_gen, - (sem_modimpl_moys_inscrits - self.seuil_moy_gen) + (sem_modimpl_moys_inscrits - self.seuil_comptage) * self.proportion_point, 0.0, ), @@ -607,8 +613,9 @@ class BonusLaRochelle(BonusSportAdditif): name = "bonus_iutlr" displayed_name = "IUT de La Rochelle" - seuil_moy_gen = 10.0 # tous les points sont comptés - proportion_point = 0.01 + seuil_moy_gen = 10.0 # si bonus > 10, + seuil_comptage = 0.0 # tous les points sont comptés + proportion_point = 0.01 # 1% class BonusLeHavre(BonusSportMultiplicatif): From c0719df0c0f0777c79fa3ff27dbda6250c30e194 Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Tue, 1 Mar 2022 19:27:03 +0100 Subject: [PATCH 07/12] noms modules sur menu saisie absences --- app/scodoc/sco_edit_module.py | 7 +++++-- app/views/absences.py | 5 ++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/app/scodoc/sco_edit_module.py b/app/scodoc/sco_edit_module.py index 9ea4e2fe7..63bcb72ab 100644 --- a/app/scodoc/sco_edit_module.py +++ b/app/scodoc/sco_edit_module.py @@ -562,7 +562,7 @@ def module_edit(module_id=None): "code", { "size": 10, - "explanation": "code du module (doit être unique dans la formation)", + "explanation": "code du module (issu du programme, exemple M1203 ou R2.01. Doit être unique dans la formation)", "allow_null": False, "validator": lambda val, field, formation_id=formation_id: check_module_code_unicity( val, field, formation_id, module_id=module_id @@ -701,7 +701,10 @@ def module_edit(module_id=None): { "title": "Code Apogée", "size": 25, - "explanation": "(optionnel) code élément pédagogique Apogée ou liste de codes ELP séparés par des virgules", + "explanation": """(optionnel) code élément pédagogique Apogée ou liste de codes ELP + séparés par des virgules (ce code est propre à chaque établissement, se rapprocher + du référent Apogée). + """, "validator": lambda val, _: len(val) < APO_CODE_STR_LEN, }, ), diff --git a/app/views/absences.py b/app/views/absences.py index cf8de2c18..bdbae1458 100644 --- a/app/views/absences.py +++ b/app/views/absences.py @@ -611,8 +611,7 @@ def SignaleAbsenceGrSemestre( """\n""" % { "modimpl_id": modimpl["moduleimpl_id"], - "modname": modimpl["module"]["code"] - or "" + "modname": (modimpl["module"]["code"] or "") + " " + (modimpl["module"]["abbrev"] or modimpl["module"]["titre"]), "sel": sel, @@ -624,7 +623,7 @@ def SignaleAbsenceGrSemestre( sel = "selected" # aucun module specifie H.append( """

-Module concerné par ces absences (%(optionel_txt)s): +Module concerné par ces absences (%(optionel_txt)s):