From ee95a6178acb847a7b6283a7514f5c52620ff860 Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Thu, 29 Jun 2023 23:24:36 +0200 Subject: [PATCH] WIP: jury BUT: adaptations des tests unitaires, traite semestre par semestre --- app/but/jury_but.py | 13 +- .../ressources/yaml/cursus_but_geii_lyon.yaml | 2 +- tests/unit/test_but_jury.py | 90 ++++-------- tests/unit/yaml_setup.py | 135 ++++++++++-------- tests/unit/yaml_setup_but.py | 4 +- 5 files changed, 122 insertions(+), 122 deletions(-) diff --git a/app/but/jury_but.py b/app/but/jury_but.py index bf39b7128..31367a98e 100644 --- a/app/but/jury_but.py +++ b/app/but/jury_but.py @@ -776,8 +776,10 @@ class DecisionsProposeesAnnee(DecisionsProposees): # UEs for dec_ue in self.decisions_ues.values(): if ( - not dec_ue.recorded - ) and dec_ue.formsemestre.annee_scolaire() == annee_scolaire: + dec_ue.formsemestre + and (not dec_ue.recorded) + and dec_ue.formsemestre.annee_scolaire() == annee_scolaire + ): # rappel: le code par défaut est en tête code = dec_ue.codes[0] if dec_ue.codes else None if (not only_validantes) or code in sco_codes.CODES_UE_VALIDES_DE_DROIT: @@ -1457,7 +1459,12 @@ class DecisionsProposeesUE(DecisionsProposees): ) or self.formsemestre.modalite == "EXT": self.codes.insert(0, sco_codes.ADM) self.explanation = f"Moyenne >= {sco_codes.CursusBUT.BARRE_MOY}/20" - elif self.rcue and self.rcue.est_compensable(): + elif ( + self.rcue + and self.rcue.est_compensable() + and self.ue_status + and not self.ue_status["is_capitalized"] + ): self.codes.insert(0, sco_codes.CMP) self.explanation = "compensable dans le RCUE" else: diff --git a/tests/ressources/yaml/cursus_but_geii_lyon.yaml b/tests/ressources/yaml/cursus_but_geii_lyon.yaml index c1457d1a5..7ae5ecaae 100644 --- a/tests/ressources/yaml/cursus_but_geii_lyon.yaml +++ b/tests/ressources/yaml/cursus_but_geii_lyon.yaml @@ -1090,7 +1090,7 @@ Etudiants: "S1.2": 12.0000 attendu: # les codes jury que l'on doit vérifier deca: - passage_de_droit: False + passage_de_droit: True nb_competences: 2 nb_rcue_annee: 0 decisions_ues: diff --git a/tests/unit/test_but_jury.py b/tests/unit/test_but_jury.py index acb5c67da..0eb2458af 100644 --- a/tests/unit/test_but_jury.py +++ b/tests/unit/test_but_jury.py @@ -30,6 +30,26 @@ from config import TestConfig DEPT = TestConfig.DEPT_TEST +def setup_and_test_jurys(yaml_filename: str): + "Charge YAML et lance test jury BUT" + app.set_sco_dept(DEPT) + # Construit la base de test GB une seule fois + # puis lance les tests de jury + doc, formation, formsemestre_titres = yaml_setup.setup_from_yaml(yaml_filename) + + for formsemestre_titre in formsemestre_titres: + formsemestre = yaml_setup.create_formsemestre_with_etuds( + doc, formation, formsemestre_titre + ) + # Vérifie les champs de DecisionsProposeesAnnee de ce semestre + yaml_setup_but.check_deca_fields(formsemestre) + + # Saisie de toutes les décisions de jury "automatiques" + # et vérification des résultats attendus: + formsemestre_validation_auto_but(formsemestre, only_adm=False) + yaml_setup_but.but_test_jury(formsemestre, doc) + + @pytest.mark.slow @pytest.mark.but_gb def test_but_jury_GB(test_client): @@ -40,76 +60,21 @@ def test_but_jury_GB(test_client): - vérification jury de S3 - vérification jury de S1 avec redoublants et capitalisations """ - app.set_sco_dept(DEPT) - # Construit la base de test GB une seule fois - # puis lance les tests de jury - doc = yaml_setup.setup_from_yaml("tests/ressources/yaml/cursus_but_gb.yaml") - - # Vérifie les deca de tous les semestres: - for formsemestre in FormSemestre.query: - yaml_setup_but.check_deca_fields(formsemestre) - - # Saisie de toutes les décisions de jury - for formsemestre in FormSemestre.query.order_by(FormSemestre.semestre_id): - formsemestre_validation_auto_but(formsemestre, only_adm=False) - - # Vérifie résultats attendus: - S1: FormSemestre = FormSemestre.query.filter_by(titre="S1_SEE").first() - yaml_setup_but.but_test_jury(S1, doc) - S2: FormSemestre = FormSemestre.query.filter_by(titre="S2_SEE").first() - yaml_setup_but.but_test_jury(S2, doc) - S3: FormSemestre = FormSemestre.query.filter_by(titre="S3").first() - yaml_setup_but.but_test_jury(S3, doc) - # _test_but_jury(S1_redoublant, doc) + setup_and_test_jurys("tests/ressources/yaml/cursus_but_gb.yaml") @pytest.mark.slow @pytest.mark.lemans def test_but_jury_GMP_lm(test_client): """Tests sur un cursus GMP fourni par Le Mans""" - app.set_sco_dept(DEPT) - # Construit la base de test GB une seule fois - # puis lance les tests de jury - doc = yaml_setup.setup_from_yaml("tests/ressources/yaml/cursus_but_gmp_iutlm.yaml") - - formsemestres = FormSemestre.query.order_by( - FormSemestre.date_debut, FormSemestre.semestre_id - ).all() - - # Vérifie les deca de tous les semestres: - for formsemestre in formsemestres: - yaml_setup_but.check_deca_fields(formsemestre) - - # Saisie de toutes les décisions de jury qui ne le seraient pas déjà - for formsemestre in formsemestres: - formsemestre_validation_auto_but(formsemestre, only_adm=False) - - # Vérifie résultats attendus: - for formsemestre in formsemestres: - yaml_setup_but.but_test_jury(formsemestre, doc) + setup_and_test_jurys("tests/ressources/yaml/cursus_but_gmp_iutlm.yaml") @pytest.mark.slow @pytest.mark.lyon def test_but_jury_GEII_lyon(test_client): """Tests sur un cursus GEII fourni par Lyon""" - app.set_sco_dept(DEPT) - # Construit la base de test GB une seule fois - # puis lance les tests de jury - doc = yaml_setup.setup_from_yaml("tests/ressources/yaml/cursus_but_geii_lyon.yaml") - formsemestres = FormSemestre.query.order_by( - FormSemestre.date_debut, FormSemestre.semestre_id - ).all() - - # Vérifie les champs de DecisionsProposeesAnnee de tous les semestres: - for formsemestre in formsemestres: - yaml_setup_but.check_deca_fields(formsemestre) - - # Saisie de toutes les décisions de jury "automatiques" - # et vérification des résultats attendus: - for formsemestre in formsemestres: - formsemestre_validation_auto_but(formsemestre, only_adm=False) - yaml_setup_but.but_test_jury(formsemestre, doc) + setup_and_test_jurys("tests/ressources/yaml/cursus_but_geii_lyon.yaml") @pytest.mark.slow @@ -118,7 +83,14 @@ def test_but_jury_GCCD_CY(test_client): """Tests sur un cursus BUT GCCD de S1 à S6""" # WIP app.set_sco_dept(DEPT) - doc = yaml_setup.setup_from_yaml("tests/ressources/yaml/cursus_but_gccd_cy.yaml") + doc, formation, formsemestre_titres = yaml_setup.setup_from_yaml( + "tests/ressources/yaml/cursus_but_gccd_cy.yaml" + ) + for formsemestre_titre in formsemestre_titres: + _ = yaml_setup.create_formsemestre_with_etuds( + doc, formation, formsemestre_titre + ) + formsemestres = FormSemestre.query.order_by( FormSemestre.date_debut, FormSemestre.semestre_id ).all() diff --git a/tests/unit/yaml_setup.py b/tests/unit/yaml_setup.py index 35b016a2f..bf4dc70e4 100644 --- a/tests/unit/yaml_setup.py +++ b/tests/unit/yaml_setup.py @@ -17,7 +17,7 @@ setup_from_yaml() - import de la formation (le test utilise une seule formation) - associe_ues_et_parcours(): - crée les associations formation <-> référentiel de compétence - - setup_formsemestres() + - setup_formsemestre() - crée les formsemestres décrits dans le YAML avec tous les modules du semestre ou des parcours si indiqués et une évaluation dans chaque moduleimpl. @@ -166,19 +166,20 @@ def create_evaluations(formsemestre: FormSemestre, publish_incomplete=True): evaluation.set_ue_poids_dict(ue_coef_dict) -def note_les_modules(doc: dict): - """Saisie les notes des étudiants +def note_les_modules(doc: dict, formsemestre_titre: str = ""): + """Saisie les notes des étudiants pour ce formsemestre doc : données YAML """ a_user = User.query.first() + formsemestre: FormSemestre = FormSemestre.query.filter_by( + titre=formsemestre_titre + ).first() + assert formsemestre is not None for nom, infos in doc["Etudiants"].items(): etud: Identite = Identite.query.filter_by(nom=nom).first() assert etud is not None - for titre, sem_infos in infos["formsemestres"].items(): - formsemestre: FormSemestre = FormSemestre.query.filter_by( - titre=titre - ).first() - assert formsemestre is not None + sem_infos = infos["formsemestres"].get(formsemestre_titre) + if sem_infos: for code_module, note in sem_infos.get("notes_modules", {}).items(): modimpl = ( formsemestre.modimpls.join(Module) @@ -209,57 +210,69 @@ def note_les_modules(doc: dict): ) -def setup_formsemestres(formation: Formation, doc: str): - """Création des formsemestres - Le cas échéant associés à leur(s) parcours. +def setup_formsemestre( + formation: Formation, doc: str, formsemestre_titre: str = "" +) -> FormSemestre: + """Création du formsemestre de titre indiqué. + Le cas échéant associé à son parcours. """ - for titre, infos in doc["FormSemestres"].items(): - codes_parcours = infos.get("codes_parcours", []) - assert formation.is_apc() or not codes_parcours # parcours seulement en APC - parcours = [] - for code_parcour in codes_parcours: - parcour = formation.referentiel_competence.parcours.filter_by( - code=code_parcour - ).first() - assert parcour is not None - parcours.append(parcour) + infos = doc["FormSemestres"][formsemestre_titre] - _ = create_formsemestre( - formation, - parcours, - infos["idx"], - titre, - infos["date_debut"], - infos["date_fin"], - ) + codes_parcours = infos.get("codes_parcours", []) + assert formation.is_apc() or not codes_parcours # parcours seulement en APC + parcours = [] + for code_parcour in codes_parcours: + parcour = formation.referentiel_competence.parcours.filter_by( + code=code_parcour + ).first() + assert parcour is not None + parcours.append(parcour) + + formsemestre = create_formsemestre( + formation, + parcours, + infos["idx"], + formsemestre_titre, + infos["date_debut"], + infos["date_fin"], + ) db.session.flush() - assert FormSemestre.query.count() == len(doc["FormSemestres"]) + return formsemestre -def inscrit_les_etudiants(formation: Formation, doc: dict): +def inscrit_les_etudiants(doc: dict, formsemestre_titre: str = ""): """Inscrit les étudiants dans chacun de leurs formsemestres""" etudiants = doc.get("Etudiants") if not etudiants: return + formsemestre: FormSemestre = FormSemestre.query.filter_by( + titre=formsemestre_titre + ).first() + assert formsemestre is not None + partition_parcours = formsemestre.partitions.filter_by( + partition_name=scu.PARTITION_PARCOURS + ).first() for nom, infos in etudiants.items(): - etud = Identite.create_etud( - dept_id=g.scodoc_dept_id, - nom=nom, - prenom=infos.get("prenom", "prénom"), - civilite=infos.get("civilite", "X"), - ) - db.session.add(etud) - db.session.commit() - # L'inscrire à ses formsemestres - for titre, sem_infos in infos["formsemestres"].items(): - formsemestre: FormSemestre = FormSemestre.query.filter_by( - titre=titre - ).first() - assert formsemestre is not None - partition_parcours = formsemestre.partitions.filter_by( - partition_name=scu.PARTITION_PARCOURS - ).first() + # Création des étudiants (sauf si déjà existants) + prenom = infos.get("prenom", "prénom") + civilite = infos.get("civilite", "X") + etud = Identite.query.filter_by( + nom=nom, prenom=prenom, civilite=civilite + ).first() + if etud is None: + etud = Identite.create_etud( + dept_id=g.scodoc_dept_id, + nom=nom, + prenom=prenom, + civilite=civilite, + ) + db.session.add(etud) + db.session.commit() + + # L'inscrire au formsemestre + sem_infos = infos["formsemestres"].get(formsemestre_titre) + if sem_infos: if partition_parcours is not None and "parcours" in sem_infos: group = partition_parcours.groups.filter_by( group_name=sem_infos["parcours"] @@ -278,9 +291,8 @@ def inscrit_les_etudiants(formation: Formation, doc: dict): etat=scu.INSCRIT, method="tests/unit/inscrit_les_etudiants", ) - # Met à jour les inscriptions: - for formsemestre in formation.formsemestres: - formsemestre.update_inscriptions_parcours_from_groups() + # Met à jour les inscriptions: + formsemestre.update_inscriptions_parcours_from_groups() def etud_dispense_ues( @@ -296,7 +308,7 @@ def etud_dispense_ues( db.session.add(disp) -def setup_from_yaml(filename: str) -> dict: +def setup_from_yaml(filename: str) -> tuple[dict, Formation, list[str]]: """Lit le fichier yaml et construit l'ensemble des objets""" with open(filename, encoding="utf-8") as f: doc = yaml.safe_load(f.read()) @@ -307,9 +319,20 @@ def setup_from_yaml(filename: str) -> dict: formation = setup_formation(doc["Formation"]) yaml_setup_but.associe_ues_et_parcours(formation, doc["Formation"]) - setup_formsemestres(formation, doc) + + formsemestre_titres = list(doc["FormSemestres"].keys()) + return doc, formation, formsemestre_titres + + +def create_formsemestre_with_etuds( + doc: dict, formation: Formation, formsemestre_titre: str +) -> FormSemestre: + """Création formsemestre de titre indiqué, puis inscrit ses étudianst et les note""" + formsemestre = setup_formsemestre( + formation, doc, formsemestre_titre=formsemestre_titre + ) etudiants = doc.get("Etudiants") if etudiants: - inscrit_les_etudiants(formation, doc) - note_les_modules(doc) - return doc + inscrit_les_etudiants(doc, formsemestre_titre=formsemestre_titre) + note_les_modules(doc, formsemestre_titre=formsemestre_titre) + return formsemestre diff --git a/tests/unit/yaml_setup_but.py b/tests/unit/yaml_setup_but.py index 606fa14d1..8412ed2be 100644 --- a/tests/unit/yaml_setup_but.py +++ b/tests/unit/yaml_setup_but.py @@ -272,9 +272,7 @@ def check_deca_fields(formsemestre: FormSemestre, etud: Identite = None): etud = etud or formsemestre.etuds.first() assert etud # il faut au moins un étudiant dans le semestre deca = DecisionsProposeesAnnee(etud, formsemestre) - assert deca.validation is None # pas encore de validation enregistrée assert False is deca.recorded - assert deca.code_valide is None parcour = deca.parcour formation: Formation = formsemestre.formation ues = ( @@ -310,7 +308,7 @@ def check_deca_fields(formsemestre: FormSemestre, etud: Identite = None): if deca.formsemestre_impair else 0 ) - assert len(deca.decisions_ues) == nb_ues + assert nb_ues > 0 assert len(deca.niveaux_competences) == len(ues) assert deca.nb_competences == len(ues)