forked from ScoDoc/ScoDoc
Association parcours/UE: amélioration formulaire. Messages erreurs. Logique association UE/niveaux. test unitaire partiel. WIP.
This commit is contained in:
parent
04b7ff7658
commit
6357dd999d
@ -346,10 +346,10 @@ def assoc_ue_niveau(ue_id: int, niveau_id: int):
|
||||
ok, error_message = ue.set_niveau_competence(niveau)
|
||||
if not ok:
|
||||
if g.scodoc_dept: # "usage web"
|
||||
flash(error_message)
|
||||
flash(error_message, "error")
|
||||
return json_error(404, error_message)
|
||||
if g.scodoc_dept: # "usage web"
|
||||
flash(f"UE {ue.acronyme} associée au niveau {niveau.libelle}")
|
||||
flash(f"""{ue.acronyme} associée au niveau "{niveau.libelle}" """)
|
||||
return {"status": 0}
|
||||
|
||||
|
||||
|
@ -280,10 +280,14 @@ class UniteEns(db.Model):
|
||||
and ue.niveau_competence_id == niveau.id
|
||||
]
|
||||
if ues_meme_niveau:
|
||||
msg_parc = f"parcours {code_parcour}" if parcour else "tronc commun"
|
||||
if len(ues_meme_niveau) > 1: # deja 2 UE sur ce niveau
|
||||
msg = f"""Niveau "{
|
||||
niveau.libelle}" déjà associé à deux UE du parcours {code_parcour}"""
|
||||
log("check_niveau_unique_dans_parcours: " + msg)
|
||||
niveau.libelle}" déjà associé à deux UE du {msg_parc}"""
|
||||
log(
|
||||
f"check_niveau_unique_dans_parcours(niveau_id={niveau.id}): "
|
||||
+ msg
|
||||
)
|
||||
return False, msg
|
||||
# s'il y a déjà une UE associée à ce niveau, elle doit être dans l'autre semestre
|
||||
# de la même année scolaire
|
||||
@ -292,8 +296,11 @@ class UniteEns(db.Model):
|
||||
)
|
||||
if ues_meme_niveau[0].semestre_idx != other_semestre_idx:
|
||||
msg = f"""Niveau "{
|
||||
niveau.libelle}" associé à une autre année du parcours {code_parcour}"""
|
||||
log("check_niveau_unique_dans_parcours: " + msg)
|
||||
niveau.libelle}" associé à une autre année du {msg_parc}"""
|
||||
log(
|
||||
f"check_niveau_unique_dans_parcours(niveau_id={niveau.id}): "
|
||||
+ msg
|
||||
)
|
||||
return False, msg
|
||||
|
||||
return True, ""
|
||||
@ -314,16 +321,30 @@ class UniteEns(db.Model):
|
||||
False,
|
||||
"La formation n'est pas associée à un référentiel de compétences",
|
||||
)
|
||||
if niveau.competence.referentiel.id != self.formation.referentiel_competence.id:
|
||||
return False, "Le niveau n'appartient pas au référentiel de la formation"
|
||||
if niveau.id == self.niveau_competence_id:
|
||||
if niveau is not None:
|
||||
if self.niveau_competence_id is not None:
|
||||
return (
|
||||
False,
|
||||
f"{self.acronyme} déjà associée à un niveau de compétences",
|
||||
)
|
||||
if (
|
||||
niveau.competence.referentiel.id
|
||||
!= self.formation.referentiel_competence.id
|
||||
):
|
||||
return (
|
||||
False,
|
||||
"Le niveau n'appartient pas au référentiel de la formation",
|
||||
)
|
||||
if niveau.id == self.niveau_competence_id:
|
||||
return True, "" # nothing to do
|
||||
if self.niveau_competence_id is not None:
|
||||
ok, error_message = self.check_niveau_unique_dans_parcours(
|
||||
niveau, self.parcours
|
||||
)
|
||||
if not ok:
|
||||
return ok, error_message
|
||||
elif self.niveau_competence_id is None:
|
||||
return True, "" # nothing to do
|
||||
if (niveau is not None) and (self.niveau_competence_id is not None):
|
||||
ok, error_message = self.check_niveau_unique_dans_parcours(
|
||||
niveau, self.parcours
|
||||
)
|
||||
if not ok:
|
||||
return ok, error_message
|
||||
self.niveau_competence = niveau
|
||||
db.session.add(self)
|
||||
db.session.commit()
|
||||
|
@ -64,6 +64,26 @@ div#gtrcontent {
|
||||
display: None;
|
||||
}
|
||||
|
||||
div.alert {
|
||||
z-index: 9;
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
}
|
||||
|
||||
div.alert-info {
|
||||
color: #0019d7;
|
||||
background-color: #68f36d;
|
||||
border-color: #0a8d0c;
|
||||
}
|
||||
|
||||
div.alert-error {
|
||||
color: #ef0020;
|
||||
background-color: #ffff00;
|
||||
border-color: #8d0a17;
|
||||
}
|
||||
|
||||
|
||||
div.tab-content {
|
||||
margin-top: 10px;
|
||||
margin-left: 15px;
|
||||
@ -938,7 +958,7 @@ span.linktitresem a:visited {
|
||||
|
||||
a.stdlink,
|
||||
a.stdlink:visited {
|
||||
color: #0e0e9d;
|
||||
color: blue;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
|
@ -177,7 +177,7 @@ function assoc_ue_niveau(event, niveau_id) {
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.status) {
|
||||
sco_message(data.message);
|
||||
/* sco_message(data.message); */
|
||||
/* revert menu to initial state */
|
||||
event.target.value = event.target.dataset.ue_id;
|
||||
}
|
||||
|
@ -87,6 +87,21 @@ def parcour_formation(formation_id: int, parcour_id: int = None) -> str:
|
||||
)
|
||||
|
||||
|
||||
def ue_associee_au_niveau_du_parcours(
|
||||
ues_possibles: list[UniteEns], niveau: ApcNiveau, sem_name: str = "S"
|
||||
) -> UniteEns:
|
||||
"L'UE associée à ce niveau, ou None"
|
||||
ues = [ue for ue in ues_possibles if ue.niveau_competence_id == niveau.id]
|
||||
if len(ues) > 1:
|
||||
# plusieurs UEs associées à ce niveau: élimine celles sans parcours
|
||||
ues_pair_avec_parcours = [ue for ue in ues if ue.parcours]
|
||||
if ues_pair_avec_parcours:
|
||||
ues = ues_pair_avec_parcours
|
||||
if len(ues) > 1:
|
||||
log(f"_niveau_ues: {len(ues)} associées au niveau {niveau} / {sem_name}")
|
||||
return ues[0] if ues else None
|
||||
|
||||
|
||||
def parcour_formation_competences(parcour: ApcParcours, formation: Formation) -> list:
|
||||
"""
|
||||
[
|
||||
@ -108,59 +123,57 @@ def parcour_formation_competences(parcour: ApcParcours, formation: Formation) ->
|
||||
"""
|
||||
|
||||
def _niveau_ues(competence: ApcCompetence, annee: int) -> dict:
|
||||
"niveau et ues pour l'année du parcours"
|
||||
"niveau et ues pour cette compétence de cette année du parcours"
|
||||
niveaux = ApcNiveau.niveaux_annee_de_parcours(
|
||||
parcour, annee, competence=competence
|
||||
)
|
||||
if len(niveaux) > 0:
|
||||
if len(niveaux) > 1:
|
||||
log(f"_niveau_ues: plus d'un niveau pour {competence} annee {annee}")
|
||||
log(
|
||||
f"""_niveau_ues: plus d'un niveau pour {competence}
|
||||
annee {annee} parcours {parcour.code}"""
|
||||
)
|
||||
niveau = niveaux[0]
|
||||
elif len(niveaux) == 0:
|
||||
return {"niveau": None, "ue_pair": None, "ue_impair": None}
|
||||
# toutes les UEs de la formation associées à ce niveau
|
||||
return {
|
||||
"niveau": None,
|
||||
"ue_pair": None,
|
||||
"ue_impair": None,
|
||||
"ues_pair": [],
|
||||
"ues_impair": [],
|
||||
}
|
||||
# Toutes les UEs de la formation dans ce parcours ou tronc commun
|
||||
ues = [
|
||||
ue
|
||||
for ue in niveau.ues
|
||||
if ue.formation.id == formation.id
|
||||
# and parcour.id in (p.id for p in ue.parcours)
|
||||
]
|
||||
ues_pair = [ue for ue in ues if ue.semestre_idx == 2 * annee]
|
||||
if len(ues_pair) > 0:
|
||||
ue_pair = ues_pair[0]
|
||||
if len(ues_pair) > 1:
|
||||
log(
|
||||
f"_niveau_ues: {len(ues)} associées au niveau {niveau} / S{2*annee}"
|
||||
)
|
||||
else:
|
||||
ue_pair = None
|
||||
ues_pair_possibles = [
|
||||
ue
|
||||
for ue in formation.ues.filter_by(semestre_idx=2 * annee, type=UE_STANDARD)
|
||||
if (ue.niveau_competence is None) or (ue.niveau_competence_id == niveau.id)
|
||||
]
|
||||
ues_impair = [ue for ue in ues if ue.semestre_idx == (2 * annee - 1)]
|
||||
if len(ues_impair) > 0:
|
||||
ue_impair = ues_impair[0]
|
||||
if len(ues_impair) > 1:
|
||||
log(
|
||||
f"_niveau_ues: {len(ues)} associées au niveau {niveau} / S{2*annee-1}"
|
||||
)
|
||||
else:
|
||||
ue_impair = None
|
||||
ues_impair_possibles = [
|
||||
ue
|
||||
for ue in formation.ues.filter_by(
|
||||
semestre_idx=2 * annee - 1, type=UE_STANDARD
|
||||
)
|
||||
if (ue.niveau_competence is None) or (ue.niveau_competence_id == niveau.id)
|
||||
for ue in formation.ues
|
||||
if ((not ue.parcours) or (parcour.id in (p.id for p in ue.parcours)))
|
||||
and ue.type == UE_STANDARD
|
||||
]
|
||||
ues_pair_possibles = [ue for ue in ues if ue.semestre_idx == (2 * annee)]
|
||||
ues_impair_possibles = [ue for ue in ues if ue.semestre_idx == (2 * annee - 1)]
|
||||
|
||||
# UE associée au niveau dans ce parcours
|
||||
ue_pair = ue_associee_au_niveau_du_parcours(
|
||||
ues_pair_possibles, niveau, f"S{2*annee}"
|
||||
)
|
||||
ue_impair = ue_associee_au_niveau_du_parcours(
|
||||
ues_impair_possibles, niveau, f"S{2*annee-1}"
|
||||
)
|
||||
|
||||
return {
|
||||
"niveau": niveau,
|
||||
"ue_pair": ue_pair,
|
||||
"ues_pair": ues_pair_possibles,
|
||||
"ues_pair": [
|
||||
ue
|
||||
for ue in ues_pair_possibles
|
||||
if (not ue.niveau_competence) or ue.niveau_competence.id == niveau.id
|
||||
],
|
||||
"ue_impair": ue_impair,
|
||||
"ues_impair": ues_impair_possibles,
|
||||
"ues_impair": [
|
||||
ue
|
||||
for ue in ues_impair_possibles
|
||||
if (not ue.niveau_competence) or ue.niveau_competence.id == niveau.id
|
||||
],
|
||||
}
|
||||
|
||||
competences = [
|
||||
|
77
tests/unit/test_cursus_but.py
Normal file
77
tests/unit/test_cursus_but.py
Normal file
@ -0,0 +1,77 @@
|
||||
# -*- coding: UTF-8 -*
|
||||
|
||||
"""Unit tests : cursus_but
|
||||
|
||||
|
||||
Ce test suppose une base département existante.
|
||||
|
||||
Usage: pytest tests/unit/test_cursus_but.py
|
||||
"""
|
||||
|
||||
|
||||
import pytest
|
||||
from tests.unit import yaml_setup
|
||||
|
||||
import app
|
||||
|
||||
# XXX from app.but.cursus_but import FormSemestreCursusBUT
|
||||
from app.comp import res_sem
|
||||
from app.comp.res_but import ResultatsSemestreBUT
|
||||
from app.models import ApcParcours, ApcReferentielCompetences, FormSemestre
|
||||
from config import TestConfig
|
||||
|
||||
DEPT = TestConfig.DEPT_TEST
|
||||
|
||||
|
||||
@pytest.mark.skip # XXX WIP
|
||||
@pytest.mark.slow
|
||||
def test_cursus_but_jury_gb(test_client):
|
||||
# Construit la base de test GB une seule fois
|
||||
# puis lance les tests de jury
|
||||
app.set_sco_dept(DEPT)
|
||||
# login_user(User.query.filter_by(user_name="admin").first()) # XXX pour tests manuels
|
||||
# ctx.push() # XXX
|
||||
doc = yaml_setup.setup_from_yaml("tests/ressources/yaml/cursus_but_gb.yaml")
|
||||
formsemestre: FormSemestre = FormSemestre.query.filter_by(titre="S3").first()
|
||||
res: ResultatsSemestreBUT = res_sem.load_formsemestre_results(formsemestre)
|
||||
cursus = FormSemestreCursusBUT(res)
|
||||
ref_comp: ApcReferentielCompetences = formsemestre.formation.referentiel_competence
|
||||
# Vérifie niveaux du tronc commun:
|
||||
niveaux_parcours_by_annee_tc = cursus.get_niveaux_parcours_by_annee(None)
|
||||
assert set(niveaux_parcours_by_annee_tc.keys()) == {1, 2, 3}
|
||||
# structure particulière à GB:
|
||||
assert [len(niveaux_parcours_by_annee_tc[annee]) for annee in (1, 2, 3)] == [
|
||||
2,
|
||||
2,
|
||||
1,
|
||||
]
|
||||
parcour: ApcParcours = ref_comp.parcours.filter_by(code="SEE").first()
|
||||
assert parcour
|
||||
niveaux_parcours_by_annee_see = cursus.get_niveaux_parcours_by_annee(parcour)
|
||||
assert set(niveaux_parcours_by_annee_see.keys()) == {1, 2, 3}
|
||||
# GB SEE: 4 niveaux en BU1, 5 en BUT2, 4 en BUT3
|
||||
assert [len(niveaux_parcours_by_annee_see[annee]) for annee in (1, 2, 3)] == [
|
||||
4,
|
||||
5,
|
||||
4,
|
||||
]
|
||||
# Un étudiant inscrit en SEE
|
||||
inscription = formsemestre.etuds_inscriptions[1]
|
||||
assert inscription.parcour.code == "SEE"
|
||||
etud = inscription.etud
|
||||
assert cursus.get_niveaux_parcours_etud(etud) == niveaux_parcours_by_annee_see
|
||||
|
||||
|
||||
# @pytest.mark.skip # XXX WIP
|
||||
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
|
||||
selon le parcours.
|
||||
"""
|
||||
# WIP
|
||||
# pour le moment juste le chargement de la formation, du ref. comp, et des UE du S4.
|
||||
app.set_sco_dept(DEPT)
|
||||
doc = yaml_setup.setup_from_yaml("tests/ressources/yaml/cursus_but_info.yaml")
|
||||
formsemestre: FormSemestre = FormSemestre.query.filter_by(titre="S4").first()
|
||||
assert formsemestre
|
||||
res: ResultatsSemestreBUT = res_sem.load_formsemestre_results(formsemestre)
|
Loading…
Reference in New Issue
Block a user