From 65b87049ca4c541dd6c64dc7876e6fc43d9c88fd Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Sun, 26 Mar 2023 10:08:50 +0200 Subject: [PATCH] BUT ref. Comp.: fix calcul niveaux/parcours, ajoute tests unitaires GCCD et MLT. --- app/models/but_refcomp.py | 38 ++++++---- app/scodoc/sco_formations.py | 2 +- app/templates/but/refcomp_show.j2 | 15 +++- app/views/pn_modules.py | 18 ++--- app/views/refcomp.py | 6 +- sco_version.py | 2 +- tests/unit/test_refcomp.py | 113 ++++++++++++++++++++++++++++++ 7 files changed, 159 insertions(+), 35 deletions(-) diff --git a/app/models/but_refcomp.py b/app/models/but_refcomp.py index 84dd2e1c..d70323ad 100644 --- a/app/models/but_refcomp.py +++ b/app/models/but_refcomp.py @@ -85,7 +85,11 @@ class ApcReferentielCompetences(db.Model, XMLModel): lazy="dynamic", cascade="all, delete-orphan", ) - formations = db.relationship("Formation", backref="referentiel_competence") + formations = db.relationship( + "Formation", + backref="referentiel_competence", + order_by="Formation.acronyme, Formation.version", + ) def __repr__(self): return f"" @@ -360,32 +364,36 @@ class ApcNiveau(db.Model, XMLModel): parcour: "ApcParcours", annee: int, referentiel_competence: ApcReferentielCompetences = None, - ) -> flask_sqlalchemy.BaseQuery: + ) -> list["ApcNiveau"]: """Les niveaux de l'année du parcours Si le parcour est None, tous les niveaux de l'année """ if annee not in {1, 2, 3}: raise ValueError("annee invalide pour un parcours BUT") + referentiel_competence = ( + parcour.referentiel if parcour else referentiel_competence + ) if referentiel_competence is None: raise ScoNoReferentielCompetences() - - annee_formation = f"BUT{annee}" - if parcour is None: + if not parcour: + annee_formation = f"BUT{annee}" return ApcNiveau.query.filter( ApcNiveau.annee == annee_formation, ApcCompetence.id == ApcNiveau.competence_id, ApcCompetence.referentiel_id == referentiel_competence.id, ) - else: - return ApcNiveau.query.filter( - ApcParcoursNiveauCompetence.annee_parcours_id == ApcAnneeParcours.id, - ApcParcours.id == ApcAnneeParcours.parcours_id, - ApcParcours.referentiel == parcour.referentiel, - ApcParcoursNiveauCompetence.competence_id == ApcCompetence.id, - ApcCompetence.id == ApcNiveau.competence_id, - ApcAnneeParcours.parcours == parcour, - ApcNiveau.annee == annee_formation, - ) + annee_parcour = parcour.annees.filter_by(ordre=annee).first() + if not annee_parcour: + return [] + + parcour_niveaux: list[ + ApcParcoursNiveauCompetence + ] = annee_parcour.niveaux_competences + niveaux: list[ApcNiveau] = [ + pn.competence.niveaux.filter_by(ordre=pn.niveau).first() + for pn in parcour_niveaux + ] + return niveaux app_critiques_modules = db.Table( diff --git a/app/scodoc/sco_formations.py b/app/scodoc/sco_formations.py index be7e348c..e2d6fd96 100644 --- a/app/scodoc/sco_formations.py +++ b/app/scodoc/sco_formations.py @@ -554,7 +554,7 @@ def formation_list_table() -> GenTable: row["_buttons_html"] = but_locked + but_suppr + but_edit rows.append(row) # Tri par annee_dernier_sem, type, acronyme, titre, version décroissante - # donc plus récemment utilsiée en tête + # donc plus récemment utilisée en tête rows.sort( key=lambda row: ( -row["annee_dernier_sem"], diff --git a/app/templates/but/refcomp_show.j2 b/app/templates/but/refcomp_show.j2 index b6bc0915..c6d4b272 100644 --- a/app/templates/but/refcomp_show.j2 +++ b/app/templates/but/refcomp_show.j2 @@ -19,7 +19,20 @@
- liste des référentiels + +
{% endblock %} diff --git a/app/views/pn_modules.py b/app/views/pn_modules.py index 70e85e8d..a5345dca 100644 --- a/app/views/pn_modules.py +++ b/app/views/pn_modules.py @@ -33,31 +33,21 @@ Emmanuel Viennet, 2021 from flask import url_for from flask import jsonify -from flask import current_app, g, request +from flask import g, request from flask.templating import render_template from flask_login import current_user -from werkzeug.utils import redirect from app.scodoc.codes_cursus import UE_SPORT -from config import Config -from app import db -from app import models -from app.auth.models import User +from app import db, models from app.comp import moy_ue from app.decorators import scodoc, permission_required +from app.models import ApcParcours, Formation, Module from app.views import notes_bp as bp -# --------------- - -from app.scodoc import sco_utils as scu -from app.scodoc import sco_formations -from app import log - -from app.models import ApcParcours, Formation, UniteEns, Module - from app.scodoc import html_sco_header +from app.scodoc import sco_utils as scu from app.scodoc.sco_permissions import Permission diff --git a/app/views/refcomp.py b/app/views/refcomp.py index 6e820f4b..825791b2 100644 --- a/app/views/refcomp.py +++ b/app/views/refcomp.py @@ -44,17 +44,17 @@ def refcomp(refcomp_id): @permission_required(Permission.ScoView) def refcomp_show(refcomp_id): """Affichage du référentiel de compétences.""" - ref = ApcReferentielCompetences.query.get_or_404(refcomp_id) + referentiel_competence = ApcReferentielCompetences.query.get_or_404(refcomp_id) return render_template( "but/refcomp_show.j2", - ref=ref, + ref=referentiel_competence, title="Référentiel de compétences", - sco=ScoData(), data_source=url_for( "notes.refcomp", scodoc_dept=g.scodoc_dept, refcomp_id=refcomp_id, ), + sco=ScoData(), ) diff --git a/sco_version.py b/sco_version.py index 0247747d..eed78321 100644 --- a/sco_version.py +++ b/sco_version.py @@ -1,7 +1,7 @@ # -*- mode: python -*- # -*- coding: utf-8 -*- -SCOVERSION = "9.4.70" +SCOVERSION = "9.4.71" SCONAME = "ScoDoc" diff --git a/tests/unit/test_refcomp.py b/tests/unit/test_refcomp.py index b09b9775..8121cf98 100644 --- a/tests/unit/test_refcomp.py +++ b/tests/unit/test_refcomp.py @@ -24,6 +24,12 @@ from tests.unit import setup REF_RT_XML = open( "ressources/referentiels/but2022/competences/but-RT-05012022-081735.xml" ).read() +REF_MLT_XML = open( + "ressources/referentiels/but2022/competences/but-MLT-05012022-081603.xml" +).read() +REF_GCCD_XML = open( + "ressources/referentiels/but2022/competences/but-GCCD-05012022-081630.xml" +).read() def test_but_refcomp(test_client): @@ -84,3 +90,110 @@ def test_but_assoc_refcomp(test_client): db.session.commit() formation.refcomp_desassoc() assert ue.niveau_competence_id is None + + +def test_refcomp_niveaux_mlt(test_client): + """Test calcul niveaux / parcours pour un BUT MLT + avec en parcours "Mobilité et Supply Chain Connectées" + une compétence "Transporter" sur BUT1, BUT2 + et une compétence "Digitaliser" sur BUT2, BUT3 + """ + # Charger le ref. comp. MLT + dept_id = models.Departement.query.first().id + ref_comp: ApcReferentielCompetences = orebut_import_refcomp(REF_MLT_XML, dept_id) + # Vérifier qu'on a 2 parcours et 4 compétences dans chaque + nb_parcours = ref_comp.parcours.count() + assert nb_parcours == 2 + assert [len(p.query_competences().all()) for p in ref_comp.parcours] == [ + 4 + ] * nb_parcours + # 3 niveaux en 1ere année (dans chaque parcours): + assert [ + len(ApcNiveau.niveaux_annee_de_parcours(p, 1, ref_comp)) + for p in ref_comp.parcours + ] == [3, 3] + # 2 niveaux en 2ème année (dans chaque parcours): + assert [ + len(ApcNiveau.niveaux_annee_de_parcours(p, 2, ref_comp)) + for p in ref_comp.parcours + ] == [4, 4] + # 3 niveaux en 3ème année (dans chaque parcours): + assert [ + len(ApcNiveau.niveaux_annee_de_parcours(p, 3, ref_comp)) + for p in ref_comp.parcours + ] == [3, 3] + # Vérifier les niveaux_by_parcours + parcour = ref_comp.parcours.first() + # BUT 1 + parcours, niveaux_by_parcours = ref_comp.get_niveaux_by_parcours(1, parcour) + assert parcours == [parcour] # le parcours indiqué + assert (tuple(niveaux_by_parcours.keys())) == (parcour.id, "TC") + assert niveaux_by_parcours[parcour.id] == [] # tout en tronc commun en BUT1 MLT + assert niveaux_by_parcours["TC"][0].competence.titre == "Transporter" + assert len(niveaux_by_parcours["TC"]) == 3 + # BUT 2 + parcours, niveaux_by_parcours = ref_comp.get_niveaux_by_parcours(2, parcour) + assert parcours == [parcour] # le parcours indiqué + assert (tuple(niveaux_by_parcours.keys())) == (parcour.id, "TC") + assert len(niveaux_by_parcours[parcour.id]) == 1 + assert len(niveaux_by_parcours["TC"]) == 3 + # BUT 3 + parcours, niveaux_by_parcours = ref_comp.get_niveaux_by_parcours(3, parcour) + assert parcours == [parcour] # le parcours indiqué + assert (tuple(niveaux_by_parcours.keys())) == (parcour.id, "TC") + assert len(niveaux_by_parcours[parcour.id]) == 1 + assert len(niveaux_by_parcours["TC"]) == 2 + # Vérifier les niveaux de chaque année + assert len(ApcNiveau.niveaux_annee_de_parcours(parcour, 1)) == 3 + assert len(ApcNiveau.niveaux_annee_de_parcours(parcour, 2)) == 4 + assert len(ApcNiveau.niveaux_annee_de_parcours(parcour, 3)) == 3 + + +def test_refcomp_niveaux_gccd(test_client): + """Test calcul niveaux / parcours pour un BUT GCCD + avec en parcours "Travaux Bâtiment" 5 compétences + dont la première "Solutions Bâtiment" sur BUT1, BUT2, BUT3 + et en parcours "Travaux Publics" la même sur BUT1, BUT2 seulement. + """ + # Charger le ref. comp. GCCD + dept_id = models.Departement.query.first().id + ref_comp: ApcReferentielCompetences = orebut_import_refcomp(REF_GCCD_XML, dept_id) + # Vérifier qu'on a 4 parcours et 5 compétences dans chaque + nb_parcours = ref_comp.parcours.count() + assert nb_parcours == 4 + assert [len(p.query_competences().all()) for p in ref_comp.parcours] == [ + 5 + ] * nb_parcours + # 5 niveaux en 1ere année (dans chaque parcours): + assert [ + len(ApcNiveau.niveaux_annee_de_parcours(p, 1, ref_comp)) + for p in ref_comp.parcours + ] == [5] * nb_parcours + # 5 niveaux en 2ème année (dans chaque parcours): + assert [ + len(ApcNiveau.niveaux_annee_de_parcours(p, 2, ref_comp)) + for p in ref_comp.parcours + ] == [5] * nb_parcours + # 3 niveaux en 3ème année (dans chaque parcours): + assert [ + len(ApcNiveau.niveaux_annee_de_parcours(p, 3, ref_comp)) + for p in ref_comp.parcours + ] == [3] * nb_parcours + # Vérifier les niveaux_by_parcours + parcour = ref_comp.parcours.first() + # BUT 1 + parcours, niveaux_by_parcours = ref_comp.get_niveaux_by_parcours(1, parcour) + assert parcours == [parcour] # le parcours indiqué + assert (tuple(niveaux_by_parcours.keys())) == (parcour.id, "TC") + assert len(niveaux_by_parcours[parcour.id]) == 0 + assert len(niveaux_by_parcours["TC"]) == 5 + # BUT 3 + parcours, niveaux_by_parcours = ref_comp.get_niveaux_by_parcours(3, parcour) + assert parcours == [parcour] # le parcours indiqué + assert (tuple(niveaux_by_parcours.keys())) == (parcour.id, "TC") + assert len(niveaux_by_parcours[parcour.id]) == 3 + assert len(niveaux_by_parcours["TC"]) == 0 + # Vérifier les niveaux de chaque année + assert len(ApcNiveau.niveaux_annee_de_parcours(parcour, 1)) == 5 + assert len(ApcNiveau.niveaux_annee_de_parcours(parcour, 2)) == 5 + assert len(ApcNiveau.niveaux_annee_de_parcours(parcour, 3)) == 3