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
3baae95baf
commit
da2f0ac2f9
@ -346,10 +346,10 @@ def assoc_ue_niveau(ue_id: int, niveau_id: int):
|
|||||||
ok, error_message = ue.set_niveau_competence(niveau)
|
ok, error_message = ue.set_niveau_competence(niveau)
|
||||||
if not ok:
|
if not ok:
|
||||||
if g.scodoc_dept: # "usage web"
|
if g.scodoc_dept: # "usage web"
|
||||||
flash(error_message)
|
flash(error_message, "error")
|
||||||
return json_error(404, error_message)
|
return json_error(404, error_message)
|
||||||
if g.scodoc_dept: # "usage web"
|
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}
|
return {"status": 0}
|
||||||
|
|
||||||
|
|
||||||
|
@ -280,10 +280,14 @@ class UniteEns(db.Model):
|
|||||||
and ue.niveau_competence_id == niveau.id
|
and ue.niveau_competence_id == niveau.id
|
||||||
]
|
]
|
||||||
if ues_meme_niveau:
|
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
|
if len(ues_meme_niveau) > 1: # deja 2 UE sur ce niveau
|
||||||
msg = f"""Niveau "{
|
msg = f"""Niveau "{
|
||||||
niveau.libelle}" déjà associé à deux UE du parcours {code_parcour}"""
|
niveau.libelle}" déjà associé à deux UE du {msg_parc}"""
|
||||||
log("check_niveau_unique_dans_parcours: " + msg)
|
log(
|
||||||
|
f"check_niveau_unique_dans_parcours(niveau_id={niveau.id}): "
|
||||||
|
+ msg
|
||||||
|
)
|
||||||
return False, msg
|
return False, msg
|
||||||
# s'il y a déjà une UE associée à ce niveau, elle doit être dans l'autre semestre
|
# 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
|
# 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:
|
if ues_meme_niveau[0].semestre_idx != other_semestre_idx:
|
||||||
msg = f"""Niveau "{
|
msg = f"""Niveau "{
|
||||||
niveau.libelle}" associé à une autre année du parcours {code_parcour}"""
|
niveau.libelle}" associé à une autre année du {msg_parc}"""
|
||||||
log("check_niveau_unique_dans_parcours: " + msg)
|
log(
|
||||||
|
f"check_niveau_unique_dans_parcours(niveau_id={niveau.id}): "
|
||||||
|
+ msg
|
||||||
|
)
|
||||||
return False, msg
|
return False, msg
|
||||||
|
|
||||||
return True, ""
|
return True, ""
|
||||||
@ -314,16 +321,30 @@ class UniteEns(db.Model):
|
|||||||
False,
|
False,
|
||||||
"La formation n'est pas associée à un référentiel de compétences",
|
"La formation n'est pas associée à un référentiel de compétences",
|
||||||
)
|
)
|
||||||
if niveau.competence.referentiel.id != self.formation.referentiel_competence.id:
|
if niveau is not None:
|
||||||
return False, "Le niveau n'appartient pas au référentiel de la formation"
|
if self.niveau_competence_id is not None:
|
||||||
if niveau.id == self.niveau_competence_id:
|
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
|
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
|
self.niveau_competence = niveau
|
||||||
db.session.add(self)
|
db.session.add(self)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
@ -64,6 +64,26 @@ div#gtrcontent {
|
|||||||
display: None;
|
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 {
|
div.tab-content {
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
margin-left: 15px;
|
margin-left: 15px;
|
||||||
@ -938,7 +958,7 @@ span.linktitresem a:visited {
|
|||||||
|
|
||||||
a.stdlink,
|
a.stdlink,
|
||||||
a.stdlink:visited {
|
a.stdlink:visited {
|
||||||
color: #0e0e9d;
|
color: blue;
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -177,7 +177,7 @@ function assoc_ue_niveau(event, niveau_id) {
|
|||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.then(data => {
|
.then(data => {
|
||||||
if (data.status) {
|
if (data.status) {
|
||||||
sco_message(data.message);
|
/* sco_message(data.message); */
|
||||||
/* revert menu to initial state */
|
/* revert menu to initial state */
|
||||||
event.target.value = event.target.dataset.ue_id;
|
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:
|
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:
|
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(
|
niveaux = ApcNiveau.niveaux_annee_de_parcours(
|
||||||
parcour, annee, competence=competence
|
parcour, annee, competence=competence
|
||||||
)
|
)
|
||||||
if len(niveaux) > 0:
|
if len(niveaux) > 0:
|
||||||
if len(niveaux) > 1:
|
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]
|
niveau = niveaux[0]
|
||||||
elif len(niveaux) == 0:
|
elif len(niveaux) == 0:
|
||||||
return {"niveau": None, "ue_pair": None, "ue_impair": None}
|
return {
|
||||||
# toutes les UEs de la formation associées à ce niveau
|
"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 = [
|
ues = [
|
||||||
ue
|
ue
|
||||||
for ue in niveau.ues
|
for ue in formation.ues
|
||||||
if ue.formation.id == formation.id
|
if ((not ue.parcours) or (parcour.id in (p.id for p in ue.parcours)))
|
||||||
# and parcour.id in (p.id for p in ue.parcours)
|
and ue.type == UE_STANDARD
|
||||||
]
|
|
||||||
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)
|
|
||||||
]
|
]
|
||||||
|
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 {
|
return {
|
||||||
"niveau": niveau,
|
"niveau": niveau,
|
||||||
"ue_pair": ue_pair,
|
"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,
|
"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 = [
|
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