BUT ref. Comp.: fix calcul niveaux/parcours, ajoute tests unitaires GCCD et MLT.

This commit is contained in:
Emmanuel Viennet 2023-03-26 10:08:50 +02:00
parent 8af5c3ffa0
commit 65b87049ca
7 changed files with 159 additions and 35 deletions

View File

@ -85,7 +85,11 @@ class ApcReferentielCompetences(db.Model, XMLModel):
lazy="dynamic", lazy="dynamic",
cascade="all, delete-orphan", 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): def __repr__(self):
return f"<ApcReferentielCompetences {self.id} {self.specialite!r} {self.departement!r}>" return f"<ApcReferentielCompetences {self.id} {self.specialite!r} {self.departement!r}>"
@ -360,32 +364,36 @@ class ApcNiveau(db.Model, XMLModel):
parcour: "ApcParcours", parcour: "ApcParcours",
annee: int, annee: int,
referentiel_competence: ApcReferentielCompetences = None, referentiel_competence: ApcReferentielCompetences = None,
) -> flask_sqlalchemy.BaseQuery: ) -> list["ApcNiveau"]:
"""Les niveaux de l'année du parcours """Les niveaux de l'année du parcours
Si le parcour est None, tous les niveaux de l'année Si le parcour est None, tous les niveaux de l'année
""" """
if annee not in {1, 2, 3}: if annee not in {1, 2, 3}:
raise ValueError("annee invalide pour un parcours BUT") raise ValueError("annee invalide pour un parcours BUT")
referentiel_competence = (
parcour.referentiel if parcour else referentiel_competence
)
if referentiel_competence is None: if referentiel_competence is None:
raise ScoNoReferentielCompetences() raise ScoNoReferentielCompetences()
if not parcour:
annee_formation = f"BUT{annee}" annee_formation = f"BUT{annee}"
if parcour is None:
return ApcNiveau.query.filter( return ApcNiveau.query.filter(
ApcNiveau.annee == annee_formation, ApcNiveau.annee == annee_formation,
ApcCompetence.id == ApcNiveau.competence_id, ApcCompetence.id == ApcNiveau.competence_id,
ApcCompetence.referentiel_id == referentiel_competence.id, ApcCompetence.referentiel_id == referentiel_competence.id,
) )
else: annee_parcour = parcour.annees.filter_by(ordre=annee).first()
return ApcNiveau.query.filter( if not annee_parcour:
ApcParcoursNiveauCompetence.annee_parcours_id == ApcAnneeParcours.id, return []
ApcParcours.id == ApcAnneeParcours.parcours_id,
ApcParcours.referentiel == parcour.referentiel, parcour_niveaux: list[
ApcParcoursNiveauCompetence.competence_id == ApcCompetence.id, ApcParcoursNiveauCompetence
ApcCompetence.id == ApcNiveau.competence_id, ] = annee_parcour.niveaux_competences
ApcAnneeParcours.parcours == parcour, niveaux: list[ApcNiveau] = [
ApcNiveau.annee == annee_formation, pn.competence.niveaux.filter_by(ordre=pn.niveau).first()
) for pn in parcour_niveaux
]
return niveaux
app_critiques_modules = db.Table( app_critiques_modules = db.Table(

View File

@ -554,7 +554,7 @@ def formation_list_table() -> GenTable:
row["_buttons_html"] = but_locked + but_suppr + but_edit row["_buttons_html"] = but_locked + but_suppr + but_edit
rows.append(row) rows.append(row)
# Tri par annee_dernier_sem, type, acronyme, titre, version décroissante # 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( rows.sort(
key=lambda row: ( key=lambda row: (
-row["annee_dernier_sem"], -row["annee_dernier_sem"],

View File

@ -19,7 +19,20 @@
<div class="part2"> <div class="part2">
<a class="stdlink" href="{{url_for('notes.refcomp_table', scodoc_dept=g.scodoc_dept)}}">liste des référentiels</a> <ul>
<li>Formations se référant à ce référentiel:
<ul>
{% for formation in ref.formations %}
<li><a class="stdlink" href="{{
url_for('notes.ue_table', scodoc_dept=g.scodoc_dept, formation_id=formation.id )
}}">{{ formation.get_titre_version() }}</a></li>
{% endfor %}
</ul>
</li>
<li><a class="stdlink" href="{{url_for('notes.refcomp_table', scodoc_dept=g.scodoc_dept)}}">Liste des référentiels</a>
</li>
</ul>
</div> </div>
{% endblock %} {% endblock %}

View File

@ -33,31 +33,21 @@ Emmanuel Viennet, 2021
from flask import url_for from flask import url_for
from flask import jsonify from flask import jsonify
from flask import current_app, g, request from flask import g, request
from flask.templating import render_template from flask.templating import render_template
from flask_login import current_user from flask_login import current_user
from werkzeug.utils import redirect
from app.scodoc.codes_cursus import UE_SPORT from app.scodoc.codes_cursus import UE_SPORT
from config import Config
from app import db from app import db, models
from app import models
from app.auth.models import User
from app.comp import moy_ue from app.comp import moy_ue
from app.decorators import scodoc, permission_required from app.decorators import scodoc, permission_required
from app.models import ApcParcours, Formation, Module
from app.views import notes_bp as bp 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 html_sco_header
from app.scodoc import sco_utils as scu
from app.scodoc.sco_permissions import Permission from app.scodoc.sco_permissions import Permission

View File

@ -44,17 +44,17 @@ def refcomp(refcomp_id):
@permission_required(Permission.ScoView) @permission_required(Permission.ScoView)
def refcomp_show(refcomp_id): def refcomp_show(refcomp_id):
"""Affichage du référentiel de compétences.""" """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( return render_template(
"but/refcomp_show.j2", "but/refcomp_show.j2",
ref=ref, ref=referentiel_competence,
title="Référentiel de compétences", title="Référentiel de compétences",
sco=ScoData(),
data_source=url_for( data_source=url_for(
"notes.refcomp", "notes.refcomp",
scodoc_dept=g.scodoc_dept, scodoc_dept=g.scodoc_dept,
refcomp_id=refcomp_id, refcomp_id=refcomp_id,
), ),
sco=ScoData(),
) )

View File

@ -1,7 +1,7 @@
# -*- mode: python -*- # -*- mode: python -*-
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
SCOVERSION = "9.4.70" SCOVERSION = "9.4.71"
SCONAME = "ScoDoc" SCONAME = "ScoDoc"

View File

@ -24,6 +24,12 @@ from tests.unit import setup
REF_RT_XML = open( REF_RT_XML = open(
"ressources/referentiels/but2022/competences/but-RT-05012022-081735.xml" "ressources/referentiels/but2022/competences/but-RT-05012022-081735.xml"
).read() ).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): def test_but_refcomp(test_client):
@ -84,3 +90,110 @@ def test_but_assoc_refcomp(test_client):
db.session.commit() db.session.commit()
formation.refcomp_desassoc() formation.refcomp_desassoc()
assert ue.niveau_competence_id is None 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