From 8a1569ac54b5b3434b1acfac5b0393c1055089e3 Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Thu, 19 May 2022 10:51:43 +0200 Subject: [PATCH 01/16] API: nom des permissions --- app/scodoc/sco_permissions.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/scodoc/sco_permissions.py b/app/scodoc/sco_permissions.py index ccf46e8e..fd04e510 100644 --- a/app/scodoc/sco_permissions.py +++ b/app/scodoc/sco_permissions.py @@ -50,10 +50,10 @@ _SCO_PERMISSIONS = ( (1 << 27, "RelationsEntreprisesCorrespondants", "Voir les correspondants"), # 27 à 39 ... réservé pour "entreprises" # Api scodoc9 - (1 << 40, "APIView", "Voir"), - (1 << 41, "APIEtudChangeGroups", "Modifier les groupes"), - (1 << 42, "APIEditAllNotes", "Modifier toutes les notes"), - (1 << 43, "APIAbsChange", "Saisir des absences"), + (1 << 40, "APIView", "API: Lecture"), + (1 << 41, "APIEtudChangeGroups", "API: Modifier les groupes"), + (1 << 42, "APIEditAllNotes", "API: Modifier toutes les notes"), + (1 << 43, "APIAbsChange", "API: Saisir des absences"), ) From 994959960cc3e3eeaf954416d64cbcc9c5031594 Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Mon, 23 May 2022 10:59:43 +0200 Subject: [PATCH 02/16] Recap: rangs dans les groupes --- app/comp/res_common.py | 22 ++++++++++++++++++++-- app/scodoc/sco_groups.py | 4 +--- app/static/js/table_recap.js | 18 ++++++++++++++---- sco_version.py | 2 +- 4 files changed, 36 insertions(+), 10 deletions(-) diff --git a/app/comp/res_common.py b/app/comp/res_common.py index 5170875d..654a1ae3 100644 --- a/app/comp/res_common.py +++ b/app/comp/res_common.py @@ -70,6 +70,7 @@ class ResultatsSemestre(ResultatsCache): self.etud_moy_gen: pd.Series = None self.etud_moy_gen_ranks = {} self.etud_moy_gen_ranks_int = {} + self.moy_gen_rangs_by_group = None # virtual self.modimpl_inscr_df: pd.DataFrame = None "Inscriptions: row etudid, col modimlpl_id" self.modimpls_results: ModuleImplResults = None @@ -824,17 +825,25 @@ class ResultatsSemestre(ResultatsCache): self.formsemestre.id ) first_partition = True + col_order = 10 for partition in partitions: cid = f"part_{partition['partition_id']}" + rg_cid = cid + "_rg" # rang dans la partition titles[cid] = partition["partition_name"] if first_partition: klass = "partition" else: klass = "partition partition_aux" titles[f"_{cid}_class"] = klass - titles[f"_{cid}_col_order"] = 10 + titles[f"_{cid}_col_order"] = col_order + titles[f"_{rg_cid}_col_order"] = col_order + 1 + col_order += 2 + if partition["bul_show_rank"]: + titles[rg_cid] = f"Rg {partition['partition_name']}" + titles[f"_{rg_cid}_class"] = "partition_rangs" partition_etud_groups = partitions_etud_groups[partition["partition_id"]] for row in rows: + group = None # group (dict) de l'étudiant dans cette partition # dans NotesTableCompat, à revoir etud_etat = self.get_etud_etat(row["etudid"]) if etud_etat == "D": @@ -847,8 +856,17 @@ class ResultatsSemestre(ResultatsCache): group = partition_etud_groups.get(row["etudid"]) gr_name = group["group_name"] if group else "" if gr_name: - row[f"{cid}"] = gr_name + row[cid] = gr_name row[f"_{cid}_class"] = klass + # Rangs dans groupe + if ( + partition["bul_show_rank"] + and (group is not None) + and (group["id"] in self.moy_gen_rangs_by_group) + ): + rang = self.moy_gen_rangs_by_group[group["id"]][0] + row[rg_cid] = rang.get(row["etudid"], "") + first_partition = False def _recap_add_evaluations( diff --git a/app/scodoc/sco_groups.py b/app/scodoc/sco_groups.py index d316640f..79ed91ca 100644 --- a/app/scodoc/sco_groups.py +++ b/app/scodoc/sco_groups.py @@ -1046,9 +1046,7 @@ def partition_set_attr(partition_id, attr, value): partition[attr] = value partitionEditor.edit(cnx, partition) # invalid bulletin cache - sco_cache.invalidate_formsemestre( - pdfonly=True, formsemestre_id=partition["formsemestre_id"] - ) + sco_cache.invalidate_formsemestre(formsemestre_id=partition["formsemestre_id"]) return "enregistré" diff --git a/app/static/js/table_recap.js b/app/static/js/table_recap.js index 36426b50..fd5068e6 100644 --- a/app/static/js/table_recap.js +++ b/app/static/js/table_recap.js @@ -15,13 +15,23 @@ $(function () { }, { name: "toggle_partitions", - text: "Toutes les partitions", + text: "Montrer groupes", action: function (e, dt, node, config) { let visible = dt.columns(".partition_aux").visible()[0]; dt.columns(".partition_aux").visible(!visible); - dt.buttons('toggle_partitions:name').text(visible ? "Toutes les partitions" : "Cacher les partitions"); + dt.buttons('toggle_partitions:name').text(visible ? "Montrer groupes" : "Cacher les groupes"); } - }]; + }, + { + name: "toggle_partitions_rangs", + text: "Rangs groupes", + action: function (e, dt, node, config) { + let rangs_visible = dt.columns(".partition_rangs").visible()[0]; + dt.columns(".partition_rangs").visible(!rangs_visible); + dt.buttons('toggle_partitions_rangs:name').text(rangs_visible ? "Rangs groupes" : "Cacher rangs groupes"); + } + }, + ]; if (!$('table.table_recap').hasClass("jury")) { buttons.push( $('table.table_recap').hasClass("apc") ? @@ -95,7 +105,7 @@ $(function () { "columnDefs": [ { // cache les codes, le détail de l'identité, les groupes, les colonnes admission et les vides - targets: ["codes", "identite_detail", "partition_aux", "admission", "col_empty"], + targets: ["codes", "identite_detail", "partition_aux", "partition_rangs", "admission", "col_empty"], visible: false, }, { diff --git a/sco_version.py b/sco_version.py index 3222274f..d3dd51f2 100644 --- a/sco_version.py +++ b/sco_version.py @@ -1,7 +1,7 @@ # -*- mode: python -*- # -*- coding: utf-8 -*- -SCOVERSION = "9.2.22" +SCOVERSION = "9.2.23" SCONAME = "ScoDoc" From 11b1b50a42ad1b506858cf2ffa141bd5ab5380bf Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Sun, 29 May 2022 17:38:52 +0200 Subject: [PATCH 03/16] Fix: recapcomplet / xlsall --- app/comp/res_common.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app/comp/res_common.py b/app/comp/res_common.py index 654a1ae3..645f067e 100644 --- a/app/comp/res_common.py +++ b/app/comp/res_common.py @@ -398,7 +398,7 @@ class ResultatsSemestre(ResultatsCache): - titles: { column_id : title } - columns_ids: (liste des id de colonnes) - . Si convert_values, transforme les notes en chaines ("12.34"). + Si convert_values, transforme les notes en chaines ("12.34"). Les colonnes générées sont: etudid rang : rang indicatif (basé sur moy gen) @@ -590,7 +590,9 @@ class ResultatsSemestre(ResultatsCache): f"moy_{modimpl.module.type_abbrv()}_{modimpl.id}_{ue.id}" ) val_fmt = val_fmt_html = fmt_note(val) - if modimpl.module.module_type == scu.ModuleType.MALUS: + if convert_values and ( + modimpl.module.module_type == scu.ModuleType.MALUS + ): val_fmt_html = ( (scu.EMO_RED_TRIANGLE_DOWN + val_fmt) if val else "" ) From 596a45a90e9b173f023d542e2e701d822117ab63 Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Mon, 30 May 2022 12:13:31 +0200 Subject: [PATCH 04/16] Fix: ajout de validations d'UE externes --- app/scodoc/sco_ue_external.py | 58 ++++++++++++++++++++------------ app/static/js/sco_ue_external.js | 17 ++++++---- 2 files changed, 47 insertions(+), 28 deletions(-) diff --git a/app/scodoc/sco_ue_external.py b/app/scodoc/sco_ue_external.py index b90afd0d..82598228 100644 --- a/app/scodoc/sco_ue_external.py +++ b/app/scodoc/sco_ue_external.py @@ -56,6 +56,7 @@ Solution proposée (nov 2014): import flask from flask import request from flask_login import current_user +from app.models.formsemestre import FormSemestre import app.scodoc.notesdb as ndb import app.scodoc.sco_utils as scu @@ -65,7 +66,6 @@ from app.scodoc import sco_codes_parcours from app.scodoc import sco_edit_matiere from app.scodoc import sco_edit_module from app.scodoc import sco_edit_ue -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 @@ -85,17 +85,21 @@ def external_ue_create( ects=0.0, ): """Crée UE/matiere/module/evaluation puis saisie les notes""" - log("external_ue_create( formsemestre_id=%s, titre=%s )" % (formsemestre_id, titre)) - sem = sco_formsemestre.get_formsemestre(formsemestre_id) + formsemestre = FormSemestre.query.get_or_404(formsemestre_id) + log(f"creating external UE in {formsemestre}: {acronyme}") + # Contrôle d'accès: if not current_user.has_permission(Permission.ScoImplement): - if not sem["resp_can_edit"] or (current_user.id not in sem["responsables"]): + if (not formsemestre.resp_can_edit) or ( + current_user.id not in [u.id for u in formsemestre.responsables] + ): raise AccessDenied("vous n'avez pas le droit d'effectuer cette opération") # - formation_id = sem["formation_id"] - log("creating external UE in %s: %s" % (formsemestre_id, acronyme)) + formation_id = formsemestre.formation.id - numero = sco_edit_ue.next_ue_numero(formation_id, semestre_id=sem["semestre_id"]) + numero = sco_edit_ue.next_ue_numero( + formation_id, semestre_id=formsemestre.semestre_id + ) ue_id = sco_edit_ue.do_ue_create( { "formation_id": formation_id, @@ -120,7 +124,8 @@ def external_ue_create( "ue_id": ue_id, "matiere_id": matiere_id, "formation_id": formation_id, - "semestre_id": sem["semestre_id"], + "semestre_id": formsemestre.semestre_id, + "module_type": scu.ModuleType.STANDARD, }, ) @@ -129,17 +134,23 @@ def external_ue_create( "module_id": module_id, "formsemestre_id": formsemestre_id, # affecte le 1er responsable du semestre comme resp. du module - "responsable_id": sem["responsables"][0], + "responsable_id": formsemestre.responsables[0].id + if len(formsemestre.responsables) + else None, }, ) return moduleimpl_id -def external_ue_inscrit_et_note(moduleimpl_id, formsemestre_id, notes_etuds): +def external_ue_inscrit_et_note( + moduleimpl_id: int, formsemestre_id: int, notes_etuds: dict +): + """Inscrit les étudiants au moduleimpl, crée au besoin une évaluation + et enregistre les notes. + """ log( - "external_ue_inscrit_et_note(moduleimpl_id=%s, notes_etuds=%s)" - % (moduleimpl_id, notes_etuds) + f"external_ue_inscrit_et_note(moduleimpl_id={moduleimpl_id}, notes_etuds={notes_etuds})" ) # Inscription des étudiants sco_moduleimpl.do_moduleimpl_inscrit_etuds( @@ -175,17 +186,17 @@ def external_ue_inscrit_et_note(moduleimpl_id, formsemestre_id, notes_etuds): ) -def get_existing_external_ue(formation_id): - "la liste de toutes les UE externes définies dans cette formation" +def get_existing_external_ue(formation_id: int) -> list[dict]: + "Liste de toutes les UE externes définies dans cette formation" return sco_edit_ue.ue_list(args={"formation_id": formation_id, "is_external": True}) -def get_external_moduleimpl_id(formsemestre_id, ue_id): +def get_external_moduleimpl_id(formsemestre_id: int, ue_id: int) -> int: "moduleimpl correspondant à l'UE externe indiquée de ce formsemestre" r = ndb.SimpleDictFetch( """ SELECT mi.id AS moduleimpl_id FROM notes_moduleimpl mi, notes_modules mo - WHERE mi.id = %(formsemestre_id)s + WHERE mi.formsemestre_id = %(formsemestre_id)s AND mi.module_id = mo.id AND mo.ue_id = %(ue_id)s """, @@ -194,11 +205,14 @@ def get_external_moduleimpl_id(formsemestre_id, ue_id): if r: return r[0]["moduleimpl_id"] else: - raise ScoValueError("aucun module externe ne correspond") + raise ScoValueError( + f"""Aucun module externe ne correspond + (formsemestre_id={formsemestre_id}, ue_id={ue_id})""" + ) # Web function -def external_ue_create_form(formsemestre_id, etudid): +def external_ue_create_form(formsemestre_id: int, etudid: int): """Formulaire création UE externe + inscription étudiant et saisie note - Demande UE: peut-être existante (liste les UE externes de cette formation), ou sinon spécifier titre, acronyme, type, ECTS @@ -233,7 +247,9 @@ def external_ue_create_form(formsemestre_id, etudid): html_footer = html_sco_header.sco_footer() Fo = sco_formations.formation_list(args={"formation_id": sem["formation_id"]})[0] parcours = sco_codes_parcours.get_parcours_from_code(Fo["type_parcours"]) - ue_types = parcours.ALLOWED_UE_TYPES + ue_types = [ + typ for typ in parcours.ALLOWED_UE_TYPES if typ != sco_codes_parcours.UE_SPORT + ] ue_types.sort() ue_types_names = [sco_codes_parcours.UE_TYPE_NAME[k] for k in ue_types] ue_types = [str(x) for x in ue_types] @@ -255,7 +271,7 @@ def external_ue_create_form(formsemestre_id, etudid): "input_type": "menu", "title": "UE externe existante:", "allowed_values": [""] - + [ue["ue_id"] for ue in existing_external_ue], + + [str(ue["ue_id"]) for ue in existing_external_ue], "labels": [default_label] + [ "%s (%s)" % (ue["titre"], ue["acronyme"]) @@ -337,7 +353,7 @@ def external_ue_create_form(formsemestre_id, etudid): + html_footer ) if tf[2]["existing_ue"]: - ue_id = tf[2]["existing_ue"] + ue_id = int(tf[2]["existing_ue"]) moduleimpl_id = get_external_moduleimpl_id(formsemestre_id, ue_id) else: acronyme = tf[2]["acronyme"].strip() diff --git a/app/static/js/sco_ue_external.js b/app/static/js/sco_ue_external.js index 24cd2157..fb57807f 100644 --- a/app/static/js/sco_ue_external.js +++ b/app/static/js/sco_ue_external.js @@ -9,19 +9,22 @@ function toggle_new_ue_form(state) { text_color = 'rgb(0,0,0)'; } - $("#tf_extue_titre td:eq(1) input").prop( "disabled", state ); - $("#tf_extue_titre td:eq(1) input").css('color', text_color) + $("#tf_extue_titre td:eq(1) input").prop("disabled", state); + $("#tf_extue_titre").css('color', text_color) - $("#tf_extue_acronyme td:eq(1) input").prop( "disabled", state ); - $("#tf_extue_acronyme td:eq(1) input").css('color', text_color) + $("#tf_extue_acronyme td:eq(1) input").prop("disabled", state); + $("#tf_extue_acronyme").css('color', text_color) - $("#tf_extue_ects td:eq(1) input").prop( "disabled", state ); - $("#tf_extue_ects td:eq(1) input").css('color', text_color) + $("#tf_extue_type td:eq(1) select").prop("disabled", state); + $("#tf_extue_type").css('color', text_color) + + $("#tf_extue_ects td:eq(1) input").prop("disabled", state); + $("#tf_extue_ects").css('color', text_color) } function update_external_ue_form() { - var state = (tf.existing_ue.value != "") + var state = (tf.existing_ue.value != ""); toggle_new_ue_form(state); } From ff0437d8448f1ab2f1aa902e564e96ce646716b2 Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Tue, 31 May 2022 14:15:21 +0200 Subject: [PATCH 05/16] lint --- app/scodoc/sco_edit_ue.py | 2 +- sco_version.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/scodoc/sco_edit_ue.py b/app/scodoc/sco_edit_ue.py index 4c0a8ef5..a0f4ff74 100644 --- a/app/scodoc/sco_edit_ue.py +++ b/app/scodoc/sco_edit_ue.py @@ -147,7 +147,7 @@ def can_delete_ue(ue: UniteEns) -> bool: et n'a pas de module rattachés """ # "pas un seul module de cette UE n'a de modimpl..."" - return (not len(ue.modules.all())) and not any(m.modimpls.all() for m in ue.modules) + return (ue.modules.count() == 0) and not any(m.modimpls.all() for m in ue.modules) def do_ue_delete(ue_id, delete_validations=False, force=False): diff --git a/sco_version.py b/sco_version.py index d3dd51f2..3de1255f 100644 --- a/sco_version.py +++ b/sco_version.py @@ -1,7 +1,7 @@ # -*- mode: python -*- # -*- coding: utf-8 -*- -SCOVERSION = "9.2.23" +SCOVERSION = "9.2.24" SCONAME = "ScoDoc" From f626fa3eff740fefd25132635cebaab43b3c53bd Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Wed, 1 Jun 2022 12:41:04 +0200 Subject: [PATCH 06/16] Modification bonus IUT de Rennes (n'affecte plus les moy. d'UE hors BUT). --- app/comp/bonus_spo.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/app/comp/bonus_spo.py b/app/comp/bonus_spo.py index 06a7bde7..48856771 100644 --- a/app/comp/bonus_spo.py +++ b/app/comp/bonus_spo.py @@ -711,7 +711,8 @@ class BonusIUTRennes1(BonusSportAdditif):
  • Les étudiants peuvent suivre un ou plusieurs activités optionnelles notées. La meilleure des notes obtenue est prise en compte, si elle est supérieure à 10/20.
  • -
  • Le vingtième des points au dessus de 10 est ajouté à la moyenne des UE. +
  • Le vingtième des points au dessus de 10 est ajouté à la moyenne de chaque UE + en BUT, ou à la moyenne générale pour les autres formations.
  • Exemple: un étudiant ayant 16/20 bénéficiera d'un bonus de (16-10)/20 = 0,3 points sur chaque UE. @@ -720,10 +721,10 @@ class BonusIUTRennes1(BonusSportAdditif): """ name = "bonus_iut_rennes1" - displayed_name = "IUTs de Rennes 1 (Lannion, St Malo)" + displayed_name = "IUTs de Rennes 1 (Lannion, Rennes, St Brieuc, St Malo)" seuil_moy_gen = 10.0 proportion_point = 1 / 20.0 - classic_use_bonus_ues = True + classic_use_bonus_ues = False # Adapté de BonusTarbes, mais s'applique aussi en classic def compute_bonus(self, sem_modimpl_moys_inscrits, modimpl_coefs_etuds_no_nan): """calcul du bonus""" From 10811173e66f3c13d2825b3778c0337b732db49d Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Thu, 2 Jun 2022 00:02:32 +0200 Subject: [PATCH 07/16] Suppression source bonus ScoDoc7 --- app/scodoc/bonus_sport.py | 492 -------------------------------------- app/scodoc/sco_config.py | 4 +- 2 files changed, 1 insertion(+), 495 deletions(-) delete mode 100644 app/scodoc/bonus_sport.py diff --git a/app/scodoc/bonus_sport.py b/app/scodoc/bonus_sport.py deleted file mode 100644 index b49b6159..00000000 --- a/app/scodoc/bonus_sport.py +++ /dev/null @@ -1,492 +0,0 @@ -# -*- mode: python -*- -# -*- coding: utf-8 -*- - -############################################################################## -# -# Gestion scolarite IUT -# -# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -# -# Emmanuel Viennet emmanuel.viennet@viennet.net -# -############################################################################## - -from operator import mul -import pprint - -""" ANCIENS BONUS SPORT pour ScoDoc < 9.2 NON UTILISES A PARTIR DE 9.2 (voir comp/bonus_spo.py) - -La fonction bonus_sport reçoit: - - - notes_sport: la liste des notes des modules de sport et culture (une note par module - de l'UE de type sport/culture, toujours dans remise sur 20); - - coefs: un coef (float) pondérant chaque note (la plupart des bonus les ignorent); - - infos: dictionnaire avec des données pouvant être utilisées pour les calculs. - Ces données dépendent du type de formation. - infos = { - "moy" : la moyenne générale (float). 0. en BUT. - "sem" : { - "date_debut_iso" : "2010-08-01", # date de début de semestre - } - "moy_ues": { - ue_id : { # ue_status - "is_capitalized" : True|False, - "moy" : float, # moyenne d'UE prise en compte (peut-être capitalisée) - "sum_coefs": float, # > 0 si UE avec la moyenne calculée - "cur_moy_ue": float, # moyenne de l'UE (sans capitalisation)) - } - } - } - -Les notes passées sont: - - pour les formations classiques, la moyenne dans le module, calculée comme d'habitude - (moyenne pondérée des notes d'évaluations); - - pour le BUT: pareil, *en ignorant* les éventuels poids des évaluations. Le coefficient - de l'évaluation est pris en compte, mais pas les poids vers les UE. - -Pour modifier les moyennes d'UE: - - modifier infos["moy_ues"][ue_id][["cur_moy_ue"] - et, seulement si l'UE n'est pas capitalisée, infos["moy_ues"][ue_id][["moy"]/ - -La valeur retournée est: - - formations classiques: ajoutée à la moyenne générale - - BUT: valeur multipliée par la somme des coefs modules sport ajoutée à chaque UE. - -""" - - -def bonus_iutv(notes_sport, coefs, infos=None): - """Calcul bonus modules optionels (sport, culture), règle IUT Villetaneuse - - Les étudiants de l'IUT peuvent suivre des enseignements optionnels - de l'Université Paris 13 (sports, musique, deuxième langue, - culture, etc) non rattachés à une unité d'enseignement. Les points - au-dessus de 10 sur 20 obtenus dans chacune des matières - optionnelles sont cumulés et 5% de ces points cumulés s'ajoutent à - la moyenne générale du semestre déjà obtenue par l'étudiant. - """ - bonus = sum([(x - 10) / 20.0 for x in notes_sport if x > 10]) - return bonus - - -def bonus_direct(notes_sport, coefs, infos=None): - """Un bonus direct et sans chichis: les points sont directement ajoutés à la moyenne générale. - Les coefficients sont ignorés: tous les points de bonus sont sommés. - (rappel: la note est ramenée sur 20 avant application). - """ - return sum(notes_sport) - - -def bonus_iut_stdenis(notes_sport, coefs, infos=None): - """Semblable à bonus_iutv mais total limité à 0.5 points.""" - points = sum([x - 10 for x in notes_sport if x > 10]) # points au dessus de 10 - bonus = points * 0.05 # ou / 20 - return min(bonus, 0.5) # bonus limité à 1/2 point - - -def bonus_colmar(notes_sport, coefs, infos=None): - """Calcul bonus modules optionels (sport, culture), règle IUT Colmar. - - Les étudiants de l'IUT peuvent suivre des enseignements optionnels - de l'U.H.A. (sports, musique, deuxième langue, culture, etc) non - rattachés à une unité d'enseignement. Les points au-dessus de 10 - sur 20 obtenus dans chacune des matières optionnelles sont cumulés - dans la limite de 10 points. 5% de ces points cumulés s'ajoutent à - la moyenne générale du semestre déjà obtenue par l'étudiant. - - """ - # les coefs sont ignorés - points = sum([x - 10 for x in notes_sport if x > 10]) - points = min(10, points) # limite total à 10 - bonus = points / 20.0 # 5% - return bonus - - -def bonus_iutva(notes_sport, coefs, infos=None): - """Calcul bonus modules optionels (sport, culture), règle IUT Ville d'Avray - - Les étudiants de l'IUT peuvent suivre des enseignements optionnels - de l'Université Paris 10 (C2I) non rattachés à une unité d'enseignement. - Si la note est >= 10 et < 12, bonus de 0.1 point - Si la note est >= 12 et < 16, bonus de 0.2 point - Si la note est >= 16, bonus de 0.3 point - Ce bonus s'ajoute à la moyenne générale du semestre déjà obtenue par - l'étudiant. - """ - sumc = sum(coefs) # assumes sum. coefs > 0 - note_sport = sum(map(mul, notes_sport, coefs)) / sumc # moyenne pondérée - if note_sport >= 16.0: - return 0.3 - if note_sport >= 12.0: - return 0.2 - if note_sport >= 10.0: - return 0.1 - return 0 - - -def bonus_iut1grenoble_2017(notes_sport, coefs, infos=None): - """Calcul bonus sport IUT Grenoble sur la moyenne générale (version 2017) - - La note de sport de nos étudiants va de 0 à 5 points. - Chaque point correspond à un % qui augmente la moyenne de chaque UE et la moyenne générale. - Par exemple : note de sport 2/5 : la moyenne générale sera augmentée de 2%. - - Calcul ici du bonus sur moyenne générale - """ - # les coefs sont ignorés - # notes de 0 à 5 - points = sum([x for x in notes_sport]) - factor = (points / 4.0) / 100.0 - bonus = infos["moy"] * factor - - return bonus - - -def bonus_lille(notes_sport, coefs, infos=None): - """calcul bonus modules optionels (sport, culture), règle IUT Villeneuve d'Ascq - - Les étudiants de l'IUT peuvent suivre des enseignements optionnels - de l'Université Lille 1 (sports,etc) non rattachés à une unité d'enseignement. Les points - au-dessus de 10 sur 20 obtenus dans chacune des matières - optionnelles sont cumulés et 4% (2% avant aout 2010) de ces points cumulés s'ajoutent à - la moyenne générale du semestre déjà obtenue par l'étudiant. - """ - if ( - infos["sem"]["date_debut_iso"] > "2010-08-01" - ): # changement de regle en aout 2010. - return sum([(x - 10) / 25.0 for x in notes_sport if x > 10]) - return sum([(x - 10) / 50.0 for x in notes_sport if x > 10]) - - -# Fonction Le Havre, par Dom. Soud. -def bonus_iutlh(notes_sport, coefs, infos=None): - """Calcul bonus sport IUT du Havre sur moyenne générale et UE - - La note de sport de nos étudiants va de 0 à 20 points. - m2=m1*(1+0.005*((10-N1)+(10-N2)) - m2 : Nouvelle moyenne de l'unité d'enseignement si note de sport et/ou de langue supérieure à 10 - m1 : moyenne de l'unité d'enseignement avant bonification - N1 : note de sport si supérieure à 10 - N2 : note de seconde langue si supérieure à 10 - Par exemple : sport 15/20 et langue 12/20 : chaque UE sera multipliée par 1+0.005*7, ainsi que la moyenne générale. - Calcul ici de la moyenne générale et moyennes d'UE non capitalisées. - """ - # les coefs sont ignorés - points = sum([x - 10 for x in notes_sport if x > 10]) - points = min(10, points) # limite total à 10 - factor = 1.0 + (0.005 * points) - # bonus nul puisque les moyennes sont directement modifiées par factor - bonus = 0 - # Modifie la moyenne générale - infos["moy"] = infos["moy"] * factor - # Modifie les moyennes de toutes les UE: - for ue_id in infos["moy_ues"]: - ue_status = infos["moy_ues"][ue_id] - if ue_status["sum_coefs"] > 0: - # modifie moyenne UE ds semestre courant - ue_status["cur_moy_ue"] = ue_status["cur_moy_ue"] * factor - if not ue_status["is_capitalized"]: - # si non capitalisee, modifie moyenne prise en compte - ue_status["moy"] = ue_status["cur_moy_ue"] - - # open('/tmp/log','a').write( pprint.pformat(ue_status) + '\n\n' ) - return bonus - - -def bonus_nantes(notes_sport, coefs, infos=None): - """IUT de Nantes (Septembre 2018) - Nous avons différents types de bonification - bonfication Sport / Culture / engagement citoyen - Nous ajoutons sur le bulletin une bonification de 0,2 pour chaque item - la bonification totale ne doit pas excéder les 0,5 point. - Sur le bulletin nous ne mettons pas une note sur 20 mais directement les bonifications. - - Dans ScoDoc: on a déclaré une UE "sport&culture" dans laquelle on aura des modules - pour chaque activité (Sport, Associations, ...) - avec à chaque fois une note (ScoDoc l'affichera comme une note sur 20, mais en fait ce sera la - valeur de la bonification: entrer 0,1/20 signifiera un bonus de 0,1 point la moyenne générale) - """ - bonus = min(0.5, sum([x for x in notes_sport])) # plafonnement à 0.5 points - return bonus - - -# Bonus sport IUT Tours -def bonus_tours(notes_sport, coefs, infos=None): - """Calcul bonus sport & culture IUT Tours sur moyenne generale - - La note de sport & culture de nos etudiants est applique sur la moyenne generale. - """ - return min(1.0, sum(notes_sport)) # bonus maximum de 1 point - - -def bonus_iutr(notes_sport, coefs, infos=None): - """Calcul du bonus , règle de l'IUT de Roanne - (contribuée par Raphael C., nov 2012) - - Le bonus est compris entre 0 et 0.35 point. - cette procédure modifie la moyenne de chaque UE capitalisable. - - """ - # modifie les moyennes de toutes les UE: - # le bonus est le minimum entre 0.35 et la somme de toutes les bonifs - bonus = min(0.35, sum([x for x in notes_sport])) - for ue_id in infos["moy_ues"]: - # open('/tmp/log','a').write( str(ue_id) + infos['moy_ues'] + '\n\n' ) - ue_status = infos["moy_ues"][ue_id] - if ue_status["sum_coefs"] > 0: - # modifie moyenne UE dans semestre courant - ue_status["cur_moy_ue"] = ue_status["cur_moy_ue"] + bonus - if not ue_status["is_capitalized"]: - ue_status["moy"] = ue_status["cur_moy_ue"] - return bonus - - -def bonus_iutam(notes_sport, coefs, infos=None): - """Calcul bonus modules optionels (sport), regle IUT d'Amiens. - Les etudiants de l'IUT peuvent suivre des enseignements optionnels. - Si la note est de 10.00 a 10.49 -> 0.50% de la moyenne - Si la note est de 10.50 a 10.99 -> 0.75% - Si la note est de 11.00 a 11.49 -> 1.00% - Si la note est de 11.50 a 11.99 -> 1.25% - Si la note est de 12.00 a 12.49 -> 1.50% - Si la note est de 12.50 a 12.99 -> 1.75% - Si la note est de 13.00 a 13.49 -> 2.00% - Si la note est de 13.50 a 13.99 -> 2.25% - Si la note est de 14.00 a 14.49 -> 2.50% - Si la note est de 14.50 a 14.99 -> 2.75% - Si la note est de 15.00 a 15.49 -> 3.00% - Si la note est de 15.50 a 15.99 -> 3.25% - Si la note est de 16.00 a 16.49 -> 3.50% - Si la note est de 16.50 a 16.99 -> 3.75% - Si la note est de 17.00 a 17.49 -> 4.00% - Si la note est de 17.50 a 17.99 -> 4.25% - Si la note est de 18.00 a 18.49 -> 4.50% - Si la note est de 18.50 a 18.99 -> 4.75% - Si la note est de 19.00 a 20.00 -> 5.00% - Ce bonus s'ajoute a la moyenne generale du semestre de l'etudiant. - """ - # une seule note - note_sport = notes_sport[0] - if note_sport < 10.0: - return 0.0 - prc = min((int(2 * note_sport - 20.0) + 2) * 0.25, 5) - bonus = infos["moy"] * prc / 100.0 - return bonus - - -def bonus_saint_etienne(notes_sport, coefs, infos=None): - """IUT de Saint-Etienne (jan 2014) - Nous avons différents types de bonification - bonfication Sport / Associations - coopératives de département / Bureau Des Étudiants - / engagement citoyen / Langues optionnelles - Nous ajoutons sur le bulletin une bonification qui varie entre 0,1 et 0,3 ou 0,35 pour chaque item - la bonification totale ne doit pas excéder les 0,6 point. - Sur le bulletin nous ne mettons pas une note sur 20 mais directement les bonifications. - - - Dans ScoDoc: on a déclarer une UE "sport&culture" dans laquelle on aura des modules - pour chaque activité (Sport, Associations, ...) - avec à chaque fois une note (ScoDoc l'affichera comme une note sur 20, mais en fait ce sera la - valeur de la bonification: entrer 0,1/20 signifiera un bonus de 0,1 point la moyenne générale) - """ - bonus = min(0.6, sum([x for x in notes_sport])) # plafonnement à 0.6 points - - return bonus - - -def bonus_iutTarbes(notes_sport, coefs, infos=None): - """Calcul bonus modules optionnels - (sport, Langues, action sociale, Théâtre), règle IUT Tarbes - Les coefficients ne sont pas pris en compte, - seule la meilleure note est prise en compte - le 1/30ème des points au-dessus de 10 sur 20 est retenu et s'ajoute à - la moyenne générale du semestre déjà obtenue par l'étudiant. - """ - bonus = max([(x - 10) / 30.0 for x in notes_sport if x > 10] or [0.0]) - return bonus - - -def bonus_iutSN(notes_sport, coefs, infos=None): - """Calcul bonus sport IUT Saint-Nazaire sur moyenne générale - - La note de sport de nos étudiants va de 0 à 5 points. - La note de culture idem, - Elles sont cumulables, - Chaque point correspond à un % qui augmente la moyenne générale. - Par exemple : note de sport 2/5 : la moyenne générale sera augmentée de 2%. - - Calcul ici du bonus sur moyenne générale et moyennes d'UE non capitalisées. - """ - # les coefs sont ignorés - # notes de 0 à 5 - points = sum([x for x in notes_sport]) - factor = points / 100.0 - bonus = infos["moy"] * factor - return bonus - - -def bonus_iutBordeaux1(notes_sport, coefs, infos=None): - """Calcul bonus modules optionels (sport, culture), règle IUT Bordeaux 1, sur moyenne générale et UE - - Les étudiants de l'IUT peuvent suivre des enseignements optionnels - de l'Université Bordeaux 1 (sport, théâtre) non rattachés à une unité d'enseignement. - En cas de double activité, c'est la meilleure des 2 notes qui compte. - Chaque point au-dessus de 10 sur 20 obtenus dans cet enseignement correspond à un % - qui augmente la moyenne de chaque UE et la moyenne générale. - Formule : le % = points>moyenne / 2 - Par exemple : sport 13/20 : chaque UE sera multipliée par 1+0,015, ainsi que la moyenne générale. - - Calcul ici du bonus sur moyenne générale et moyennes d'UE non capitalisées. - """ - # open('/tmp/log','a').write( '\n---------------\n' + pprint.pformat(infos) + '\n' ) - # les coefs sont ignorés - # on récupère la note maximum et les points au-dessus de la moyenne - sport = max(notes_sport) - points = max(0, sport - 10) - # on calcule le bonus - factor = (points / 2.0) / 100.0 - bonus = infos["moy"] * factor - # Modifie les moyennes de toutes les UE: - for ue_id in infos["moy_ues"]: - ue_status = infos["moy_ues"][ue_id] - if ue_status["sum_coefs"] > 0: - # modifie moyenne UE ds semestre courant - ue_status["cur_moy_ue"] = ue_status["cur_moy_ue"] * (1.0 + factor) - if not ue_status["is_capitalized"]: - # si non capitalisee, modifie moyenne prise en compte - ue_status["moy"] = ue_status["cur_moy_ue"] - - # open('/tmp/log','a').write( pprint.pformat(ue_status) + '\n\n' ) - return bonus - - -def bonus_iuto(notes_sport, coefs, infos=None): # OBSOLETE => EN ATTENTE (27/01/2022) - """Calcul bonus modules optionels (sport, culture), règle IUT Orleans - * Avant aout 2013 - Un bonus de 2,5% de la note de sport est accordé à chaque UE sauf - les UE de Projet et Stages - * Après aout 2013 - Un bonus de 2,5% de la note de sport est accordé à la moyenne générale - """ - sumc = sum(coefs) # assumes sum. coefs > 0 - note_sport = sum(map(mul, notes_sport, coefs)) / sumc # moyenne pondérée - bonus = note_sport * 2.5 / 100 - if ( - infos["sem"]["date_debut_iso"] > "2013-08-01" - ): # changement de regle en aout 2013. - return bonus - coefs = 0.0 - coefs_total = 0.0 - for ue_id in infos["moy_ues"]: - ue_status = infos["moy_ues"][ue_id] - coefs_total = coefs_total + ue_status["sum_coefs"] - # Extremement spécifique (et n'est plus utilisé) - if ue_status["ue"]["ue_code"] not in { - "ORA14", - "ORA24", - "ORA34", - "ORA44", - "ORB34", - "ORB44", - "ORD42", - "ORE14", - "ORE25", - "ORN44", - "ORO44", - "ORP44", - "ORV34", - "ORV42", - "ORV43", - }: - if ue_status["sum_coefs"] > 0: - coefs = coefs + ue_status["sum_coefs"] - # modifie moyenne UE ds semestre courant - ue_status["cur_moy_ue"] = ue_status["cur_moy_ue"] + bonus - if not ue_status["is_capitalized"]: - # si non capitalisee, modifie moyenne prise en compte - ue_status["moy"] = ue_status["cur_moy_ue"] - return bonus * coefs / coefs_total - - -def bonus_iutbethune(notes_sport, coefs, infos=None): - """Calcul bonus modules optionels (sport), règle IUT Bethune - - Les points au dessus de la moyenne de 10 apportent un bonus pour le semestre. - Ce bonus est égal au nombre de points divisé par 200 et multiplié par la - moyenne générale du semestre de l'étudiant. - """ - # les coefs sont ignorés - points = sum([x - 10 for x in notes_sport if x > 10]) - points = min(10, points) # limite total à 10 - bonus = int(infos["moy"] * points / 2) / 100.0 # moyenne-semestre x points x 0,5% - return bonus - - -def bonus_iutbeziers(notes_sport, coefs, infos=None): - """Calcul bonus modules optionels (sport, culture), regle IUT BEZIERS - - Les étudiants de l'IUT peuvent suivre des enseignements optionnels - sport , etc) non rattaches à une unité d'enseignement. Les points - au-dessus de 10 sur 20 obtenus dans chacune des matières - optionnelles sont cumulés et 3% de ces points cumulés s'ajoutent à - la moyenne générale du semestre déjà obtenue par l'étudiant. - """ - sumc = sum(coefs) # assumes sum. coefs > 0 - # note_sport = sum(map(mul, notes_sport, coefs)) / sumc # moyenne pondérée - bonus = sum([(x - 10) * 0.03 for x in notes_sport if x > 10]) - # le total du bonus ne doit pas dépasser 0.3 - Fred, 28/01/2020 - - if bonus > 0.3: - bonus = 0.3 - return bonus - - -def bonus_iutlemans(notes_sport, coefs, infos=None): - "fake: formule inutilisée en ScoDoc 9.2 mais doiut être présente" - return 0.0 - - -def bonus_iutlr(notes_sport, coefs, infos=None): - """Calcul bonus modules optionels (sport, culture), règle IUT La Rochelle - Si la note de sport est comprise entre 0 et 10 : pas d'ajout de point - Si la note de sport est comprise entre 10.1 et 20 : ajout de 1% de cette note sur la moyenne générale du semestre - """ - # les coefs sont ignorés - # une seule note - note_sport = notes_sport[0] - if note_sport <= 10: - return 0 - bonus = note_sport * 0.01 # 1% - return bonus - - -def bonus_demo(notes_sport, coefs, infos=None): - """Fausse fonction "bonus" pour afficher les informations disponibles - et aider les développeurs. - Les informations sont placées dans le fichier /tmp/scodoc_bonus.log - qui est ECRASE à chaque appel. - *** Ne pas utiliser en production !!! *** - """ - with open("/tmp/scodoc_bonus.log", "w") as f: # mettre 'a' pour ajouter en fin - f.write("\n---------------\n" + pprint.pformat(infos) + "\n") - # Statut de chaque UE - # for ue_id in infos['moy_ues']: - # ue_status = infos['moy_ues'][ue_id] - # #open('/tmp/log','a').write( pprint.pformat(ue_status) + '\n\n' ) - - return 0.0 diff --git a/app/scodoc/sco_config.py b/app/scodoc/sco_config.py index 95b9dcc9..b338f9a7 100644 --- a/app/scodoc/sco_config.py +++ b/app/scodoc/sco_config.py @@ -1,13 +1,11 @@ # -*- mode: python -*- # -*- coding: utf-8 -*- -"""Configuration de ScoDoc (version ScoDOc 9) +"""Configuration de ScoDoc (version ScoDoc 9) NE PAS MODIFIER localement ce fichier ! mais éditer /opt/scodoc-data/config/scodoc_local.py """ -from app.scodoc import bonus_sport - class AttrDict(dict): def __init__(self, *args, **kwargs): From 3240d625b0c8836ce35bf4a63758103e41de8097 Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Thu, 2 Jun 2022 11:36:47 +0200 Subject: [PATCH 08/16] Bonus St Nazaire --- app/comp/bonus_spo.py | 37 ++++++++++++++++++++++++++++++++++--- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/app/comp/bonus_spo.py b/app/comp/bonus_spo.py index 48856771..0cc5257f 100644 --- a/app/comp/bonus_spo.py +++ b/app/comp/bonus_spo.py @@ -266,6 +266,8 @@ class BonusSportMultiplicatif(BonusSport): amplitude = 0.005 # multiplie les points au dessus du seuil # En classique, les bonus multiplicatifs agissent par défaut sur les UE: classic_use_bonus_ues = True + # Facteur multiplicatif max: (bonus = moy_ue*factor) + factor_max = 1000.0 # infini # C'est un bonus "multiplicatif": on l'exprime en additif, # sur chaque moyenne d'UE m_0 @@ -285,6 +287,8 @@ class BonusSportMultiplicatif(BonusSport): notes = np.nan_to_num(notes, copy=False) factor = (notes - self.seuil_moy_gen) * self.amplitude # 5% si note=20 factor[factor <= 0] = 0.0 # note < seuil_moy_gen, pas de bonus + # note < seuil_moy_gen, pas de bonus: pas de facteur négatif, ni + factor.clip(0.0, self.factor_max, out=factor) # Ne s'applique qu'aux moyennes d'UE if len(factor.shape) == 1: # classic @@ -967,7 +971,7 @@ class BonusNantes(BonusSportAdditif): class BonusPoitiers(BonusSportAdditif): """Calcul bonus optionnels (sport, culture), règle IUT de Poitiers. - Les deux notes d'option supérieure à 10, bonifies les moyennes de chaque UE. + Les deux notes d'option supérieure à 10, bonifient les moyennes de chaque UE. bonus = (option1 - 10)*5% + (option2 - 10)*5% """ @@ -993,7 +997,7 @@ class BonusRoanne(BonusSportAdditif): class BonusStBrieuc(BonusSportAdditif): - """IUT de Saint Brieuc + """IUT de Saint-Brieuc Ne s'applique qu'aux semestres pairs (S2, S4, S6), et bonifie les moyennes d'UE:
      @@ -1045,7 +1049,7 @@ class BonusStDenis(BonusSportAdditif): class BonusStMalo(BonusStBrieuc): # identique à St Brieux, sauf la doc - """IUT de Saint Malo + """IUT de Saint-Malo Ne s'applique qu'aux semestres pairs (S2, S4, S6), et bonifie les moyennes d'UE:
        @@ -1056,6 +1060,33 @@ class BonusStMalo(BonusStBrieuc): displayed_name = "IUT de Saint-Malo" +class BonusStNazaire(BonusSportMultiplicatif): + """IUT de Saint-Nazaire + + Trois bonifications sont possibles : sport, culture et engagement citoyen + (qui seront déclarées comme des modules séparés de l'UE bonus). +
          +
        • Chaque bonus est compris entre 0 et 20 points -> 4pt = 1%
          + (note 4/20: 1%, 8/20: 2%, 12/20: 3%, 16/20: 4%, 20/20: 5%) +
        • +
        • Le total des 3 bonus ne peut excéder 10%
        • +
        • La somme des bonus s'applique à la moyenne de chaque UE
        • +
        +

        Exemple: une moyenne d'UE de 10/20 avec un total des bonus de 6% donne + une moyenne de 10,6.

        +

        Les bonifications s'appliquent aussi au classement général du semestre + et de l'année. +

        + """ + + name = "bonus_iutSN" + displayed_name = "IUT de Saint-Nazaire" + classic_use_bonus_ues = True # s'applique aux UEs en DUT et LP + seuil_moy_gen = 0.0 # tous les points comptent + amplitude = 0.01 / 4 # 4pt => 1% + factor_max = 0.1 # 10% max + + class BonusTarbes(BonusSportAdditif): """Calcul bonus optionnels (sport, culture), règle IUT de Tarbes. From be92c86baf6bb61cb0ea7e7465b0528bb094f330 Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Thu, 2 Jun 2022 11:37:13 +0200 Subject: [PATCH 09/16] cosmetic --- app/scodoc/sco_evaluations.py | 14 ++++++++------ app/scodoc/sco_moduleimpl_status.py | 6 +++++- app/scodoc/sco_saisie_notes.py | 4 +++- 3 files changed, 16 insertions(+), 8 deletions(-) diff --git a/app/scodoc/sco_evaluations.py b/app/scodoc/sco_evaluations.py index 6152e881..09bf2102 100644 --- a/app/scodoc/sco_evaluations.py +++ b/app/scodoc/sco_evaluations.py @@ -606,12 +606,10 @@ def formsemestre_evaluations_delai_correction(formsemestre_id, format="html"): # -------------- VIEWS -def evaluation_describe(evaluation_id="", edit_in_place=True): +def evaluation_describe(evaluation_id="", edit_in_place=True, link_saisie=True): """HTML description of evaluation, for page headers edit_in_place: allow in-place editing when permitted (not implemented) """ - from app.scodoc import sco_saisie_notes - E = sco_evaluation_db.do_evaluation_list({"evaluation_id": evaluation_id})[0] moduleimpl_id = E["moduleimpl_id"] M = sco_moduleimpl.moduleimpl_list(moduleimpl_id=moduleimpl_id)[0] @@ -646,7 +644,7 @@ def evaluation_describe(evaluation_id="", edit_in_place=True): if Mod["module_type"] == ModuleType.MALUS: etit += ' (points de malus)' H = [ - 'Evaluation%s

        Module : %s

        ' + 'Évaluation%s

        Module : %s

        ' % (etit, mod_descr) ] if Mod["module_type"] == ModuleType.MALUS: @@ -689,12 +687,16 @@ def evaluation_describe(evaluation_id="", edit_in_place=True): modifier l'évaluation - + """ + ) + if link_saisie: + H.append( + f""" saisie des notes """ - ) + ) H.append("

        ") return '
        ' + "\n".join(H) + "
        " diff --git a/app/scodoc/sco_moduleimpl_status.py b/app/scodoc/sco_moduleimpl_status.py index d6def369..b9e6a3ee 100644 --- a/app/scodoc/sco_moduleimpl_status.py +++ b/app/scodoc/sco_moduleimpl_status.py @@ -277,7 +277,11 @@ def moduleimpl_status(moduleimpl_id=None, partition_id=None): if modimpl.module.is_apc(): H.append(_ue_coefs_html(modimpl.module.ue_coefs_list())) else: - H.append(f"Coef. dans le semestre: {modimpl.module.coefficient}") + H.append( + f"""Coef. dans le semestre: { + "non défini" if modimpl.module.coefficient is None else modimpl.module.coefficient + }""" + ) H.append("""""") # 3ieme ligne: Formation H.append( diff --git a/app/scodoc/sco_saisie_notes.py b/app/scodoc/sco_saisie_notes.py index 733ef173..e7597735 100644 --- a/app/scodoc/sco_saisie_notes.py +++ b/app/scodoc/sco_saisie_notes.py @@ -943,7 +943,9 @@ def saisie_notes(evaluation_id, group_ids=[]): cssstyles=sco_groups_view.CSSSTYLES, init_qtip=True, ), - sco_evaluations.evaluation_describe(evaluation_id=evaluation_id), + sco_evaluations.evaluation_describe( + evaluation_id=evaluation_id, link_saisie=False + ), '
        Saisie des notes', ] H.append("""
        """) From 1dc6dd3d6dd45afd4ee6602c6a95878a7af43b7a Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Thu, 2 Jun 2022 11:53:53 +0200 Subject: [PATCH 10/16] Regroupe bonus IUT de Renens 1 --- app/comp/bonus_spo.py | 52 +++++++++++++------------------------------ 1 file changed, 16 insertions(+), 36 deletions(-) diff --git a/app/comp/bonus_spo.py b/app/comp/bonus_spo.py index 0cc5257f..7cbac1c7 100644 --- a/app/comp/bonus_spo.py +++ b/app/comp/bonus_spo.py @@ -709,10 +709,11 @@ class BonusGrenobleIUT1(BonusSportMultiplicatif): class BonusIUTRennes1(BonusSportAdditif): """Calcul bonus optionnels (sport, langue vivante, engagement étudiant), - règle IUT de l'Université de Rennes 1 (Lannion, St Malo). + règle IUT de l'Université de Rennes 1 (Lannion, Rennes, St Brieuc, St Malo).
          -
        • Les étudiants peuvent suivre un ou plusieurs activités optionnelles notées. +
        • Les étudiants peuvent suivre un ou plusieurs activités optionnelles notées + dans les semestres pairs.
          La meilleure des notes obtenue est prise en compte, si elle est supérieure à 10/20.
        • Le vingtième des points au dessus de 10 est ajouté à la moyenne de chaque UE @@ -753,6 +754,19 @@ class BonusIUTRennes1(BonusSportAdditif): self.bonus_additif(bonus_moy_arr) +# juste pour compatibilité (nom bonus en base): +class BonusStBrieuc(BonusIUTRennes1): + name = "bonus_iut_stbrieuc" + displayed_name = "IUTs de Rennes 1/St-Brieuc" + __doc__ = BonusIUTRennes1.__doc__ + + +class BonusStMalo(BonusIUTRennes1): + name = "bonus_iut_stmalo" + displayed_name = "IUTs de Rennes 1/St-Malo" + __doc__ = BonusIUTRennes1.__doc__ + + class BonusLaRochelle(BonusSportAdditif): """Calcul bonus modules optionnels (sport, culture), règle IUT de La Rochelle. @@ -996,27 +1010,6 @@ class BonusRoanne(BonusSportAdditif): proportion_point = 1 -class BonusStBrieuc(BonusSportAdditif): - """IUT de Saint-Brieuc - - Ne s'applique qu'aux semestres pairs (S2, S4, S6), et bonifie les moyennes d'UE: -
            -
          • Bonus = (S - 10)/20
          • -
          - """ - - # Utilisé aussi par St Malo, voir plus bas - name = "bonus_iut_stbrieuc" - displayed_name = "IUT de Saint-Brieuc" - proportion_point = 1 / 20.0 - classic_use_bonus_ues = False - - def compute_bonus(self, sem_modimpl_moys_inscrits, modimpl_coefs_etuds_no_nan): - """calcul du bonus""" - if self.formsemestre.semestre_id % 2 == 0: - super().compute_bonus(sem_modimpl_moys_inscrits, modimpl_coefs_etuds_no_nan) - - class BonusStEtienne(BonusSportAdditif): """IUT de Saint-Etienne. @@ -1047,19 +1040,6 @@ class BonusStDenis(BonusSportAdditif): bonus_max = 0.5 -class BonusStMalo(BonusStBrieuc): - # identique à St Brieux, sauf la doc - """IUT de Saint-Malo - - Ne s'applique qu'aux semestres pairs (S2, S4, S6), et bonifie les moyennes d'UE: -
            -
          • Bonus = (S - 10)/20
          • -
          - """ - name = "bonus_iut_stmalo" - displayed_name = "IUT de Saint-Malo" - - class BonusStNazaire(BonusSportMultiplicatif): """IUT de Saint-Nazaire From c890e21ed0b75d3cb8e85d739e1b6b317f8c466b Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Wed, 8 Jun 2022 14:49:31 +0200 Subject: [PATCH 11/16] =?UTF-8?q?Modif=20bonus=20Tarbes,=20qui=20a=20la=20?= =?UTF-8?q?m=C3=AAme=20logique=20que=20celui=20de=20Rennes=201?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/comp/bonus_spo.py | 30 ++++-------------------------- 1 file changed, 4 insertions(+), 26 deletions(-) diff --git a/app/comp/bonus_spo.py b/app/comp/bonus_spo.py index 7cbac1c7..cd2db631 100644 --- a/app/comp/bonus_spo.py +++ b/app/comp/bonus_spo.py @@ -730,7 +730,7 @@ class BonusIUTRennes1(BonusSportAdditif): seuil_moy_gen = 10.0 proportion_point = 1 / 20.0 classic_use_bonus_ues = False - # Adapté de BonusTarbes, mais s'applique aussi en classic + # S'applique aussi en classic, sur la moy. gen. def compute_bonus(self, sem_modimpl_moys_inscrits, modimpl_coefs_etuds_no_nan): """calcul du bonus""" # Prend la note de chaque modimpl, sans considération d'UE @@ -1067,14 +1067,15 @@ class BonusStNazaire(BonusSportMultiplicatif): factor_max = 0.1 # 10% max -class BonusTarbes(BonusSportAdditif): +class BonusTarbes(BonusIUTRennes1): """Calcul bonus optionnels (sport, culture), règle IUT de Tarbes.
          • Les étudiants opeuvent suivre un ou plusieurs activités optionnelles notées. La meilleure des notes obtenue est prise en compte, si elle est supérieure à 10/20.
          • -
          • Le trentième des points au dessus de 10 est ajouté à la moyenne des UE. +
          • Le trentième des points au dessus de 10 est ajouté à la moyenne des UE en BUT, + ou à la moyenne générale en DUT et LP.
          • Exemple: un étudiant ayant 16/20 bénéficiera d'un bonus de (16-10)/30 = 0,2 points sur chaque UE. @@ -1088,29 +1089,6 @@ class BonusTarbes(BonusSportAdditif): proportion_point = 1 / 30.0 classic_use_bonus_ues = True - def compute_bonus(self, sem_modimpl_moys_inscrits, modimpl_coefs_etuds_no_nan): - """calcul du bonus""" - # Prend la note de chaque modimpl, sans considération d'UE - if len(sem_modimpl_moys_inscrits.shape) > 2: # apc - sem_modimpl_moys_inscrits = sem_modimpl_moys_inscrits[:, :, 0] - # ici sem_modimpl_moys_inscrits est nb_etuds x nb_mods_bonus, en APC et en classic - note_bonus_max = np.max(sem_modimpl_moys_inscrits, axis=1) # 1d, nb_etuds - ues = self.formsemestre.query_ues(with_sport=False).all() - ues_idx = [ue.id for ue in ues] - - if self.formsemestre.formation.is_apc(): # --- BUT - bonus_moy_arr = np.where( - note_bonus_max > self.seuil_moy_gen, - (note_bonus_max - self.seuil_moy_gen) * self.proportion_point, - 0.0, - ) - self.bonus_ues = pd.DataFrame( - np.stack([bonus_moy_arr] * len(ues)).T, - index=self.etuds_idx, - columns=ues_idx, - dtype=float, - ) - class BonusTours(BonusDirect): """Calcul bonus sport & culture IUT Tours. From bde51dc6391dccca6f8342a2e36b32a07aa249ff Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Wed, 8 Jun 2022 17:42:52 +0200 Subject: [PATCH 12/16] Fix: check suppression formation/ue --- app/models/ues.py | 9 +++++++++ app/scodoc/sco_edit_formation.py | 5 +++-- app/scodoc/sco_edit_ue.py | 16 ++++------------ 3 files changed, 16 insertions(+), 14 deletions(-) diff --git a/app/models/ues.py b/app/models/ues.py index 518bd721..48d81a14 100644 --- a/app/models/ues.py +++ b/app/models/ues.py @@ -75,6 +75,15 @@ class UniteEns(db.Model): return sco_edit_ue.ue_is_locked(self.id) + def can_be_deleted(self) -> bool: + """True si l'UE n'est pas utilisée dans des formsemestre + et n'a pas de module rattachés + """ + # "pas un seul module de cette UE n'a de modimpl..."" + return (self.modules.count() == 0) or not any( + m.modimpls.all() for m in self.modules + ) + def guess_semestre_idx(self) -> None: """Lorsqu'on prend une ancienne formation non APC, les UE n'ont pas d'indication de semestre. diff --git a/app/scodoc/sco_edit_formation.py b/app/scodoc/sco_edit_formation.py index aabfaddc..606fc742 100644 --- a/app/scodoc/sco_edit_formation.py +++ b/app/scodoc/sco_edit_formation.py @@ -66,8 +66,9 @@ def formation_delete(formation_id=None, dialog_confirmed=False): sems = sco_formsemestre.do_formsemestre_list({"formation_id": formation_id}) if sems: H.append( - """

            Impossible de supprimer cette formation, car les sessions suivantes l'utilisent:

            -
              """ + """

              Impossible de supprimer cette formation, + car les sessions suivantes l'utilisent:

              +
                """ ) for sem in sems: H.append( diff --git a/app/scodoc/sco_edit_ue.py b/app/scodoc/sco_edit_ue.py index a0f4ff74..28cc1e08 100644 --- a/app/scodoc/sco_edit_ue.py +++ b/app/scodoc/sco_edit_ue.py @@ -142,14 +142,6 @@ def do_ue_create(args): return ue_id -def can_delete_ue(ue: UniteEns) -> bool: - """True si l'UE n'est pas utilisée dans des formsemestre - et n'a pas de module rattachés - """ - # "pas un seul module de cette UE n'a de modimpl..."" - return (ue.modules.count() == 0) and not any(m.modimpls.all() for m in ue.modules) - - def do_ue_delete(ue_id, delete_validations=False, force=False): "delete UE and attached matieres (but not modules)" from app.scodoc import sco_formations @@ -158,9 +150,9 @@ def do_ue_delete(ue_id, delete_validations=False, force=False): ue = UniteEns.query.get_or_404(ue_id) formation_id = ue.formation_id semestre_idx = ue.semestre_idx - if not can_delete_ue(ue): + if not ue.can_be_deleted(): raise ScoNonEmptyFormationObject( - "UE", + f"UE (id={ue.id}, dud)", msg=ue.titre, dest_url=url_for( "notes.ue_table", @@ -540,9 +532,9 @@ def ue_delete(ue_id=None, delete_validations=False, dialog_confirmed=False): semestre_idx=ue.semestre_idx, ), ) - if not can_delete_ue(ue): + if not ue.can_be_deleted(): raise ScoNonEmptyFormationObject( - "UE", + f"UE", msg=ue.titre, dest_url=url_for( "notes.ue_table", From 1676fba5ab53d8ac6b4f8dd26d36f1e3031539e0 Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Wed, 8 Jun 2022 17:56:27 +0200 Subject: [PATCH 13/16] moduleimpl_is_conforme: bug cache (?) => refresh et exception --- app/comp/moy_mod.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/app/comp/moy_mod.py b/app/comp/moy_mod.py index f5efbeb2..8f8bd1a8 100644 --- a/app/comp/moy_mod.py +++ b/app/comp/moy_mod.py @@ -41,7 +41,8 @@ from app import db from app.models import ModuleImpl, Evaluation, EvaluationUEPoids from app.scodoc import sco_utils as scu from app.scodoc.sco_codes_parcours import UE_SPORT - +from app.scodoc import sco_cache +from app.scodoc.sco_exceptions import ScoBugCatcher from app.scodoc.sco_utils import ModuleType @@ -423,7 +424,9 @@ def moduleimpl_is_conforme( if nb_ues == 0: return False # situation absurde (pas d'UE) if len(modules_coefficients) != nb_ues: - raise ValueError("moduleimpl_is_conforme: nb ue incoherent") + # il arrive (#bug) que le cache ne soit pas à jour... + sco_cache.invalidate_formsemestre() + raise ScoBugCatcher("moduleimpl_is_conforme: nb ue incoherent") module_evals_poids = evals_poids.transpose().sum(axis=1).to_numpy() != 0 check = all( (modules_coefficients[moduleimpl.module_id].to_numpy() != 0) From 3f15ff099dcd2560532746523bfa09c907aae263 Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Wed, 8 Jun 2022 21:16:09 +0200 Subject: [PATCH 14/16] =?UTF-8?q?R=C3=A9-=C3=A9criture=20fonction=20ajout?= =?UTF-8?q?=20modules=20de=20malus=20=C3=A0=20toutes=20les=20UE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/scodoc/sco_edit_module.py | 104 ++++++++++++++++++---------------- 1 file changed, 54 insertions(+), 50 deletions(-) diff --git a/app/scodoc/sco_edit_module.py b/app/scodoc/sco_edit_module.py index 68166788..f99bb8a1 100644 --- a/app/scodoc/sco_edit_module.py +++ b/app/scodoc/sco_edit_module.py @@ -33,7 +33,7 @@ from flask import url_for, render_template from flask import g, request from flask_login import current_user -from app import log +from app import db, log from app import models from app.models import APO_CODE_STR_LEN from app.models import Formation, Matiere, Module, UniteEns @@ -421,7 +421,7 @@ def module_delete(module_id=None): H = [ html_sco_header.sco_header(page_title="Suppression d'un module"), - f"""

                Suppression du module {module.titre} ({module.code})

                """, + f"""

                Suppression du module {module.titre or "sans titre"} ({module.code})

                """, ] dest_url = url_for( @@ -848,21 +848,13 @@ def module_count_moduleimpls(module_id): def formation_add_malus_modules(formation_id, titre=None, redirect=True): """Création d'un module de "malus" dans chaque UE d'une formation""" - from app.scodoc import sco_edit_ue - ues = sco_edit_ue.ue_list(args={"formation_id": formation_id}) + formation = Formation.query.get_or_404(formation_id) - for ue in ues: - # Un seul module de malus par UE: - nb_mod_malus = len( - [ - mod - for mod in module_list(args={"ue_id": ue["ue_id"]}) - if mod["module_type"] == scu.ModuleType.MALUS - ] - ) - if nb_mod_malus == 0: - ue_add_malus_module(ue["ue_id"], titre=titre) + for ue in formation.ues: + ue_add_malus_module(ue, titre=titre) + + formation.invalidate_cached_sems() if redirect: return flask.redirect( @@ -872,46 +864,58 @@ def formation_add_malus_modules(formation_id, titre=None, redirect=True): ) -def ue_add_malus_module(ue_id, titre=None, code=None): - """Add a malus module in this ue""" - from app.scodoc import sco_edit_ue +def ue_add_malus_module(ue: UniteEns, titre=None, code=None) -> int: + """Add a malus module in this ue. + If already exists, do nothing. + Returns id of malus module. + """ + modules_malus = [m for m in ue.modules if m.module_type == scu.ModuleType.MALUS] + if len(modules_malus) > 0: + return modules_malus[0].id # déjà existant - ue = sco_edit_ue.ue_list(args={"ue_id": ue_id})[0] - - if titre is None: - titre = "" - if code is None: - code = "MALUS%d" % ue["numero"] + titre = titre or "" + code = code or f"MALUS{ue.numero}" # Tout module doit avoir un semestre_id (indice 1, 2, ...) - semestre_ids = sco_edit_ue.ue_list_semestre_ids(ue) - if semestre_ids: - semestre_id = semestre_ids[0] + if ue.semestre_idx is None: + semestre_ids = sorted(list(set([m.semestre_id for m in ue.modules]))) + if len(semestre_ids) > 0: + semestre_id = semestre_ids[0] + else: + # c'est ennuyeux: dans ce cas, on pourrait demander à indiquer explicitement + # le semestre ? ou affecter le malus au semestre 1 ??? + raise ScoValueError( + "Impossible d'ajouter un malus s'il n'y a pas d'autres modules" + ) else: - # c'est ennuyeux: dans ce cas, on pourrait demander à indiquer explicitement - # le semestre ? ou affecter le malus au semestre 1 ??? - raise ScoValueError( - "Impossible d'ajouter un malus s'il n'y a pas d'autres modules" - ) + semestre_id = ue.semestre_idx # Matiere pour placer le module malus - Matlist = sco_edit_matiere.matiere_list(args={"ue_id": ue_id}) - numero = max([mat["numero"] for mat in Matlist]) + 10 - matiere_id = sco_edit_matiere.do_matiere_create( - {"ue_id": ue_id, "titre": "Malus", "numero": numero} - ) + titre_matiere_malus = "Malus" - module_id = do_module_create( - { - "titre": titre, - "code": code, - "coefficient": 0.0, # unused - "ue_id": ue_id, - "matiere_id": matiere_id, - "formation_id": ue["formation_id"], - "semestre_id": semestre_id, - "module_type": scu.ModuleType.MALUS, - }, - ) + matieres_malus = [mat for mat in ue.matieres if mat.titre == titre_matiere_malus] + if len(matieres_malus) > 0: + # matière Malus déjà existante, l'utilise + matiere = matieres_malus[0] + else: + if ue.matieres.count() > 0: + numero = max([mat.numero for mat in ue.matieres]) + 10 + else: + numero = 0 + matiere = Matiere(ue_id=ue.id, titre=titre_matiere_malus, numero=numero) + db.session.add(matiere) - return module_id + module = Module( + titre=titre, + code=code, + coefficient=0.0, + ue=ue, + matiere=matiere, + formation=ue.formation, + semestre_id=semestre_id, + module_type=scu.ModuleType.MALUS, + ) + db.session.add(module) + db.session.commit() + + return module.id From b140e891f13e77a3e2521786a3ee6656fbbacc98 Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Wed, 8 Jun 2022 22:18:39 +0200 Subject: [PATCH 15/16] Bonus La Rochelle (nouvelle version) --- app/comp/bonus_spo.py | 47 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 44 insertions(+), 3 deletions(-) diff --git a/app/comp/bonus_spo.py b/app/comp/bonus_spo.py index cd2db631..6cf0767f 100644 --- a/app/comp/bonus_spo.py +++ b/app/comp/bonus_spo.py @@ -771,18 +771,59 @@ class BonusLaRochelle(BonusSportAdditif): """Calcul bonus modules optionnels (sport, culture), règle IUT de La Rochelle.
                  -
                • Si la note de sport est comprise entre 0 et 10 : pas d'ajout de point.
                • -
                • Si la note de sport est comprise entre 10 et 20 : ajout de 1% de cette - note sur la moyenne générale du semestre (ou sur les UE en BUT).
                • +
                • Si la note de sport est comprise entre 0 et 10 : pas d’ajout de point.
                • +
                • Si la note de sport est comprise entre 10 et 20 : +
                    +
                  • Pour le BUT, application pour chaque UE du semestre : +
                      +
                    • pour une note entre 18 et 20 => + 0,10 points
                    • +
                    • pour une note entre 16 et 17,99 => + 0,08 points
                    • +
                    • pour une note entre 14 et 15,99 => + 0,06 points
                    • +
                    • pour une note entre 12 et 13,99 => + 0,04 points
                    • +
                    • pour une note entre 10 et 11,99 => + 0,02 points
                    • +
                    +
                  • +
                  • Pour les DUT/LP : + ajout de 1% de la note sur la moyenne générale du semestre +
                  • +
                  +
                """ name = "bonus_iutlr" displayed_name = "IUT de La Rochelle" + seuil_moy_gen = 10.0 # si bonus > 10, seuil_comptage = 0.0 # tous les points sont comptés proportion_point = 0.01 # 1% + def compute_bonus(self, sem_modimpl_moys_inscrits, modimpl_coefs_etuds_no_nan): + """calcul du bonus""" + # La date du semestre ? + if self.formsemestre.formation.is_apc(): + if 0 in sem_modimpl_moys_inscrits.shape: + # pas d'étudiants ou pas d'UE ou pas de module... + return + # Calcule moyenne pondérée des notes de sport: + 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.0] = 0.10 + bonus_moy_arr[bonus_moy_arr >= 16.0] = 0.08 + bonus_moy_arr[bonus_moy_arr >= 14.0] = 0.06 + bonus_moy_arr[bonus_moy_arr >= 12.0] = 0.04 + bonus_moy_arr[bonus_moy_arr >= 10.0] = 0.02 + self.bonus_additif(bonus_moy_arr) + else: + # DUT et LP: + return super().compute_bonus( + sem_modimpl_moys_inscrits, modimpl_coefs_etuds_no_nan + ) + class BonusLeHavre(BonusSportAdditif): """Bonus sport IUT du Havre sur les moyennes d'UE From 21a2c1b7e764a03aa47d8e7f95cc286d2a771ef5 Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Wed, 8 Jun 2022 22:41:53 +0200 Subject: [PATCH 16/16] removed useless function --- app/scodoc/sco_edit_ue.py | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/app/scodoc/sco_edit_ue.py b/app/scodoc/sco_edit_ue.py index 28cc1e08..33ffc69c 100644 --- a/app/scodoc/sco_edit_ue.py +++ b/app/scodoc/sco_edit_ue.py @@ -1346,16 +1346,6 @@ def ue_is_locked(ue_id): return len(r) > 0 -def ue_list_semestre_ids(ue: dict): - """Liste triée des numeros de semestres des modules dans cette UE - Il est recommandable que tous les modules d'une UE aient le même indice de semestre. - Mais cela n'a pas toujours été le cas dans les programmes pédagogiques officiels, - aussi ScoDoc laisse le choix. - """ - modules = sco_edit_module.module_list(args={"ue_id": ue["ue_id"]}) - return sorted(list(set([mod["semestre_id"] for mod in modules]))) - - UE_PALETTE = [ "#B80004", # rouge "#F97B3D", # Orange Crayola