forked from ScoDoc/DocScoDoc
332 lines
12 KiB
Python
332 lines
12 KiB
Python
|
"""
|
||
|
Met en place une base pour les tests, à partir d'une description YAML
|
||
|
qui peut donner la formation, son ref. compétences, les formsemestres,
|
||
|
les étudiants et leurs notes.
|
||
|
"""
|
||
|
|
||
|
import os
|
||
|
from pathlib import Path
|
||
|
import re
|
||
|
|
||
|
import yaml
|
||
|
from flask import current_app, g
|
||
|
|
||
|
from app import db
|
||
|
|
||
|
from app.auth.models import User
|
||
|
from app.but.import_refcomp import orebut_import_refcomp
|
||
|
from app.models import (
|
||
|
ApcNiveau,
|
||
|
ApcParcours,
|
||
|
ApcReferentielCompetences,
|
||
|
Evaluation,
|
||
|
Formation,
|
||
|
FormSemestre,
|
||
|
Identite,
|
||
|
Module,
|
||
|
ModuleImpl,
|
||
|
ModuleUECoef,
|
||
|
UniteEns,
|
||
|
)
|
||
|
|
||
|
from app.scodoc import sco_formations
|
||
|
from app.scodoc import sco_formsemestre_inscriptions
|
||
|
from app.scodoc import sco_groups
|
||
|
from app.scodoc import sco_saisie_notes
|
||
|
from app.scodoc import sco_utils as scu
|
||
|
|
||
|
from tests.conftest import RESOURCES_DIR
|
||
|
|
||
|
|
||
|
def setup_but_formation(formation_infos: dict) -> Formation:
|
||
|
"""Importe la formation, qui est lue à partir du fichier XML
|
||
|
formation_infos["filename"].
|
||
|
La formation peut être quelconque, on vérifie juste qu'elle est bien créée.
|
||
|
"""
|
||
|
# Lecture fichier XML local:
|
||
|
with open(
|
||
|
os.path.join(RESOURCES_DIR, "formations", formation_infos["filename"]),
|
||
|
encoding="utf-8",
|
||
|
) as f:
|
||
|
doc = f.read()
|
||
|
|
||
|
# --- Création de la formation
|
||
|
formation_id, _, _ = sco_formations.formation_import_xml(doc)
|
||
|
formation: Formation = Formation.query.get(formation_id)
|
||
|
assert formation
|
||
|
return formation
|
||
|
|
||
|
|
||
|
def setup_formation_referentiel(formation: Formation, refcomp_infos: dict):
|
||
|
"""Associe la formation au référentiel de compétences"""
|
||
|
if not refcomp_infos:
|
||
|
return
|
||
|
refcomp_filename = refcomp_infos["filename"]
|
||
|
refcomp_specialite = refcomp_infos["specialite"]
|
||
|
# --- Chargement Référentiel
|
||
|
if (
|
||
|
ApcReferentielCompetences.query.filter_by(
|
||
|
scodoc_orig_filename=refcomp_filename, dept_id=g.scodoc_dept_id
|
||
|
).first()
|
||
|
is None
|
||
|
):
|
||
|
# pas déjà chargé
|
||
|
filename = (
|
||
|
Path(current_app.config["SCODOC_DIR"])
|
||
|
/ "ressources/referentiels/but2022/competences"
|
||
|
/ refcomp_filename
|
||
|
)
|
||
|
with open(filename, encoding="utf-8") as f:
|
||
|
xml_data = f.read()
|
||
|
referentiel_competence = orebut_import_refcomp(
|
||
|
xml_data, dept_id=g.scodoc_dept_id, orig_filename=Path(filename).name
|
||
|
)
|
||
|
assert referentiel_competence
|
||
|
|
||
|
# --- Association au référentiel de compétences
|
||
|
referentiel_competence = ApcReferentielCompetences.query.filter_by(
|
||
|
specialite=refcomp_specialite
|
||
|
).first() # le recherche à nouveau (test)
|
||
|
assert referentiel_competence
|
||
|
formation.referentiel_competence_id = referentiel_competence.id
|
||
|
db.session.add(formation)
|
||
|
|
||
|
|
||
|
def associe_ues_et_parcours(formation: Formation, formation_infos: dict):
|
||
|
"""Associe les UE et modules de la formation aux parcours du ref. comp."""
|
||
|
referentiel_competence = formation.referentiel_competence
|
||
|
if not referentiel_competence:
|
||
|
return
|
||
|
# --- Association des UEs aux parcours niveaux de compétences
|
||
|
for ue_acronyme, ue_infos in formation_infos["ues"].items():
|
||
|
ue: UniteEns = formation.ues.filter_by(acronyme=ue_acronyme).first()
|
||
|
assert ue is not None # l'UE doit exister dans la formation avec cet acronyme
|
||
|
# Parcours:
|
||
|
if ue_infos.get("parcours", False):
|
||
|
parcour = referentiel_competence.parcours.filter_by(
|
||
|
code=ue_infos["parcours"]
|
||
|
).first()
|
||
|
assert parcour is not None # le parcours indiqué pour cette UE doit exister
|
||
|
ue.set_parcour(parcour)
|
||
|
|
||
|
# Niveaux compétences:
|
||
|
competence = referentiel_competence.competences.filter_by(
|
||
|
titre=ue_infos["competence"]
|
||
|
).first()
|
||
|
assert competence is not None # La compétence de titre indiqué doit exister
|
||
|
niveau: ApcNiveau = competence.niveaux.filter_by(
|
||
|
annee=ue_infos["annee"]
|
||
|
).first()
|
||
|
assert niveau is not None # le niveau de l'année indiquée doit exister
|
||
|
ue.set_niveau_competence(niveau)
|
||
|
|
||
|
db.session.commit()
|
||
|
associe_modules_et_parcours(formation, formation_infos)
|
||
|
|
||
|
|
||
|
def associe_modules_et_parcours(formation: Formation, formation_infos: dict):
|
||
|
"""Associe les modules à des parcours, grâce au champ modules_parcours"""
|
||
|
for code_parcours, codes_modules in formation_infos.get(
|
||
|
"modules_parcours", {}
|
||
|
).items():
|
||
|
parcour = formation.referentiel_competence.parcours.filter_by(
|
||
|
code=code_parcours
|
||
|
).first()
|
||
|
assert parcour is not None # code parcours doit exister dans le ref. comp.
|
||
|
for code_module in codes_modules:
|
||
|
for module in [
|
||
|
module
|
||
|
for module in formation.modules
|
||
|
if re.match(code_module, module.code)
|
||
|
]:
|
||
|
module.parcours.append(parcour)
|
||
|
db.session.add(module)
|
||
|
db.session.commit()
|
||
|
|
||
|
|
||
|
def _un_semestre(
|
||
|
formation: Formation,
|
||
|
parcours: list[ApcParcours],
|
||
|
semestre_id: int,
|
||
|
titre: str,
|
||
|
date_debut: str,
|
||
|
date_fin: str,
|
||
|
) -> FormSemestre:
|
||
|
"Création d'un formsemetre"
|
||
|
formsemestre = FormSemestre(
|
||
|
formation=formation,
|
||
|
parcours=parcours,
|
||
|
dept_id=g.scodoc_dept_id,
|
||
|
titre=titre,
|
||
|
semestre_id=semestre_id,
|
||
|
date_debut=date_debut,
|
||
|
date_fin=date_fin,
|
||
|
)
|
||
|
# set responsable (list)
|
||
|
a_user = User.query.first()
|
||
|
formsemestre.responsables = [a_user]
|
||
|
db.session.add(formsemestre)
|
||
|
# Ajoute pour chaque UE une ressource avec un coef vers cette UE
|
||
|
added_ressources = set()
|
||
|
for parcour in parcours + [None]:
|
||
|
for ue in formation.query_ues_parcour(parcour):
|
||
|
ressource = (
|
||
|
Module.query.filter_by(
|
||
|
formation=formation,
|
||
|
semestre_id=1,
|
||
|
module_type=scu.ModuleType.RESSOURCE,
|
||
|
)
|
||
|
.join(ModuleUECoef)
|
||
|
.filter_by(ue=ue)
|
||
|
.first()
|
||
|
)
|
||
|
if ressource is not None:
|
||
|
if ressource.id not in added_ressources:
|
||
|
modimpl = ModuleImpl(module=ressource, responsable_id=a_user.id)
|
||
|
db.session.add(modimpl)
|
||
|
formsemestre.modimpls.append(modimpl)
|
||
|
added_ressources.add(ressource.id)
|
||
|
|
||
|
# Ajoute la première SAE
|
||
|
sae = formation.modules.filter_by(
|
||
|
semestre_id=1, module_type=scu.ModuleType.SAE
|
||
|
).first()
|
||
|
modimpl = ModuleImpl(module=sae, responsable_id=a_user.id)
|
||
|
formsemestre.modimpls.append(modimpl)
|
||
|
# Crée une évaluation dans chaque module
|
||
|
create_evaluations(formsemestre)
|
||
|
|
||
|
# Partition par défaut:
|
||
|
db.session.commit()
|
||
|
partition_id = sco_groups.partition_create(
|
||
|
formsemestre.id, default=True, redirect=False
|
||
|
)
|
||
|
_ = sco_groups.create_group(partition_id, default=True)
|
||
|
# Partition de parcours:
|
||
|
formsemestre.setup_parcours_groups()
|
||
|
|
||
|
return formsemestre
|
||
|
|
||
|
|
||
|
def create_evaluations(formsemestre: FormSemestre):
|
||
|
"""Crée une évaluation dans chaque module du semestre"""
|
||
|
for modimpl in formsemestre.modimpls:
|
||
|
evaluation = Evaluation(
|
||
|
moduleimpl=modimpl,
|
||
|
jour=formsemestre.date_debut,
|
||
|
description=f"Exam {modimpl.module.titre}",
|
||
|
coefficient=1.0,
|
||
|
note_max=20.0,
|
||
|
numero=1,
|
||
|
)
|
||
|
db.session.add(evaluation)
|
||
|
# Affecte les mêmes poids que les coefs APC:
|
||
|
ue_coef_dict = modimpl.module.get_ue_coef_dict() # { ue_id : coef }
|
||
|
evaluation.set_ue_poids_dict(ue_coef_dict)
|
||
|
|
||
|
|
||
|
def note_les_modules(doc: dict):
|
||
|
"""Saisie les notes des étudiants"""
|
||
|
a_user = User.query.first()
|
||
|
for nom, infos in doc["Etudiants"].items():
|
||
|
etud: Identite = Identite.query.filter_by(nom=nom).first()
|
||
|
assert etud is not None
|
||
|
for titre, sem_infos in infos["formsemestres"].items():
|
||
|
formsemestre: FormSemestre = FormSemestre.query.filter_by(
|
||
|
titre=titre
|
||
|
).first()
|
||
|
assert formsemestre is not None
|
||
|
for code_module, note in sem_infos.get("notes_modules", {}).items():
|
||
|
modimpl = (
|
||
|
formsemestre.modimpls.join(Module)
|
||
|
.filter_by(code=code_module)
|
||
|
.first()
|
||
|
)
|
||
|
# le sem. doit avoir un module du code indiqué:
|
||
|
assert modimpl is not None
|
||
|
for evaluation in modimpl.evaluations:
|
||
|
# s'il y a plusieurs evals, affecte la même note à chacune
|
||
|
sco_saisie_notes.notes_add(
|
||
|
a_user,
|
||
|
evaluation.id,
|
||
|
[(etud.id, float(note))],
|
||
|
comment="note_les_modules",
|
||
|
)
|
||
|
|
||
|
|
||
|
def setup_formsemestres(formation: Formation, doc: str):
|
||
|
"""Création des formsemestres pour tester les parcours BUT"""
|
||
|
for titre, infos in doc["FormSemestres"].items():
|
||
|
parcours = []
|
||
|
for code_parcour in infos["codes_parcours"]:
|
||
|
parcour = formation.referentiel_competence.parcours.filter_by(
|
||
|
code=code_parcour
|
||
|
).first()
|
||
|
assert parcour is not None
|
||
|
parcours.append(parcour)
|
||
|
_ = _un_semestre(
|
||
|
formation,
|
||
|
parcours,
|
||
|
infos["idx"],
|
||
|
titre,
|
||
|
infos["date_debut"],
|
||
|
infos["date_fin"],
|
||
|
)
|
||
|
|
||
|
db.session.flush()
|
||
|
assert FormSemestre.query.count() == len(doc["FormSemestres"])
|
||
|
|
||
|
|
||
|
def inscrit_les_etudiants(formation: Formation, doc: dict):
|
||
|
"""Inscrit les étudiants dans chacun de leurs formsemestres"""
|
||
|
for nom, infos in doc["Etudiants"].items():
|
||
|
etud = Identite.create_etud(
|
||
|
dept_id=g.scodoc_dept_id,
|
||
|
nom=nom,
|
||
|
prenom=infos["prenom"],
|
||
|
civilite=infos["civilite"],
|
||
|
)
|
||
|
db.session.add(etud)
|
||
|
db.session.commit()
|
||
|
# L'inscrire à ses formsemestres
|
||
|
for titre, sem_infos in infos["formsemestres"].items():
|
||
|
formsemestre: FormSemestre = FormSemestre.query.filter_by(
|
||
|
titre=titre
|
||
|
).first()
|
||
|
assert formsemestre is not None
|
||
|
partition_parcours = formsemestre.partitions.filter_by(
|
||
|
partition_name=scu.PARTITION_PARCOURS
|
||
|
).first()
|
||
|
if partition_parcours is None:
|
||
|
group_ids = []
|
||
|
else:
|
||
|
group = partition_parcours.groups.filter_by(
|
||
|
group_name=sem_infos["parcours"]
|
||
|
).first()
|
||
|
assert group is not None # le groupe de parcours doit exister
|
||
|
group_ids = [group.id]
|
||
|
sco_formsemestre_inscriptions.do_formsemestre_inscription_with_modules(
|
||
|
formsemestre.id,
|
||
|
etud.id,
|
||
|
group_ids=group_ids,
|
||
|
etat=scu.INSCRIT,
|
||
|
method="tests/unit/inscrit_les_etudiants",
|
||
|
)
|
||
|
# Met à jour les inscriptions:
|
||
|
for formsemestre in formation.formsemestres:
|
||
|
formsemestre.update_inscriptions_parcours_from_groups()
|
||
|
|
||
|
|
||
|
def setup_from_yaml(filename: str) -> dict:
|
||
|
"""Lit le fichier yaml et construit l'ensemble des objets"""
|
||
|
with open(filename, encoding="utf-8") as f:
|
||
|
doc = yaml.safe_load(f.read())
|
||
|
|
||
|
formation = setup_but_formation(doc["Formation"])
|
||
|
setup_formation_referentiel(formation, doc.get("ReferentielCompetences", {}))
|
||
|
associe_ues_et_parcours(formation, doc["Formation"])
|
||
|
setup_formsemestres(formation, doc)
|
||
|
inscrit_les_etudiants(formation, doc)
|
||
|
note_les_modules(doc)
|
||
|
return doc
|