diff --git a/app/comp/bonus_spo.py b/app/comp/bonus_spo.py index 99738336..d1c3ddca 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,11 +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) - * self.proportion_point, + (sem_modimpl_moys_inscrits - seuil_comptage) * self.proportion_point, 0.0, ), axis=1, @@ -338,9 +343,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 @@ -604,8 +612,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): @@ -823,9 +832,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/app/models/formsemestre.py b/app/models/formsemestre.py index 1ae0a5a8..c66df782 100644 --- a/app/models/formsemestre.py +++ b/app/models/formsemestre.py @@ -207,7 +207,11 @@ class FormSemestre(db.Model): modimpls = self.modimpls.all() if self.formation.is_apc(): modimpls.sort( - key=lambda m: (m.module.module_type, m.module.numero, m.module.code) + key=lambda m: ( + m.module.module_type or 0, + m.module.numero or 0, + m.module.code or 0, + ) ) else: modimpls.sort( diff --git a/app/pe/pe_view.py b/app/pe/pe_view.py index 5a98d375..5af1a575 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( diff --git a/app/scodoc/sco_bulletins.py b/app/scodoc/sco_bulletins.py index c7a75ea4..6db18f91 100644 --- a/app/scodoc/sco_bulletins.py +++ b/app/scodoc/sco_bulletins.py @@ -300,9 +300,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 diff --git a/app/scodoc/sco_edit_apc.py b/app/scodoc/sco_edit_apc.py index c6b0151d..cec8b7c2 100644 --- a/app/scodoc/sco_edit_apc.py +++ b/app/scodoc/sco_edit_apc.py @@ -52,6 +52,7 @@ def html_edit_formation_apc( """ parcours = formation.get_parcours() assert parcours.APC_SAE + ressources = formation.modules.filter_by(module_type=ModuleType.RESSOURCE).order_by( Module.semestre_id, Module.numero, Module.code ) @@ -68,6 +69,19 @@ def html_edit_formation_apc( ).order_by( Module.semestre_id, Module.module_type.desc(), Module.numero, Module.code ) + + ues_by_sem = {} + ects_by_sem = {} + for semestre_idx in semestre_ids: + ues_by_sem[semestre_idx] = formation.ues.filter_by( + semestre_idx=semestre_idx + ).order_by(UniteEns.semestre_idx, UniteEns.numero, UniteEns.acronyme) + ects = [ue.ects for ue in ues_by_sem[semestre_idx]] + if None in ects: + ects_by_sem[semestre_idx] = 'manquant' + else: + ects_by_sem[semestre_idx] = sum(ects) + arrow_up, arrow_down, arrow_none = sco_groups.get_arrow_icons_tags() icons = { @@ -93,7 +107,8 @@ def html_edit_formation_apc( editable=editable, tag_editable=tag_editable, icons=icons, - UniteEns=UniteEns, + ues_by_sem=ues_by_sem, + ects_by_sem=ects_by_sem, ), ] for semestre_idx in semestre_ids: diff --git a/app/scodoc/sco_edit_module.py b/app/scodoc/sco_edit_module.py index c9e2ffe0..3068ee39 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/scodoc/sco_edit_ue.py b/app/scodoc/sco_edit_ue.py index ce2c67b7..8f18a5b2 100644 --- a/app/scodoc/sco_edit_ue.py +++ b/app/scodoc/sco_edit_ue.py @@ -305,7 +305,7 @@ def ue_edit(ue_id=None, create=False, formation_id=None, default_semestre_idx=No ( "numero", { - "size": 2, + "size": 4, "explanation": "numéro (1,2,3,4) de l'UE pour l'ordre d'affichage", "type": "int", }, @@ -722,16 +722,16 @@ du programme" (menu "Semestre") si vous avez un semestre en cours); {formation.referentiel_competence.type_titre} {formation.referentiel_competence.specialite_long} - """ +  """ msg_refcomp = "changer" H.append( f""" ", ] ) diff --git a/app/scodoc/sco_utils.py b/app/scodoc/sco_utils.py index b3d99ac3..196c2c21 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) @@ -799,7 +810,7 @@ def abbrev_prenom(prenom): # def timedate_human_repr(): - "representation du temps courant pour utilisateur: a localiser" + "representation du temps courant pour utilisateur" return time.strftime("%d/%m/%Y à %Hh%M") diff --git a/app/templates/pn/form_ues.html b/app/templates/pn/form_ues.html index 98c590f7..a91f2942 100644 --- a/app/templates/pn/form_ues.html +++ b/app/templates/pn/form_ues.html @@ -3,11 +3,9 @@
Unités d'Enseignement (UEs)
{% for semestre_idx in semestre_ids %} -
Semestre S{{semestre_idx}}
+
Semestre S{{semestre_idx}} (ECTS: {{ects_by_sem[semestre_idx] | safe}})