# -*- 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.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 # ...