Update opolka/ScoDoc from ScoDoc/ScoDoc #2

Merged
opolka merged 1272 commits from ScoDoc/ScoDoc:master into master 2024-05-27 09:11:04 +02:00
5 changed files with 122 additions and 122 deletions
Showing only changes of commit 066a24b302 - Show all commits

View File

@ -776,8 +776,10 @@ class DecisionsProposeesAnnee(DecisionsProposees):
# UEs # UEs
for dec_ue in self.decisions_ues.values(): for dec_ue in self.decisions_ues.values():
if ( if (
not dec_ue.recorded dec_ue.formsemestre
) and dec_ue.formsemestre.annee_scolaire() == annee_scolaire: and (not dec_ue.recorded)
and dec_ue.formsemestre.annee_scolaire() == annee_scolaire
):
# rappel: le code par défaut est en tête # rappel: le code par défaut est en tête
code = dec_ue.codes[0] if dec_ue.codes else None 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: 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": ) or self.formsemestre.modalite == "EXT":
self.codes.insert(0, sco_codes.ADM) self.codes.insert(0, sco_codes.ADM)
self.explanation = f"Moyenne >= {sco_codes.CursusBUT.BARRE_MOY}/20" 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.codes.insert(0, sco_codes.CMP)
self.explanation = "compensable dans le RCUE" self.explanation = "compensable dans le RCUE"
else: else:

View File

@ -1090,7 +1090,7 @@ Etudiants:
"S1.2": 12.0000 "S1.2": 12.0000
attendu: # les codes jury que l'on doit vérifier attendu: # les codes jury que l'on doit vérifier
deca: deca:
passage_de_droit: False passage_de_droit: True
nb_competences: 2 nb_competences: 2
nb_rcue_annee: 0 nb_rcue_annee: 0
decisions_ues: decisions_ues:

View File

@ -30,6 +30,26 @@ from config import TestConfig
DEPT = TestConfig.DEPT_TEST 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.slow
@pytest.mark.but_gb @pytest.mark.but_gb
def test_but_jury_GB(test_client): 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 S3
- vérification jury de S1 avec redoublants et capitalisations - vérification jury de S1 avec redoublants et capitalisations
""" """
app.set_sco_dept(DEPT) setup_and_test_jurys("tests/ressources/yaml/cursus_but_gb.yaml")
# 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)
@pytest.mark.slow @pytest.mark.slow
@pytest.mark.lemans @pytest.mark.lemans
def test_but_jury_GMP_lm(test_client): def test_but_jury_GMP_lm(test_client):
"""Tests sur un cursus GMP fourni par Le Mans""" """Tests sur un cursus GMP fourni par Le Mans"""
app.set_sco_dept(DEPT) setup_and_test_jurys("tests/ressources/yaml/cursus_but_gmp_iutlm.yaml")
# 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)
@pytest.mark.slow @pytest.mark.slow
@pytest.mark.lyon @pytest.mark.lyon
def test_but_jury_GEII_lyon(test_client): def test_but_jury_GEII_lyon(test_client):
"""Tests sur un cursus GEII fourni par Lyon""" """Tests sur un cursus GEII fourni par Lyon"""
app.set_sco_dept(DEPT) setup_and_test_jurys("tests/ressources/yaml/cursus_but_geii_lyon.yaml")
# 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)
@pytest.mark.slow @pytest.mark.slow
@ -118,7 +83,14 @@ def test_but_jury_GCCD_CY(test_client):
"""Tests sur un cursus BUT GCCD de S1 à S6""" """Tests sur un cursus BUT GCCD de S1 à S6"""
# WIP # WIP
app.set_sco_dept(DEPT) 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( formsemestres = FormSemestre.query.order_by(
FormSemestre.date_debut, FormSemestre.semestre_id FormSemestre.date_debut, FormSemestre.semestre_id
).all() ).all()

View File

@ -17,7 +17,7 @@ setup_from_yaml()
- import de la formation (le test utilise une seule formation) - import de la formation (le test utilise une seule formation)
- associe_ues_et_parcours(): - associe_ues_et_parcours():
- crée les associations formation <-> référentiel de compétence - crée les associations formation <-> référentiel de compétence
- setup_formsemestres() - setup_formsemestre()
- crée les formsemestres décrits dans le YAML - crée les formsemestres décrits dans le YAML
avec tous les modules du semestre ou des parcours si indiqués avec tous les modules du semestre ou des parcours si indiqués
et une évaluation dans chaque moduleimpl. 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) evaluation.set_ue_poids_dict(ue_coef_dict)
def note_les_modules(doc: dict): def note_les_modules(doc: dict, formsemestre_titre: str = ""):
"""Saisie les notes des étudiants """Saisie les notes des étudiants pour ce formsemestre
doc : données YAML doc : données YAML
""" """
a_user = User.query.first() 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(): for nom, infos in doc["Etudiants"].items():
etud: Identite = Identite.query.filter_by(nom=nom).first() etud: Identite = Identite.query.filter_by(nom=nom).first()
assert etud is not None assert etud is not None
for titre, sem_infos in infos["formsemestres"].items(): sem_infos = infos["formsemestres"].get(formsemestre_titre)
formsemestre: FormSemestre = FormSemestre.query.filter_by( if sem_infos:
titre=titre
).first()
assert formsemestre is not None
for code_module, note in sem_infos.get("notes_modules", {}).items(): for code_module, note in sem_infos.get("notes_modules", {}).items():
modimpl = ( modimpl = (
formsemestre.modimpls.join(Module) formsemestre.modimpls.join(Module)
@ -209,57 +210,69 @@ def note_les_modules(doc: dict):
) )
def setup_formsemestres(formation: Formation, doc: str): def setup_formsemestre(
"""Création des formsemestres formation: Formation, doc: str, formsemestre_titre: str = ""
Le cas échéant associés à leur(s) parcours. ) -> FormSemestre:
"""Création du formsemestre de titre indiqué.
Le cas échéant associé à son parcours.
""" """
for titre, infos in doc["FormSemestres"].items(): infos = doc["FormSemestres"][formsemestre_titre]
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)
_ = create_formsemestre( codes_parcours = infos.get("codes_parcours", [])
formation, assert formation.is_apc() or not codes_parcours # parcours seulement en APC
parcours, parcours = []
infos["idx"], for code_parcour in codes_parcours:
titre, parcour = formation.referentiel_competence.parcours.filter_by(
infos["date_debut"], code=code_parcour
infos["date_fin"], ).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() 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""" """Inscrit les étudiants dans chacun de leurs formsemestres"""
etudiants = doc.get("Etudiants") etudiants = doc.get("Etudiants")
if not etudiants: if not etudiants:
return 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(): for nom, infos in etudiants.items():
etud = Identite.create_etud( # Création des étudiants (sauf si déjà existants)
dept_id=g.scodoc_dept_id, prenom = infos.get("prenom", "prénom")
nom=nom, civilite = infos.get("civilite", "X")
prenom=infos.get("prenom", "prénom"), etud = Identite.query.filter_by(
civilite=infos.get("civilite", "X"), nom=nom, prenom=prenom, civilite=civilite
) ).first()
db.session.add(etud) if etud is None:
db.session.commit() etud = Identite.create_etud(
# L'inscrire à ses formsemestres dept_id=g.scodoc_dept_id,
for titre, sem_infos in infos["formsemestres"].items(): nom=nom,
formsemestre: FormSemestre = FormSemestre.query.filter_by( prenom=prenom,
titre=titre civilite=civilite,
).first() )
assert formsemestre is not None db.session.add(etud)
partition_parcours = formsemestre.partitions.filter_by( db.session.commit()
partition_name=scu.PARTITION_PARCOURS
).first() # 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: if partition_parcours is not None and "parcours" in sem_infos:
group = partition_parcours.groups.filter_by( group = partition_parcours.groups.filter_by(
group_name=sem_infos["parcours"] group_name=sem_infos["parcours"]
@ -278,9 +291,8 @@ def inscrit_les_etudiants(formation: Formation, doc: dict):
etat=scu.INSCRIT, etat=scu.INSCRIT,
method="tests/unit/inscrit_les_etudiants", method="tests/unit/inscrit_les_etudiants",
) )
# Met à jour les inscriptions: # Met à jour les inscriptions:
for formsemestre in formation.formsemestres: formsemestre.update_inscriptions_parcours_from_groups()
formsemestre.update_inscriptions_parcours_from_groups()
def etud_dispense_ues( def etud_dispense_ues(
@ -296,7 +308,7 @@ def etud_dispense_ues(
db.session.add(disp) 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""" """Lit le fichier yaml et construit l'ensemble des objets"""
with open(filename, encoding="utf-8") as f: with open(filename, encoding="utf-8") as f:
doc = yaml.safe_load(f.read()) doc = yaml.safe_load(f.read())
@ -307,9 +319,20 @@ def setup_from_yaml(filename: str) -> dict:
formation = setup_formation(doc["Formation"]) formation = setup_formation(doc["Formation"])
yaml_setup_but.associe_ues_et_parcours(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") etudiants = doc.get("Etudiants")
if etudiants: if etudiants:
inscrit_les_etudiants(formation, doc) inscrit_les_etudiants(doc, formsemestre_titre=formsemestre_titre)
note_les_modules(doc) note_les_modules(doc, formsemestre_titre=formsemestre_titre)
return doc return formsemestre

View File

@ -272,9 +272,7 @@ def check_deca_fields(formsemestre: FormSemestre, etud: Identite = None):
etud = etud or formsemestre.etuds.first() etud = etud or formsemestre.etuds.first()
assert etud # il faut au moins un étudiant dans le semestre assert etud # il faut au moins un étudiant dans le semestre
deca = DecisionsProposeesAnnee(etud, formsemestre) deca = DecisionsProposeesAnnee(etud, formsemestre)
assert deca.validation is None # pas encore de validation enregistrée
assert False is deca.recorded assert False is deca.recorded
assert deca.code_valide is None
parcour = deca.parcour parcour = deca.parcour
formation: Formation = formsemestre.formation formation: Formation = formsemestre.formation
ues = ( ues = (
@ -310,7 +308,7 @@ def check_deca_fields(formsemestre: FormSemestre, etud: Identite = None):
if deca.formsemestre_impair if deca.formsemestre_impair
else 0 else 0
) )
assert len(deca.decisions_ues) == nb_ues assert nb_ues > 0
assert len(deca.niveaux_competences) == len(ues) assert len(deca.niveaux_competences) == len(ues)
assert deca.nb_competences == len(ues) assert deca.nb_competences == len(ues)