From a42ae3e8edc221f9b76cd8bcc64dc795901db73f Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Sun, 14 May 2023 17:35:06 +0200 Subject: [PATCH 01/12] =?UTF-8?q?petites=20am=C3=A9lioration=20formations?= =?UTF-8?q?=20BUT?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/but/apc_edit_ue.py | 13 +++++++++++-- app/views/notes.py | 8 +++++++- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/app/but/apc_edit_ue.py b/app/but/apc_edit_ue.py index da0b3113..b2fcec0d 100644 --- a/app/but/apc_edit_ue.py +++ b/app/but/apc_edit_ue.py @@ -38,13 +38,22 @@ def form_ue_choix_parcours(ue: UniteEns) -> str: # Choix des parcours ue_pids = [p.id for p in ue.parcours] H.append("""
""") + + ects_differents = { + ue.get_ects(parcour, only_parcours=True) for parcour in ref_comp.parcours + } != {None} for parcour in ref_comp.parcours: + ects_parcour = ue.get_ects(parcour) + ects_parcour_txt = ( + f" ({ue.get_ects(parcour):.3g} ects)" if ects_differents else "" + ) H.append( f"""""" + data-setter="{url_for("apiweb.set_ue_parcours", + scodoc_dept=g.scodoc_dept, ue_id=ue.id)}" + >{parcour.code}{ects_parcour_txt}""" ) H.append("""
""") # diff --git a/app/views/notes.py b/app/views/notes.py index d8515925..ba8a9e52 100644 --- a/app/views/notes.py +++ b/app/views/notes.py @@ -693,7 +693,13 @@ def formation_import_xml_form(): { html_sco_header.sco_header(page_title="Import d'une formation") }

Import d'une formation

Création d'une formation (avec UE, matières, modules) - à partir un fichier XML (réservé aux utilisateurs avertis) + à partir un fichier XML (réservé aux utilisateurs avertis). +

+

S'il s'agit d'une formation par compétence (BUT), assurez-vous d'avoir + chargé le référentiel de compétences AVANT d'importer le fichier formation + (voir page des référentiels).

{ tf[1] } { html_sco_header.sco_footer() } From 49e8cf8e5f0da6694e7a71242543869ffd1dab3b Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Sun, 14 May 2023 17:36:06 +0200 Subject: [PATCH 02/12] =?UTF-8?q?test=20unitaire=20BUT=20Info:=20compl?= =?UTF-8?q?=C3=A8te=20formation=20et=20config=20yaml?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/ressources/yaml/cursus_but_info.yaml | 116 ++++++++++++++++++--- tests/unit/test_but_cursus.py | 2 +- tests/unit/yaml_setup.py | 7 +- tests/unit/yaml_setup_but.py | 33 +++--- 4 files changed, 127 insertions(+), 31 deletions(-) diff --git a/tests/ressources/yaml/cursus_but_info.yaml b/tests/ressources/yaml/cursus_but_info.yaml index 473014e1..35fe3d9e 100644 --- a/tests/ressources/yaml/cursus_but_info.yaml +++ b/tests/ressources/yaml/cursus_but_info.yaml @@ -3,41 +3,131 @@ # mais à ces niveaux sont associés des UEs dont les coefficients des ressources # varient selon le parcours. # +# Mise en place cursus avec parcours A et B ReferentielCompetences: filename: but-INFO-05012022-081701.xml specialite: INFO Formation: - filename: scodoc_formation_BUT_INFO_v1.xml + filename: scodoc_formation_BUT_INFO_v0514.xml + # nota: les associations UE/Niveaux sont déjà données dans ce fichier XML. ues: + # S1 + 'UE11': + annee: BUT1 + 'UE12': + annee: BUT1 + 'UE13': + annee: BUT1 + 'UE14': + annee: BUT1 + 'UE15': + annee: BUT1 + 'UE16': + annee: BUT1 + # S2 + 'UE21': + annee: BUT1 + 'UE22': + annee: BUT1 + 'UE23': + annee: BUT1 + 'UE24': + annee: BUT1 + 'UE25': + annee: BUT1 + 'UE26': + annee: BUT1 + # S3 + 'UE31': + annee: BUT2 + 'UE32': + annee: BUT2 + 'UE33': + annee: BUT2 + 'UE34': + annee: BUT2 + 'UE35': + annee: BUT2 + 'UE36': + annee: BUT2 # S4 'UE41-A': # UE pour le parcours A annee: BUT2 - competence: Réaliser - 'UE41-C': # UE pour le parcours C (même contenu, coefs différents) + 'UE41-B': # UE pour le parcours B (même contenu, coefs différents) annee: BUT2 - competence: Réaliser 'UE42': annee: BUT2 - competence: Optimiser 'UE43': annee: BUT2 - competence: Administrer 'UE44': annee: BUT2 - competence: Gérer 'UE45': annee: BUT2 - competence: Conduire 'UE46': annee: BUT2 - competence: Collaborer FormSemestres: - # S4 avec parcours A et C - S4: + # Semestres avec parcours A et B + S1: idx: 1 - date_debut: 2023-01-01 + date_debut: 2021-09-01 + date_fin: 2022-01-15 + codes_parcours: ['A', 'B'] + S2: + idx: 2 + date_debut: 2022-01-16 + date_fin: 2022-06-30 + codes_parcours: ['A', 'B'] + S3: + idx: 3 + date_debut: 2022-09-01 + date_fin: 2023-01-15 + codes_parcours: ['A', 'B'] + S4: + idx: 4 + date_debut: 2023-01-16 date_fin: 2023-06-30 - codes_parcours: ['A', 'C'] \ No newline at end of file + codes_parcours: ['A', 'B'] + S5: + idx: 5 + date_debut: 2023-09-01 + date_fin: 2024-01-15 + codes_parcours: ['A', 'B'] + S6: + idx: 6 + date_debut: 2024-01-16 + date_fin: 2024-06-30 + codes_parcours: ['A', 'B'] + +Etudiants: + brillant_a: # cursus S1 -> S6, valide tout + prenom: étudiant + civilite: X + formsemestres: + # on ne note que le portfolio, qui affecte toutes les UEs + S1: + parcours: A + notes_modules: + "P1": 11 + S2: + parcours: A + notes_modules: + "P2": 12 + S3: + parcours: A + notes_modules: + "P3": 13 + S4: + parcours: A + notes_modules: + "P4-A": 14 + S5: + parcours: A + notes_modules: + "P5-A": 15 + S6: + parcours: A + notes_modules: + "P6-A": 16 diff --git a/tests/unit/test_but_cursus.py b/tests/unit/test_but_cursus.py index f1e09df9..2d6b029a 100644 --- a/tests/unit/test_but_cursus.py +++ b/tests/unit/test_but_cursus.py @@ -5,7 +5,7 @@ Ce test suppose une base département existante. -Usage: pytest tests/unit/test_cursus_but.py +Usage: pytest tests/unit/test_but_cursus.py """ diff --git a/tests/unit/yaml_setup.py b/tests/unit/yaml_setup.py index 19696acd..4967af23 100644 --- a/tests/unit/yaml_setup.py +++ b/tests/unit/yaml_setup.py @@ -298,10 +298,11 @@ def setup_from_yaml(filename: str) -> dict: with open(filename, encoding="utf-8") as f: doc = yaml.safe_load(f.read()) + # Charge de ref. comp. avant la formation, de façon à pouvoir + # re-créer les associations UE/Niveaux + yaml_setup_but.setup_formation_referentiel(doc.get("ReferentielCompetences", {})) formation = setup_formation(doc["Formation"]) - yaml_setup_but.setup_formation_referentiel( - formation, doc.get("ReferentielCompetences", {}) - ) + yaml_setup_but.associe_ues_et_parcours(formation, doc["Formation"]) setup_formsemestres(formation, doc) etudiants = doc.get("Etudiants") diff --git a/tests/unit/yaml_setup_but.py b/tests/unit/yaml_setup_but.py index b1f9c61a..6532cce1 100644 --- a/tests/unit/yaml_setup_but.py +++ b/tests/unit/yaml_setup_but.py @@ -32,13 +32,15 @@ from app.scodoc import sco_utils as scu from app.scodoc import sco_pv_dict -def setup_formation_referentiel(formation: Formation, refcomp_infos: dict): +def setup_formation_referentiel( + refcomp_infos: dict, formation: Formation = None +) -> ApcReferentielCompetences: """Si il y a un référentiel de compétences, indiqué dans le YAML, le charge au besoin et l'associe à la formation. """ if not refcomp_infos: - return - assert formation.is_apc() # si ref; comp., doit être APC + return None + assert formation is None or formation.is_apc() # si ref. comp., doit être APC refcomp_filename = refcomp_infos["filename"] refcomp_specialite = refcomp_infos["specialite"] # --- Chargement Référentiel @@ -66,8 +68,10 @@ def setup_formation_referentiel(formation: Formation, refcomp_infos: dict): specialite=refcomp_specialite ).first() # le recherche à nouveau (test) assert referentiel_competence - formation.referentiel_competence_id = referentiel_competence.id - db.session.add(formation) + if formation: + formation.referentiel_competence_id = referentiel_competence.id + db.session.add(formation) + return referentiel_competence def associe_ues_et_parcours(formation: Formation, formation_infos: dict): @@ -100,15 +104,16 @@ def associe_ues_et_parcours(formation: Formation, formation_infos: dict): ue.set_parcours(parcours) # Niveaux compétences: - competence = referentiel_competence.competences.filter_by( - titre=ue_infos["competence"] - ).first() - assert competence is not None # La compétence de titre indiqué doit exister - niveau: ApcNiveau = competence.niveaux.filter_by( - annee=ue_infos["annee"] - ).first() - assert niveau is not None # le niveau de l'année indiquée doit exister - ue.set_niveau_competence(niveau) + if ue_infos.get("competence"): + competence = referentiel_competence.competences.filter_by( + titre=ue_infos["competence"] + ).first() + assert competence is not None # La compétence de titre indiqué doit exister + niveau: ApcNiveau = competence.niveaux.filter_by( + annee=ue_infos["annee"] + ).first() + assert niveau is not None # le niveau de l'année indiquée doit exister + ue.set_niveau_competence(niveau) db.session.commit() associe_modules_et_parcours(formation, formation_infos) From 063e59f1729bd73fec5146fc9152dfaf21f10c5c Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Sun, 14 May 2023 19:03:55 +0200 Subject: [PATCH 03/12] =?UTF-8?q?Test=20BUT=20Info:=20ajout=20=C3=A9dtudia?= =?UTF-8?q?nts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/static/css/scodoc.css | 1 + tests/ressources/yaml/cursus_but_info.yaml | 96 +++++++++++++++++++++- 2 files changed, 94 insertions(+), 3 deletions(-) diff --git a/app/static/css/scodoc.css b/app/static/css/scodoc.css index e6bf3160..7c414e18 100644 --- a/app/static/css/scodoc.css +++ b/app/static/css/scodoc.css @@ -2058,6 +2058,7 @@ span.eval_coef_ue_titre {} div.list_but_ue_inscriptions { margin-top: 16px; margin-bottom: 16px; + margin-right: 8px; padding-left: 8px; padding-bottom: 8px; border-radius: 16px; diff --git a/tests/ressources/yaml/cursus_but_info.yaml b/tests/ressources/yaml/cursus_but_info.yaml index 35fe3d9e..f735e1e5 100644 --- a/tests/ressources/yaml/cursus_but_info.yaml +++ b/tests/ressources/yaml/cursus_but_info.yaml @@ -102,9 +102,9 @@ FormSemestres: codes_parcours: ['A', 'B'] Etudiants: - brillant_a: # cursus S1 -> S6, valide tout - prenom: étudiant - civilite: X + ex_a1: # cursus S1 -> S6, valide tout + prenom: Jean + civilite: M formsemestres: # on ne note que le portfolio, qui affecte toutes les UEs S1: @@ -131,3 +131,93 @@ Etudiants: parcours: A notes_modules: "P6-A": 16 + + ex_a2: # cursus S1 -> S6, valide tout sauf S5 + prenom: Lucie + civilite: F + formsemestres: + # on ne note que le portfolio, qui affecte toutes les UEs + S1: + parcours: A + notes_modules: + "P1": 11 + S2: + parcours: A + notes_modules: + "P2": 12 + S3: + parcours: A + notes_modules: + "P3": 13 + S4: + parcours: A + notes_modules: + "P4-A": 14 + S5: + parcours: A + notes_modules: + "P5-A": 7 + S6: + parcours: A + notes_modules: + "P6-A": 16 + + ex_b1: # cursus S1 -> S6, valide tout + prenom: Hélène + civilite: F + formsemestres: + # on ne note que le portfolio, qui affecte toutes les UEs + S1: + parcours: B + notes_modules: + "P1": 11 + S2: + parcours: B + notes_modules: + "P2": 12 + S3: + parcours: B + notes_modules: + "P3": 13 + S4: + parcours: B + notes_modules: + "P4-B": 14 + S5: + parcours: B + notes_modules: + "P5-B": 15 + S6: + parcours: B + notes_modules: + "P6-B": 16 + + ex_b2: # cursus S1 -> S6, valide tout sauf S6 + prenom: Rose + civilite: F + formsemestres: + # on ne note que le portfolio, qui affecte toutes les UEs + S1: + parcours: B + notes_modules: + "P1": 11 + S2: + parcours: B + notes_modules: + "P2": 12 + S3: + parcours: B + notes_modules: + "P3": 13 + S4: + parcours: B + notes_modules: + "P4-B": 14 + S5: + parcours: B + notes_modules: + "P5-B": 15 + S6: + parcours: B + notes_modules: + "P6-B": 9 From d103b14142a697d8efde7e985428a67b1198f361 Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Sun, 14 May 2023 19:29:25 +0200 Subject: [PATCH 04/12] Fix test unitaire jury BUT (bug dans le setup test) --- sco_version.py | 2 +- tests/unit/yaml_setup.py | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/sco_version.py b/sco_version.py index e43776aa..3e13b5ef 100644 --- a/sco_version.py +++ b/sco_version.py @@ -1,7 +1,7 @@ # -*- mode: python -*- # -*- coding: utf-8 -*- -SCOVERSION = "9.4.74" +SCOVERSION = "9.4.75" SCONAME = "ScoDoc" diff --git a/tests/unit/yaml_setup.py b/tests/unit/yaml_setup.py index 4967af23..f24b92b7 100644 --- a/tests/unit/yaml_setup.py +++ b/tests/unit/yaml_setup.py @@ -120,7 +120,9 @@ def create_formsemestre( modules = [ m for m in formsemestre.formation.modules.filter_by(semestre_id=semestre_id) - if (not m.parcours) or ({p.id for p in m.parcours} & sem_parcours_ids) + if (not m.parcours) # module de tronc commun + or (not sem_parcours_ids) # semestre sans parcours => tous + or ({p.id for p in m.parcours} & sem_parcours_ids) ] for module in modules: modimpl = ModuleImpl(module=module, responsable_id=a_user.id) From d28cc8389143bca17981c55d9d24802d24bbb4a6 Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Sun, 14 May 2023 19:35:47 +0200 Subject: [PATCH 05/12] Added missing Debian dependency: graphviz-dev --- tools/debian/control | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/debian/control b/tools/debian/control index 0857ac6f..4c5c809b 100644 --- a/tools/debian/control +++ b/tools/debian/control @@ -4,4 +4,4 @@ Architecture: amd64 Maintainer: Emmanuel Viennet Description: ScoDoc 9 Un logiciel pour le suivi de la scolarité universitaire. -Depends: adduser, curl, gcc, graphviz, libpq-dev, postfix|exim4, cracklib-runtime, libcrack2-dev, python3-dev, python3-venv, python3-pip, python3-wheel, nginx, postgresql, libpq-dev, redis, ufw +Depends: adduser, curl, gcc, graphviz, graphviz-dev, libpq-dev, postfix|exim4, cracklib-runtime, libcrack2-dev, python3-dev, python3-venv, python3-pip, python3-wheel, nginx, postgresql, libpq-dev, redis, ufw From be03ad0fad83bdcca71fb4c537e891e5b32cbeea Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Mon, 15 May 2023 11:05:51 +0200 Subject: [PATCH 06/12] Fix: cas BUT semestre sans parcours mais UE avec parcours. --- app/comp/res_but.py | 8 ++++++-- app/models/formations.py | 2 +- app/models/formsemestre.py | 7 ++----- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/app/comp/res_but.py b/app/comp/res_but.py index b9efebab..a7af9565 100644 --- a/app/comp/res_but.py +++ b/app/comp/res_but.py @@ -230,7 +230,7 @@ class ResultatsSemestreBUT(NotesTableCompat): } self.etuds_parcour_id = etuds_parcour_id ue_ids = [ue.id for ue in self.ues if ue.type != UE_SPORT] - + ue_ids_set = set(ue_ids) if self.formsemestre.formation.referentiel_competence is None: return pd.DataFrame( 1.0, index=etuds_parcour_id.keys(), columns=ue_ids, dtype=float @@ -240,7 +240,10 @@ class ResultatsSemestreBUT(NotesTableCompat): np.nan, index=etuds_parcour_id.keys(), columns=ue_ids, dtype=float ) # Construit pour chaque parcours du référentiel l'ensemble de ses UE - # (considère aussi le cas des semestres sans parcours: None) + # - considère aussi le cas des semestres sans parcours (clé parcour None) + # - retire les UEs qui ont un parcours mais qui ne sont pas dans l'un des + # parcours du semestre + ue_by_parcours = {} # parcours_id : {ue_id:0|1} for ( parcour @@ -250,6 +253,7 @@ class ResultatsSemestreBUT(NotesTableCompat): for ue in self.formsemestre.formation.query_ues_parcour(parcour).filter( UniteEns.semestre_idx == self.formsemestre.semestre_id ) + if ue.id in ue_ids_set } # for etudid in etuds_parcour_id: diff --git a/app/models/formations.py b/app/models/formations.py index 8c52dff2..e98d66f7 100644 --- a/app/models/formations.py +++ b/app/models/formations.py @@ -217,7 +217,7 @@ class Formation(db.Model): def query_ues_parcour( self, parcour: ApcParcours, with_sport: bool = False ) -> Query: - """Les UEs (non bonus) d'un parcours de la formation + """Les UEs (sans bonus, sauf si with_sport) d'un parcours de la formation (déclarée comme faisant partie du parcours ou du tronc commun, sans aucun parcours) Si parcour est None, les UE sans parcours. Exemple: pour avoir les UE du semestre 3, faire diff --git a/app/models/formsemestre.py b/app/models/formsemestre.py index b061b248..26bb5ac4 100644 --- a/app/models/formsemestre.py +++ b/app/models/formsemestre.py @@ -15,10 +15,8 @@ from functools import cached_property from operator import attrgetter from flask_login import current_user -from flask_sqlalchemy.query import Query from flask import flash, g -from sqlalchemy import and_, or_ from sqlalchemy.sql import text import app.scodoc.sco_utils as scu @@ -26,10 +24,7 @@ from app import db, log from app.auth.models import User from app.models import APO_CODE_STR_LEN, CODE_STR_LEN, SHORT_STR_LEN from app.models.but_refcomp import ( - ApcAnneeParcours, - ApcNiveau, ApcParcours, - ApcParcoursNiveauCompetence, ApcReferentielCompetences, parcours_formsemestre, ) @@ -298,12 +293,14 @@ class FormSemestre(db.Model): """ formation: Formation = self.formation if formation.is_apc(): + # UEs de tronc commun (sans parcours indiqué) sem_ues = { ue.id: ue for ue in formation.query_ues_parcour( None, with_sport=with_sport ).filter(UniteEns.semestre_idx == self.semestre_id) } + # Ajoute les UE de parcours for parcour in self.parcours: sem_ues.update( { From 3de9286e61d37287fd8e578b61426d4a84054b40 Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Mon, 15 May 2023 17:20:38 +0200 Subject: [PATCH 07/12] orthographe --- app/but/jury_but.py | 3 ++- app/entreprises/forms.py | 4 ++-- app/scodoc/sco_apogee_csv.py | 2 +- sco_version.py | 2 +- tests/unit/test_but_cursus.py | 1 + 5 files changed, 7 insertions(+), 5 deletions(-) diff --git a/app/but/jury_but.py b/app/but/jury_but.py index b78a8530..1caa4411 100644 --- a/app/but/jury_but.py +++ b/app/but/jury_but.py @@ -421,7 +421,8 @@ class DecisionsProposeesAnnee(DecisionsProposees): + '
'.join(messages) + "
" ) - # + + # WIP TODO XXX def get_moyenne_annuelle(self) def infos(self) -> str: """informations, for debugging purpose.""" diff --git a/app/entreprises/forms.py b/app/entreprises/forms.py index c50cdd3b..c2b8cdf3 100644 --- a/app/entreprises/forms.py +++ b/app/entreprises/forms.py @@ -318,7 +318,7 @@ class OffreCreationForm(FlaskForm): duree = _build_string_field("Durée (*)") depts = MultiCheckboxField("Départements (*)", validators=[Optional()], coerce=int) expiration_date = DateField("Date expiration", validators=[Optional()]) - correspondant = SelectField("Correspondant à contacté", validators=[Optional()]) + correspondant = SelectField("Correspondant à contacter", validators=[Optional()]) fichier = FileField( "Fichier", validators=[ @@ -373,7 +373,7 @@ class OffreModificationForm(FlaskForm): duree = _build_string_field("Durée (*)") depts = MultiCheckboxField("Départements (*)", validators=[Optional()], coerce=int) expiration_date = DateField("Date expiration", validators=[Optional()]) - correspondant = SelectField("Correspondant à contacté", validators=[Optional()]) + correspondant = SelectField("Correspondant à contacter", validators=[Optional()]) submit = SubmitField("Modifier", render_kw=SUBMIT_MARGE) cancel = SubmitField("Annuler", render_kw=SUBMIT_MARGE) diff --git a/app/scodoc/sco_apogee_csv.py b/app/scodoc/sco_apogee_csv.py index 1449bc65..898647e5 100644 --- a/app/scodoc/sco_apogee_csv.py +++ b/app/scodoc/sco_apogee_csv.py @@ -465,7 +465,7 @@ class ApoEtud(dict): return VOID_APO_RES return dict( - N="", + N="", # n'exporte pas de moyenne indicative annuelle, car pas de définition officielle B=20, J="", R=ScoDocSiteConfig.get_code_apo(self.validation_annee_but.code), diff --git a/sco_version.py b/sco_version.py index 3e13b5ef..8c22b238 100644 --- a/sco_version.py +++ b/sco_version.py @@ -1,7 +1,7 @@ # -*- mode: python -*- # -*- coding: utf-8 -*- -SCOVERSION = "9.4.75" +SCOVERSION = "9.4.76" SCONAME = "ScoDoc" diff --git a/tests/unit/test_but_cursus.py b/tests/unit/test_but_cursus.py index 2d6b029a..12b54be1 100644 --- a/tests/unit/test_but_cursus.py +++ b/tests/unit/test_but_cursus.py @@ -63,6 +63,7 @@ def test_cursus_but_jury_gb(test_client): # @pytest.mark.skip # XXX WIP +@pytest.mark.slow def test_refcomp_niveaux_info(test_client): """Test niveaux / parcours / UE pour un BUT INFO avec parcours A et C, même compétences mais coefs différents From fbf56e7cd7628b204c4026811de306a6f9f30ac5 Mon Sep 17 00:00:00 2001 From: Jean-Marie Place Date: Mon, 15 May 2023 18:00:22 +0200 Subject: [PATCH 08/12] adaptation bulletins classiques pour etat-civil --- app/scodoc/sco_etud.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/app/scodoc/sco_etud.py b/app/scodoc/sco_etud.py index 70b7b163..9f94f382 100644 --- a/app/scodoc/sco_etud.py +++ b/app/scodoc/sco_etud.py @@ -72,6 +72,7 @@ def format_etud_ident(etud): etud["nom_disp"] = etud["nom"] etud["nomprenom"] = format_nomprenom(etud) # M. Pierre DUPONT + etud["etat_civil"] = format_etat_civil(etud) if etud["civilite"] == "M": etud["ne"] = "" elif etud["civilite"] == "F": @@ -157,6 +158,14 @@ def format_civilite(civilite): raise ScoValueError("valeur invalide pour la civilité: %s" % civilite) +def format_etat_civil(etud: dict): + if etud["prenom_etat_civil"]: + civ = {"M": "M.", "F": "Mme", "X": ""}[etud["civilite_etat_civil"]] + return f'{civ} {etud["prenom_etat_civil"]} {etud["nom"]}' + else: + return etud["nomprenom"] + + def format_lycee(nomlycee): nomlycee = nomlycee.strip() s = nomlycee.lower() From 2f16a2f4dfeaf6d4784b745d9a2f3f1cfca9b508 Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Mon, 15 May 2023 19:50:11 +0200 Subject: [PATCH 09/12] =?UTF-8?q?Modification=20cr=C3=A9ation/=C3=A9dition?= =?UTF-8?q?=20=C3=A9tudiants?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/etudiants.py | 61 ++++++++++++++++++++++++++++++++-- app/scodoc/notesdb.py | 4 +-- app/scodoc/sco_etud.py | 47 +++++++++----------------- app/scodoc/sco_import_etuds.py | 4 +-- app/views/scolar.py | 8 +++-- 5 files changed, 84 insertions(+), 40 deletions(-) diff --git a/app/models/etudiants.py b/app/models/etudiants.py index 61f34296..9b2c164c 100644 --- a/app/models/etudiants.py +++ b/app/models/etudiants.py @@ -18,7 +18,7 @@ from app import models from app.scodoc import notesdb as ndb from app.scodoc.sco_bac import Baccalaureat -from app.scodoc.sco_exceptions import ScoInvalidParamError +from app.scodoc.sco_exceptions import ScoInvalidParamError, ScoValueError import app.scodoc.sco_utils as scu @@ -100,7 +100,7 @@ class Identite(db.Model): def create_etud(cls, **args): "Crée un étudiant, avec admission et adresse vides." etud: Identite = cls(**args) - etud.adresses.append(Adresse()) + etud.adresses.append(Adresse(typeadresse="domicile")) etud.admission.append(Admission()) return etud @@ -201,6 +201,48 @@ class Identite(db.Model): reverse=True, ) + @classmethod + def convert_dict_fields(cls, args: dict) -> dict: + "Convert fields in the given dict. No other side effect" + fs_uppercase = {"nom", "prenom", "prenom_etat_civil"} + fs_empty_stored_as_nulls = { + "nom", + "prenom", + "nom_usuel", + "date_naissance", + "lieu_naissance", + "dept_naissance", + "nationalite", + "statut", + "photo_filename", + "code_nip", + "code_ine", + } + args_dict = {} + for key, value in args.items(): + if hasattr(cls, key): + # compat scodoc7 (mauvaise idée de l'époque) + if key in fs_empty_stored_as_nulls and value == "": + value = None + if key in fs_uppercase and value: + value = value.upper() + if key == "civilite" or key == "civilite_etat_civil": + value = input_civilite(value) + elif key == "boursier": + value = bool(value) + elif key == "date_naissance": + value = ndb.DateDMYtoISO(value) + args_dict[key] = value + return args_dict + + def from_dict(self, args: dict): + "update fields given in dict. Add to session but don't commit." + args_dict = Identite.convert_dict_fields(args) + for key, value in args_dict.items(): + if hasattr(self, key): + setattr(self, key, value) + db.session.add(self) + def to_dict_short(self) -> dict: """Les champs essentiels""" return { @@ -543,6 +585,21 @@ def make_etud_args( return args +def input_civilite(s): + """Converts external representation of civilite to internal: + 'M', 'F', or 'X' (and nothing else). + Raises ScoValueError if conversion fails. + """ + s = s.upper().strip() + if s in ("M", "M.", "MR", "H"): + return "M" + elif s in ("F", "MLLE", "MLLE.", "MELLE", "MME"): + return "F" + elif s == "X" or not s: + return "X" + raise ScoValueError("valeur invalide pour la civilité: %s" % s) + + class Adresse(db.Model): """Adresse d'un étudiant (le modèle permet plusieurs adresses, mais l'UI n'en gère qu'une seule) diff --git a/app/scodoc/notesdb.py b/app/scodoc/notesdb.py index e2cb304e..4d9b6aa2 100644 --- a/app/scodoc/notesdb.py +++ b/app/scodoc/notesdb.py @@ -186,7 +186,7 @@ def DBSelectArgs( cond = "" i = 1 cl = [] - for (_, aux_id) in aux_tables: + for _, aux_id in aux_tables: cl.append("T0.%s = T%d.%s" % (id_name, i, aux_id)) i = i + 1 cond += " and ".join(cl) @@ -403,7 +403,7 @@ class EditableTable(object): def format_output(self, r, disable_formatting=False): "Format dict using provided output_formators" - for (k, v) in r.items(): + for k, v in r.items(): if v is None and self.convert_null_outputs_to_empty: v = "" # format value diff --git a/app/scodoc/sco_etud.py b/app/scodoc/sco_etud.py index 70b7b163..4c88f530 100644 --- a/app/scodoc/sco_etud.py +++ b/app/scodoc/sco_etud.py @@ -35,10 +35,10 @@ from operator import itemgetter from flask import url_for, g -from app import email +from app import db, email from app import log -from app.models import Admission -from app.models.etudiants import make_etud_args +from app.models import Admission, Identite +from app.models.etudiants import input_civilite, make_etud_args import app.scodoc.sco_utils as scu import app.scodoc.notesdb as ndb from app.scodoc.sco_exceptions import ScoGenError, ScoValueError @@ -127,21 +127,6 @@ def format_nom(s, uppercase=True): return format_prenom(s) -def input_civilite(s): - """Converts external representation of civilite to internal: - 'M', 'F', or 'X' (and nothing else). - Raises ScoValueError if conversion fails. - """ - s = s.upper().strip() - if s in ("M", "M.", "MR", "H"): - return "M" - elif s in ("F", "MLLE", "MLLE.", "MELLE", "MME"): - return "F" - elif s == "X" or not s: - return "X" - raise ScoValueError("valeur invalide pour la civilité: %s" % s) - - def format_civilite(civilite): """returns 'M.' ou 'Mme' ou '' (pour le genre neutre, personne ne souhaitant pas d'affichage). @@ -281,7 +266,9 @@ def identite_list(cnx, *a, **kw): def identite_edit_nocheck(cnx, args): """Modifie les champs mentionnes dans args, sans verification ni notification.""" - _identiteEditor.edit(cnx, args) + etud = Identite.query.get(args["etudid"]) + etud.from_dict(args) + db.session.commit() def check_nom_prenom(cnx, nom="", prenom="", etudid=None): @@ -572,6 +559,7 @@ admission_delete = _admissionEditor.delete admission_list = _admissionEditor.list admission_edit = _admissionEditor.edit + # Edition simultanee de identite et admission class EtudIdentEditor(object): def create(self, cnx, args): @@ -615,7 +603,6 @@ class EtudIdentEditor(object): _etudidentEditor = EtudIdentEditor() etudident_list = _etudidentEditor.list etudident_edit = _etudidentEditor.edit -etudident_create = _etudidentEditor.create def log_unknown_etud(): @@ -654,8 +641,8 @@ def get_etud_info(etudid=False, code_nip=False, filled=False) -> list[dict]: # return etud[0] -def create_etud(cnx, args={}): - """Creation d'un étudiant. génère aussi évenement et "news". +def create_etud(cnx, args: dict = None): + """Création d'un étudiant. Génère aussi évenement et "news". Args: args: dict avec les attributs de l'étudiant @@ -666,16 +653,12 @@ def create_etud(cnx, args={}): from app.models import ScolarNews # creation d'un etudiant - etudid = etudident_create(cnx, args) - # crée une adresse vide (chaque etudiant doit etre dans la table "adresse" !) - _ = adresse_create( - cnx, - { - "etudid": etudid, - "typeadresse": "domicile", - "description": "(creation individuelle)", - }, - ) + args_dict = Identite.convert_dict_fields(args) + args_dict["dept_id"] = g.scodoc_dept_id + etud = Identite.create_etud(**args_dict) + db.session.add(etud) + db.session.commit() + etudid = etud.id # event scolar_events_create( diff --git a/app/scodoc/sco_import_etuds.py b/app/scodoc/sco_import_etuds.py index 2d67c08c..736d6021 100644 --- a/app/scodoc/sco_import_etuds.py +++ b/app/scodoc/sco_import_etuds.py @@ -40,7 +40,7 @@ import app.scodoc.sco_utils as scu import app.scodoc.notesdb as ndb from app import log from app.models import ScolarNews, GroupDescr - +from app.models.etudiants import input_civilite from app.scodoc.sco_excel import COLORS from app.scodoc.sco_formsemestre_inscriptions import ( do_formsemestre_inscription_with_modules, @@ -370,7 +370,7 @@ def scolars_import_excel_file( # xxx Ad-hoc checks (should be in format description) if titleslist[i].lower() == "sexe": try: - val = sco_etud.input_civilite(val) + val = input_civilite(val) except: raise ScoValueError( "valeur invalide pour 'SEXE' (doit etre 'M', 'F', ou 'MME', 'H', 'X' ou vide, mais pas '%s') ligne %d, colonne %s" diff --git a/app/views/scolar.py b/app/views/scolar.py index 390ba239..44084b50 100644 --- a/app/views/scolar.py +++ b/app/views/scolar.py @@ -1742,8 +1742,12 @@ def _etudident_create_or_edit_form(edit): etudid = etud["etudid"] else: # modif d'un etudiant - sco_etud.etudident_edit(cnx, tf[2]) - etud = sco_etud.etudident_list(cnx, {"etudid": etudid})[0] + args_dict = Identite.convert_dict_fields(tf[2]) + etud_o = Identite.create_etud(**args_dict) + db.session.add(etud_o) + db.session.commit() + + etud = sco_etud.etudident_list(cnx, {"etudid": etud_o.id})[0] sco_etud.fill_etuds_info([etud]) # Inval semesters with this student: to_inval = [s["formsemestre_id"] for s in etud["sems"]] From a6b8f2b05972accd8e46a75864d098aaa9faeb71 Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Mon, 15 May 2023 22:20:28 +0200 Subject: [PATCH 10/12] =?UTF-8?q?Edition=20donn=C3=A9es=20admission?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/etudiants.py | 64 ++++++++++++++++++++++++++++++++++++----- app/scodoc/sco_etud.py | 34 ++++------------------ app/views/scolar.py | 7 +++-- 3 files changed, 67 insertions(+), 38 deletions(-) diff --git a/app/models/etudiants.py b/app/models/etudiants.py index 9b2c164c..0f5f59c2 100644 --- a/app/models/etudiants.py +++ b/app/models/etudiants.py @@ -238,6 +238,8 @@ class Identite(db.Model): def from_dict(self, args: dict): "update fields given in dict. Add to session but don't commit." args_dict = Identite.convert_dict_fields(args) + args_dict.pop("id", None) + args_dict.pop("etudid", None) for key, value in args_dict.items(): if hasattr(self, key): setattr(self, key, value) @@ -597,7 +599,23 @@ def input_civilite(s): return "F" elif s == "X" or not s: return "X" - raise ScoValueError("valeur invalide pour la civilité: %s" % s) + raise ScoValueError(f"valeur invalide pour la civilité: {s}") + + +PIVOT_YEAR = 70 + + +def pivot_year(y) -> int: + "converti et calcule l'année si saisie à deux chiffres" + if y == "" or y is None: + return None + y = int(round(float(y))) + if y >= 0 and y < 100: + if y < PIVOT_YEAR: + y = y + 2000 + else: + y = y + 1900 + return y class Adresse(db.Model): @@ -693,19 +711,51 @@ class Admission(db.Model): d = dict(self.__dict__) d.pop("_sa_instance_state", None) if no_nulls: - for k in d.keys(): - if d[k] is None: + for key, value in d.items(): + if value is None: col_type = getattr( - sqlalchemy.inspect(models.Admission).columns, "apb_groupe" + sqlalchemy.inspect(models.Admission).columns, key ).expression.type if isinstance(col_type, sqlalchemy.Text): - d[k] = "" + d[key] = "" elif isinstance(col_type, sqlalchemy.Integer): - d[k] = 0 + d[key] = 0 elif isinstance(col_type, sqlalchemy.Boolean): - d[k] = False + d[key] = False return d + @classmethod + def convert_dict_fields(cls, args: dict) -> dict: + "Convert fields in the given dict. No other side effect" + fs_uppercase = {"bac", "specialite"} + args_dict = {} + for key, value in args.items(): + if hasattr(cls, key): + if ( + value == "" + ): # les chaines vides donne des NULLS (scodoc7 convention) + value = None + if key in fs_uppercase and value: + value = value.upper() + if key == "civilite" or key == "civilite_etat_civil": + value = input_civilite(value) + elif key == "annee" or key == "annee_bac": + value = pivot_year(value) + elif key == "classement" or key == "apb_classement_gr": + value = ndb.int_null_is_null(value) + args_dict[key] = value + return args_dict + + def from_dict(self, args: dict): # TODO à refactoriser dans une super-classe + "update fields given in dict. Add to session but don't commit." + args_dict = Admission.convert_dict_fields(args) + args_dict.pop("adm_id", None) + args_dict.pop("id", None) + for key, value in args_dict.items(): + if hasattr(self, key): + setattr(self, key, value) + db.session.add(self) + # Suivi scolarité / débouchés class ItemSuivi(db.Model): diff --git a/app/scodoc/sco_etud.py b/app/scodoc/sco_etud.py index 2176b26b..7cdb19a0 100644 --- a/app/scodoc/sco_etud.py +++ b/app/scodoc/sco_etud.py @@ -38,7 +38,7 @@ from flask import url_for, g from app import db, email from app import log from app.models import Admission, Identite -from app.models.etudiants import input_civilite, make_etud_args +from app.models.etudiants import input_civilite, make_etud_args, pivot_year import app.scodoc.sco_utils as scu import app.scodoc.notesdb as ndb from app.scodoc.sco_exceptions import ScoGenError, ScoValueError @@ -189,21 +189,6 @@ def format_pays(s): return "" -PIVOT_YEAR = 70 - - -def pivot_year(y): - if y == "" or y is None: - return None - y = int(round(float(y))) - if y >= 0 and y < 100: - if y < PIVOT_YEAR: - y = y + 2000 - else: - y = y + 1900 - return y - - def etud_sort_key(etud: dict) -> tuple: """Clé de tri pour les étudiants représentés par des dict (anciens codes). Equivalent moderne: identite.sort_key @@ -637,19 +622,6 @@ def get_etud_info(etudid=False, code_nip=False, filled=False) -> list[dict]: return etud -# Optim par cache local, utilité non prouvée mais -# on s'oriente vers un cahce persistent dans Redis ou bien l'utilisation de NT -# def get_etud_info_filled_by_etudid(etudid, cnx=None) -> dict: -# """Infos sur un étudiant, avec cache local à la requête""" -# if etudid in g.stored_etud_info: -# return g.stored_etud_info[etudid] -# cnx = cnx or ndb.GetDBConnexion() -# etud = etudident_list(cnx, args={"etudid": etudid}) -# fill_etuds_info(etud) -# g.stored_etud_info[etudid] = etud[0] -# return etud[0] - - def create_etud(cnx, args: dict = None): """Création d'un étudiant. Génère aussi évenement et "news". @@ -667,6 +639,10 @@ def create_etud(cnx, args: dict = None): etud = Identite.create_etud(**args_dict) db.session.add(etud) db.session.commit() + admission = etud.admission.first() + admission.from_dict(args) + db.session.add(admission) + db.session.commit() etudid = etud.id # event diff --git a/app/views/scolar.py b/app/views/scolar.py index 44084b50..a53df3f3 100644 --- a/app/views/scolar.py +++ b/app/views/scolar.py @@ -1742,9 +1742,12 @@ def _etudident_create_or_edit_form(edit): etudid = etud["etudid"] else: # modif d'un etudiant - args_dict = Identite.convert_dict_fields(tf[2]) - etud_o = Identite.create_etud(**args_dict) + etud_o = Identite.query.get(tf[2]["etudid"]) + etud_o.from_dict(tf[2]) db.session.add(etud_o) + admission = etud_o.admission.first() + admission.from_dict(tf[2]) + db.session.add(admission) db.session.commit() etud = sco_etud.etudident_list(cnx, {"etudid": etud_o.id})[0] From ee9fd059da4ed9bc6ab9452bd6b194d1e2c0c7d7 Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Mon, 15 May 2023 23:39:08 +0200 Subject: [PATCH 11/12] API: groups_auto_assignment --- app/api/formsemestres.py | 45 +++++++++++- app/models/formsemestre.py | 6 ++ ...c79_add_data_for_groups_auto_assignment.py | 34 +++++++++ tests/api/test_api_formsemestre_data.py | 71 +++++++++++++++++++ 4 files changed, 155 insertions(+), 1 deletion(-) create mode 100644 migrations/versions/b8df1b913c79_add_data_for_groups_auto_assignment.py create mode 100644 tests/api/test_api_formsemestre_data.py diff --git a/app/api/formsemestres.py b/app/api/formsemestres.py index e7ca242f..18b98738 100644 --- a/app/api/formsemestres.py +++ b/app/api/formsemestres.py @@ -9,11 +9,12 @@ """ from operator import attrgetter, itemgetter -from flask import g, request +from flask import g, make_response, request from flask_json import as_json from flask_login import login_required import app +from app import db from app.api import api_bp as bp, api_web_bp, API_CLIENT_ERROR from app.decorators import scodoc, permission_required from app.scodoc.sco_utils import json_error @@ -30,6 +31,7 @@ from app.models import ( ModuleImpl, NotesNotes, ) +from app.models.formsemestre import GROUPS_AUTO_ASSIGNMENT_DATA_MAX from app.scodoc.sco_bulletins import get_formsemestre_bulletin_etud_json from app.scodoc import sco_groups from app.scodoc.sco_permissions import Permission @@ -496,3 +498,44 @@ def formsemestre_resultat(formsemestre_id: int): row["partitions"] = etud_groups.get(row["etudid"], {}) return rows + + +@bp.route("/formsemestre//get_groups_auto_assignment") +@api_web_bp.route("/formsemestre//get_groups_auto_assignment") +@login_required +@scodoc +@permission_required(Permission.ScoView) +@as_json +def get_groups_auto_assignment(formsemestre_id: int): + """rend les données""" + query = FormSemestre.query.filter_by(id=formsemestre_id) + if g.scodoc_dept: + query = query.filter_by(dept_id=g.scodoc_dept_id) + formsemestre: FormSemestre = query.first_or_404(formsemestre_id) + response = make_response(formsemestre.groups_auto_assignment_data or b"") + response.headers["Content-Type"] = scu.JSON_MIMETYPE + return response + + +@bp.route( + "/formsemestre//save_groups_auto_assignment", methods=["POST"] +) +@api_web_bp.route( + "/formsemestre//save_groups_auto_assignment", methods=["POST"] +) +@login_required +@scodoc +@permission_required(Permission.ScoView) +@as_json +def save_groups_auto_assignment(formsemestre_id: int): + """enregistre les données""" + query = FormSemestre.query.filter_by(id=formsemestre_id) + if g.scodoc_dept: + query = query.filter_by(dept_id=g.scodoc_dept_id) + formsemestre: FormSemestre = query.first_or_404(formsemestre_id) + + if len(request.data) > GROUPS_AUTO_ASSIGNMENT_DATA_MAX: + return json_error(413, "data too large") + formsemestre.groups_auto_assignment_data = request.data + db.session.add(formsemestre) + db.session.commit() diff --git a/app/models/formsemestre.py b/app/models/formsemestre.py index 26bb5ac4..68399262 100644 --- a/app/models/formsemestre.py +++ b/app/models/formsemestre.py @@ -42,6 +42,8 @@ from app.scodoc.sco_permissions import Permission from app.scodoc.sco_utils import MONTH_NAMES_ABBREV from app.scodoc.sco_vdi import ApoEtapeVDI +GROUPS_AUTO_ASSIGNMENT_DATA_MAX = 1024 * 1024 # bytes + class FormSemestre(db.Model): """Mise en oeuvre d'un semestre de formation""" @@ -108,6 +110,10 @@ class FormSemestre(db.Model): elt_annee_apo = db.Column(db.Text()) "code element annee Apogee, eg 'VRT1A' ou 'V2INLA,V2INCA,...'" + # Data pour groups_auto_assignment + # (ce champ est utilisé uniquement via l'API par le front js) + groups_auto_assignment_data = db.Column(db.LargeBinary(), nullable=True) + # Relations: etapes = db.relationship( "FormSemestreEtape", cascade="all,delete", backref="formsemestre" diff --git a/migrations/versions/b8df1b913c79_add_data_for_groups_auto_assignment.py b/migrations/versions/b8df1b913c79_add_data_for_groups_auto_assignment.py new file mode 100644 index 00000000..54e9db27 --- /dev/null +++ b/migrations/versions/b8df1b913c79_add_data_for_groups_auto_assignment.py @@ -0,0 +1,34 @@ +"""Add data for groups_auto_assignment + +Revision ID: b8df1b913c79 +Revises: 054dd6133b9c +Create Date: 2023-05-15 23:12:58.257709 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = "b8df1b913c79" +down_revision = "054dd6133b9c" +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table("notes_formsemestre", schema=None) as batch_op: + batch_op.add_column( + sa.Column("groups_auto_assignment_data", sa.LargeBinary(), nullable=True) + ) + + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table("notes_formsemestre", schema=None) as batch_op: + batch_op.drop_column("groups_auto_assignment_data") + + # ### end Alembic commands ### diff --git a/tests/api/test_api_formsemestre_data.py b/tests/api/test_api_formsemestre_data.py new file mode 100644 index 00000000..fd9b9470 --- /dev/null +++ b/tests/api/test_api_formsemestre_data.py @@ -0,0 +1,71 @@ +"""Test formsemestre + +Utilisation : + créer les variables d'environnement: (indiquer les valeurs + pour le serveur ScoDoc que vous voulez interroger) + + export SCODOC_URL="https://scodoc.xxx.net/" + export SCODOC_USER="xxx" + export SCODOC_PASSWD="xxx" + export CHECK_CERTIFICATE=0 # ou 1 si serveur de production avec certif SSL valide + + (on peut aussi placer ces valeurs dans un fichier .env du répertoire tests/api). + + Lancer : + pytest tests/api/test_api_formsemestre.py +""" +import requests + +from app.scodoc import sco_utils as scu + +from tests.api.setup_test_api import ( + API_URL, + CHECK_CERTIFICATE, + api_headers, +) + + +def test_save_groups_auto_assignment(api_headers): + """ + Routes: + /formsemestre//save_groups_auto_assignment + /formsemestre//get_groups_auto_assignment + """ + formsemestre_id = 1 + r = requests.get( + f"{API_URL}/formsemestre/{formsemestre_id}", + headers=api_headers, + verify=CHECK_CERTIFICATE, + timeout=scu.SCO_TEST_API_TIMEOUT, + ) + assert r.status_code == 200 + # On stocke une chaine quelconque + data_orig = ( + """{ "attribute" : "Un paquet de json", "valide": pas nécessairement +}--""" + ) + r = requests.post( + f"{API_URL}/formsemestre/{formsemestre_id}/save_groups_auto_assignment", + data=data_orig.encode("utf-8"), + headers=api_headers, + verify=CHECK_CERTIFICATE, + timeout=scu.SCO_TEST_API_TIMEOUT, + ) + assert r.status_code == 200 + # GET + r = requests.get( + f"{API_URL}/formsemestre/{formsemestre_id}/get_groups_auto_assignment", + headers=api_headers, + verify=CHECK_CERTIFICATE, + timeout=scu.SCO_TEST_API_TIMEOUT, + ) + assert r.status_code == 200 + assert r.text == data_orig + # Tente d'envoyer trop de données + r = requests.post( + f"{API_URL}/formsemestre/{formsemestre_id}/save_groups_auto_assignment", + data="F*CK" * 1000000, # environ 4MB + headers=api_headers, + verify=CHECK_CERTIFICATE, + timeout=scu.SCO_TEST_API_TIMEOUT, + ) + assert r.status_code == 413 From 66215101b571116fc2df2a7be4b2bc5257d62caf Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Tue, 16 May 2023 10:56:37 +0200 Subject: [PATCH 12/12] Fix: bonus sport si aucune UE --- app/comp/bonus_spo.py | 2 ++ sco_version.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/app/comp/bonus_spo.py b/app/comp/bonus_spo.py index d9ca76bb..64ba2979 100644 --- a/app/comp/bonus_spo.py +++ b/app/comp/bonus_spo.py @@ -106,6 +106,8 @@ class BonusSport: if formsemestre.formation.is_apc(): # BUT nb_ues_no_bonus = sem_modimpl_moys.shape[2] + if nb_ues_no_bonus == 0: # aucune UE... + return # no bonus at all # Duplique les inscriptions sur les UEs non bonus: modimpl_inscr_spo_stacked = np.stack( [modimpl_inscr_spo] * nb_ues_no_bonus, axis=2 diff --git a/sco_version.py b/sco_version.py index 8c22b238..071863e3 100644 --- a/sco_version.py +++ b/sco_version.py @@ -1,7 +1,7 @@ # -*- mode: python -*- # -*- coding: utf-8 -*- -SCOVERSION = "9.4.76" +SCOVERSION = "9.4.77" SCONAME = "ScoDoc"