ScoDoc/tools/fakedatabase/create_test_api_database.py

477 lines
15 KiB
Python

# -*- coding: utf-8 -*-
"""Initialise une base pour les tests de l'API ScoDoc 9
Création des départements, formations, semestres, étudiants, groupes...
Utilisation: voir tests/api/README.md
"""
import datetime
import os
import random
import shutil
import sys
from app import db
from app.auth.models import Role, User
from app.but.import_refcomp import orebut_import_refcomp
from app import models
from app.formations import formation_io
from app.models import departements
from app.models import (
Absence,
Assiduite,
Departement,
Evaluation,
Formation,
FormSemestre,
FormSemestreEtape,
Identite,
Justificatif,
ModuleImpl,
NotesNotes,
)
from app.scodoc import (
sco_cache,
sco_formsemestre_inscriptions,
sco_groups,
)
from app.scodoc.sco_permissions import Permission
from app.scodoc.sco_utils import localize_datetime
from tools.fakeportal.gen_nomprenoms import nomprenom
random.seed(12345678) # tests reproductibles
# La formation à utiliser:
FORMATION_XML_FILENAME = "tests/ressources/formations/scodoc_formation_BUT_RT_v1.xml"
REFCOMP_FILENAME = (
"ressources/referentiels/but2022/competences/but-RT-05012022-081735.xml"
)
# la réserve de logos
LOGOS_STOCK = "/opt/scodoc/tests/ressources/test_logos/"
LOGOS_DIR = "/opt/scodoc-data/config/logos/"
def create_departements(acronyms: list[str]) -> list[Departement]:
"Create depts"
return [departements.create_dept(acronym) for acronym in acronyms]
def import_formation(dept_id: int) -> Formation:
"""Import formation from XML.
Associate ref. comp.
Returns formation_id
"""
with open(FORMATION_XML_FILENAME, encoding="utf-8") as f:
doc = f.read()
# --- Création de la formation (import programme)
f = formation_io.formation_import_xml(doc)
formation = db.session.get(Formation, f[0])
# --- Association ref. comp.
with open(REFCOMP_FILENAME, encoding="utf-8") as f:
xml_data = f.read()
ref_comp = orebut_import_refcomp(
xml_data, dept_id=dept_id, orig_filename=REFCOMP_FILENAME
)
formation.referentiel_competence_id = ref_comp.id
db.session.add(formation)
# --- Association niveaux de compétences aux UE de S1:
niveaux = ref_comp.get_niveaux_by_parcours(1)[1]["TC"]
ues = formation.ues.filter_by(semestre_idx=1).all()
assert len(niveaux) == len(ues) # le ref comp et les formation doivent correspondre
for ue, niveau in zip(ues, niveaux):
ue.niveau_competence = niveau
db.session.add(ue)
db.session.commit()
return formation
def create_users(depts: list[Departement]) -> tuple:
"""Crée les roles et utilisateurs nécessaires aux tests"""
dept = depts[0]
# Le rôle standard LecteurAPI existe déjà: lui donne les permissions
# ScoView, AbsAddBillet, EtudChangeGroups
role_lecteur = Role.query.filter_by(name="LecteurAPI").first()
if role_lecteur is None:
print("Erreur: rôle LecteurAPI non existant")
sys.exit(1)
perm_sco_view = Permission.get_by_name("ScoView")
role_lecteur.add_permission(perm_sco_view)
perm_sco_users = Permission.get_by_name("UsersView")
role_lecteur.add_permission(perm_sco_users)
# Edition billets
perm_billets = Permission.get_by_name("AbsAddBillet")
role_lecteur.add_permission(perm_billets)
perm_groups = Permission.get_by_name("EtudChangeGroups")
role_lecteur.add_permission(perm_groups)
db.session.add(role_lecteur)
# Un role pour juste voir les utilisateurs
role_users_viewer = Role(
name="UsersViewer", permissions=Permission.UsersView | Permission.ScoView
)
db.session.add(role_users_viewer)
# Role View sur l'API, pour demander un jeton
role_view = Role(name="Viewer", permissions=Permission.ScoView)
db.session.add(role_view)
# Un utilisateur "test" (passwd test) pouvant lire l'API
user_test = User(user_name="test", nom="Doe", prenom="John", dept=dept.acronym)
user_test.set_password("test")
db.session.add(user_test)
user_test.add_role(role_lecteur, None)
# Un utilisateur "other" n'ayant aucune permission sur l'API
other = User(user_name="other", nom="Sans", prenom="Permission", dept=dept.acronym)
other.set_password("other")
db.session.add(other)
# Un utilisateur "admin_api"
admin_api = User(user_name="admin_api", nom="Admin", prenom="API")
admin_api.set_password("admin_api")
db.session.add(admin_api)
role_super_admin = Role.query.filter_by(name="SuperAdmin").first()
if role_super_admin is None:
print("Erreur: rôle SuperAdmin non existant")
sys.exit(1)
admin_api.add_role(role_super_admin, None)
# Des utilisateurs voyant certains utilisateurs...
users = []
for dept in depts:
u = User(
user_name=f"u_{dept.acronym}",
nom=f"U {dept.acronym}",
prenom="lambda",
dept=dept.acronym,
)
u.set_password("test")
users.append(u)
db.session.add(u)
# Roles pour tester les fonctions sur les utilisateurs
for i, u in enumerate(users):
for dept in depts[: i + 1]:
u.add_role(role_users_viewer, dept.acronym)
u.add_role(role_view, None) # necessaire pour avoir le jeton
db.session.commit()
return user_test, other
def create_fake_etud(dept: Departement) -> Identite:
"""Créé un faux étudiant et l'insère dans la base."""
civilite = random.choice(("M", "F", "X"))
nom, prenom = nomprenom(civilite)
etud: Identite = Identite.create_etud(
civilite=civilite, nom=nom, prenom=prenom, dept_id=dept.id
)
db.session.add(etud)
db.session.commit()
# créé un étudiant sur deux avec un NIP et INE alphanumérique
etud.code_nip = f"{etud.id}" if (etud.id % 2) else f"NIP{etud.id}"
etud.code_ine = f"INE{etud.id}" if (etud.id % 2) else f"{etud.id}"
etud.date_naissance = datetime.date(2005, 2, 1) + datetime.timedelta(days=etud.id)
etud.adresse = [models.Adresse(email=f"{etud.prenom}.{etud.nom}@example.com")]
db.session.add(etud)
db.session.commit()
return etud
def create_etuds(dept: Departement, nb=16) -> list:
"create nb etuds"
return [create_fake_etud(dept) for _ in range(nb)]
def create_formsemestre(
formation: Formation, responsable: User, semestre_idx=1
) -> FormSemestre:
"""Create formsemestre and moduleimpls
responsable: resp. du formsemestre
"""
formsemestre = FormSemestre(
dept_id=formation.dept_id,
semestre_id=semestre_idx,
titre="Semestre test",
date_debut=datetime.datetime(2021, 9, 1),
date_fin=datetime.datetime(2022, 8, 31),
modalite="FI",
formation=formation,
)
db.session.add(formsemestre)
db.session.commit()
# Crée un modulimpl par module de ce semestre:
for module in formation.modules.filter_by(semestre_id=semestre_idx):
modimpl = models.ModuleImpl(
module_id=module.id,
formsemestre_id=formsemestre.id,
responsable_id=responsable.id,
)
db.session.add(modimpl)
db.session.commit()
# Partition par défaut (requise):
partition_id = sco_groups.partition_create(
formsemestre.id, default=True, redirect=False
)
sco_groups.create_group(partition_id, default=True)
# Ajoute partition normale, TD avec groupes A et B:
partition_id = sco_groups.partition_create(
formsemestre.id, partition_name="TD", redirect=False
)
sco_groups.create_group(partition_id, group_name="A")
sco_groups.create_group(partition_id, group_name="B")
return formsemestre
def inscrit_etudiants(etuds: list, formsemestre: FormSemestre):
"""Inscrit les étudiants au semestre et à tous ses modules.
1/5 DEF, 1/5 DEF
"""
for i, etud in enumerate(etuds):
if (i + 1) % 5 == 0:
etat = "D"
elif (i + 2) % 5 == 0:
etat = "DEF"
else:
etat = "I"
sco_formsemestre_inscriptions.do_formsemestre_inscription_with_modules(
formsemestre.id,
etud.id,
group_ids=[],
etat=etat,
method="init db test",
)
def create_evaluations(formsemestre: FormSemestre):
"Création d'une evaluation dans chaque modimpl du semestre"
for moduleimpl in formsemestre.modimpls:
args = {
"jour": datetime.date(2022, 3, 1)
+ datetime.timedelta(days=moduleimpl.id), # TODO à changer
"heure_debut": "8h00",
"heure_fin": "9h00",
"description": f"Evaluation-{moduleimpl.module.code}",
"note_max": 20,
"coefficient": 1.0,
"visibulletin": True,
"publish_incomplete": True,
"evaluation_type": None,
"numero": None,
}
evaluation = Evaluation.create(moduleimpl=moduleimpl, **args)
db.session.add(evaluation)
db.session.commit()
def saisie_notes_evaluations(formsemestre: FormSemestre, user: User):
"""
Saisie les notes des evaluations d'un semestre
"""
etuds = formsemestre.etuds
list_etuds = []
for etu in etuds:
list_etuds.append(etu)
date_debut = formsemestre.date_debut
date_fin = formsemestre.date_fin
list_ues = formsemestre.get_ues()
def saisir_notes(evaluation_id: int, condition: int):
"""
Permet de saisir les notes de manière aléatoire suivant une condition
Définition des valeurs de condition :
0 : all_notes_saisies
1 : all_notes_manquantes
2 : some_notes_manquantes
"""
if condition == 2:
return # nothing to do
if condition == 0:
list_a_saisir = list_etuds
else:
percent = 80 / 100
len_etuds = len(list_etuds)
list_a_saisir = random.sample(list_etuds, k=int(percent * len_etuds))
for etud in list_a_saisir:
note = NotesNotes(
etudid=etud.id,
evaluation_id=evaluation_id,
value=random.uniform(0, 20),
comment="",
date=date_debut + random.random() * (date_fin - date_debut),
uid=user.id,
)
db.session.add(note)
db.session.commit()
for ue in list_ues:
mods = ue.modules
for mod in mods:
moduleimpl = ModuleImpl.query.get_or_404(mod.id)
for evaluation in moduleimpl.evaluations:
condition_saisie_notes = random.randint(0, 2)
saisir_notes(evaluation.id, condition_saisie_notes)
def add_absences(formsemestre: FormSemestre):
"""
Ajoute des absences en base
"""
date_debut = formsemestre.date_debut
date_fin = formsemestre.date_fin
etuds = formsemestre.etuds
for etu in etuds:
aleatoire = random.randint(0, 1)
if aleatoire == 1:
nb_absences = random.randint(1, 5)
for absence in range(0, nb_absences):
etudid = etu.id
jour = date_debut + random.random() * (date_fin - date_debut)
estabs = True
estjust = True if random.randint(0, 1) == 1 else False
matin = True if random.randint(0, 1) == 1 else False
description = ""
absence = Absence(
etudid=etudid,
jour=jour,
estabs=estabs,
estjust=estjust,
matin=matin,
description=description,
)
db.session.add(absence)
db.session.commit()
def create_etape_apo(formsemestre: FormSemestre):
"""
Ajoute étapes Apogée au formsemestre
"""
etape_apo1 = FormSemestreEtape(
id=1, formsemestre_id=formsemestre.id, etape_apo="A1"
)
db.session.add(etape_apo1)
etape_apo2 = FormSemestreEtape(
id=2, formsemestre_id=formsemestre.id, etape_apo="A2"
)
db.session.add(etape_apo2)
etape_apo3 = FormSemestreEtape(
id=3, formsemestre_id=formsemestre.id, etape_apo="A3"
)
db.session.add(etape_apo3)
list_etapes = [etape_apo1, etape_apo2, etape_apo3]
formsemestre.etapes = list_etapes
db.session.commit()
def create_logos():
if not os.path.exists(LOGOS_DIR + "logos_1"):
os.mkdir(LOGOS_DIR + "logos_1")
shutil.copy(
LOGOS_STOCK + "logo_A.jpg",
LOGOS_DIR + "logos_1/logo_A.jpg",
)
shutil.copy(
LOGOS_STOCK + "logo_D.png",
LOGOS_DIR + "logos_1/logo_D.png",
)
shutil.copy(
LOGOS_STOCK + "logo_A.jpg",
LOGOS_DIR + "logo_B.jpg",
)
shutil.copy(
LOGOS_STOCK + "logo_D.png",
LOGOS_DIR + "logo_C.png",
)
def ajouter_assiduites_justificatifs(formsemestre: FormSemestre):
"""
Ajoute des assiduités semi-aléatoires à chaque étudiant du semestre
"""
MODS = [moduleimpl for moduleimpl in formsemestre.modimpls]
MODS.append(None)
for etud in formsemestre.etuds:
# Se base sur la date des évaluations
base_date = datetime.datetime(
2022, 3, [1, 8, 15, 22, 29][random.randint(0, 4)], 8, 0, 0
)
base_date = localize_datetime(base_date)
for i in range(random.randint(1, 4)):
etat = random.randint(0, 2)
moduleimpl = random.choice(MODS)
deb_date = base_date + datetime.timedelta(days=i)
fin_date = deb_date + datetime.timedelta(hours=i)
code = Assiduite.create_assiduite(
etud, deb_date, fin_date, etat, moduleimpl
)
assert isinstance(
code, Assiduite
), "Erreur dans la génération des assiduités"
db.session.add(code)
for i in range(random.randint(0, 2)):
etat = random.randint(0, 3)
deb_date = base_date + datetime.timedelta(days=i)
fin_date = deb_date + datetime.timedelta(hours=8)
raison = random.choice(["raison", None])
code = Justificatif.create_justificatif(
etud,
deb_date,
fin_date,
etat=etat,
raison=raison,
)
assert isinstance(
code, Justificatif
), "Erreur dans la génération des justificatifs"
db.session.add(code)
db.session.commit()
def init_test_database():
"""Appelé par la commande `flask init-test-database`
Création d'un département et de son contenu pour les tests
"""
import app as mapp
depts = create_departements(["TAPI", "AA", "BB", "CC", "DD"])
dept = depts[0]
mapp.set_sco_dept(dept.acronym)
user_lecteur, user_autre = create_users(depts)
with sco_cache.DeferredSemCacheManager():
etuds = create_etuds(dept, nb=20)
formation = import_formation(dept.id)
formsemestre = create_formsemestre(formation, user_lecteur)
create_evaluations(formsemestre)
inscrit_etudiants(etuds[:16], formsemestre)
saisie_notes_evaluations(formsemestre, user_lecteur)
add_absences(formsemestre)
create_etape_apo(formsemestre)
ajouter_assiduites_justificatifs(formsemestre)
create_logos()
# à compléter
# - groupes
# - absences
# - notes
# - décisions de jury
# ...