From aba6330065c1691c2cbcd057175648f3ba36bad9 Mon Sep 17 00:00:00 2001 From: leonard_montalbano Date: Thu, 28 Apr 2022 16:12:15 +0200 Subject: [PATCH 01/32] ajout des tests pour la version 'long' de etudiant_courant + correction routes et doc sur les fichiers api --- app/api/absences.py | 8 +- app/api/etudiants.py | 2 +- app/api/evaluations.py | 42 +++++------ app/api/formations.py | 10 +-- app/api/formsemestres.py | 29 ++++++++ app/api/partitions.py | 6 +- tests/api/test_api_etudiants.py | 128 ++++++++++++++------------------ 7 files changed, 119 insertions(+), 106 deletions(-) diff --git a/app/api/absences.py b/app/api/absences.py index 35abe34ff..35056a05b 100644 --- a/app/api/absences.py +++ b/app/api/absences.py @@ -93,7 +93,7 @@ def absences_just(etudid: int = None, nip: int = None, ine: int = None): "jour": "Fri, 15 Apr 2022 00:00:00 GMT", "matin": false, "estabs": true, - "estjust": false, + "estjust": true, "description": "", "begin": "2022-04-15 12:00:00", "end": "2022-04-15 17:59:59" @@ -120,7 +120,11 @@ def absences_just(etudid: int = None, nip: int = None, ine: int = None): @bp.route( - "/absences/abs_group_etat/?group_id=&date_debut=date_debut&date_fin=date_fin", + "/absences/abs_group_etat/", + methods=["GET"], +) +@bp.route( + "/absences/abs_group_etat/group_id//date_debut//date_fin/", methods=["GET"], ) @token_permission_required(Permission.APIView) diff --git a/app/api/etudiants.py b/app/api/etudiants.py index 1f22463ec..4cab02a09 100644 --- a/app/api/etudiants.py +++ b/app/api/etudiants.py @@ -59,7 +59,7 @@ def etudiants_courant(long=False): data = [etud.to_dict_bul(include_urls=False) for etud in etuds] else: data = [etud.to_dict_short() for etud in etuds] - + print(jsonify(data)) return jsonify(data) diff --git a/app/api/evaluations.py b/app/api/evaluations.py index 2f6049d99..4cbfa9f33 100644 --- a/app/api/evaluations.py +++ b/app/api/evaluations.py @@ -62,27 +62,27 @@ def evaluation_notes(evaluation_id: int): evaluation_id : l'id d'une évaluation Exemple de résultat : - { - "1": { - "id": 1, - "etudid": 10, - "evaluation_id": 1, - "value": 15.0, - "comment": "", - "date": "Wed, 20 Apr 2022 06:49:05 GMT", - "uid": 2 - }, - "2": { - "id": 2, - "etudid": 1, - "evaluation_id": 1, - "value": 12.0, - "comment": "", - "date": "Wed, 20 Apr 2022 06:49:06 GMT", - "uid": 2 - }, - ... - } + { + "1": { + "id": 1, + "etudid": 10, + "evaluation_id": 1, + "value": 15.0, + "comment": "", + "date": "Wed, 20 Apr 2022 06:49:05 GMT", + "uid": 2 + }, + "2": { + "id": 2, + "etudid": 1, + "evaluation_id": 1, + "value": 12.0, + "comment": "", + "date": "Wed, 20 Apr 2022 06:49:06 GMT", + "uid": 2 + }, + ... + } """ # Fonction utilisée : app.scodoc.sco_evaluation_db.do_evaluation_get_all_notes() diff --git a/app/api/formations.py b/app/api/formations.py index f7d1a6424..ee017514d 100644 --- a/app/api/formations.py +++ b/app/api/formations.py @@ -177,7 +177,7 @@ def formation_export_by_formation_id(formation_id: int, export_ids=False): try: # Utilisation de la fonction formation_export - data = formation_export(formation_id) + data = formation_export(formation_id, export_ids) except ValueError: return error_response( 409, @@ -192,15 +192,15 @@ def formation_export_by_formation_id(formation_id: int, export_ids=False): @token_permission_required(Permission.APIView) def moduleimpls(moduleimpl_id: int): """ - Retourne la liste des moduleimpl + Retourne un module moduleimpl en fonction de son id moduleimpl_id : l'id d'un moduleimpl """ # Récupération des tous les moduleimpl - list_moduleimpls = models.ModuleImpl.query.filter_by(id=moduleimpl_id) + moduleimpl = models.ModuleImpl.query.filter_by(id=moduleimpl_id).first_or_404() # Mise en forme des données - data = [moduleimpl.to_dict() for moduleimpl in list_moduleimpls] + data = moduleimpl.to_dict() return jsonify(data) @@ -208,7 +208,7 @@ def moduleimpls(moduleimpl_id: int): @bp.route( "/formations/moduleimpl/formsemestre//liste", methods=["GET"], -) # XXX TODO penser à changer la route sur la doc +) @token_permission_required(Permission.APIView) def moduleimpls_sem(formsemestre_id: int): """ diff --git a/app/api/formsemestres.py b/app/api/formsemestres.py index 9704d753c..4ab86132e 100644 --- a/app/api/formsemestres.py +++ b/app/api/formsemestres.py @@ -68,6 +68,35 @@ def formsemestre_apo(etape_apo: str): Retourne les informations sur les formsemestres etape_apo : l'id d'une étape apogée + + Exemple de résultat : + { + "date_fin": "31/08/2022", + "resp_can_edit": false, + "dept_id": 1, + "etat": true, + "resp_can_change_ens": true, + "id": 1, + "modalite": "FI", + "ens_can_edit_eval": false, + "formation_id": 1, + "gestion_compensation": false, + "elt_sem_apo": null, + "semestre_id": 1, + "bul_hide_xml": false, + "elt_annee_apo": null, + "titre": "Semestre test", + "block_moyennes": false, + "scodoc7_id": null, + "date_debut": "01/09/2021", + "gestion_semestrielle": false, + "bul_bgcolor": "white", + "formsemestre_id": 1, + "titre_num": "Semestre test semestre 1", + "date_debut_iso": "2021-09-01", + "date_fin_iso": "2022-08-31", + "responsables": [] + } """ formsemestres = FormSemestre.query.filter( FormSemestreEtape.etape_apo == etape_apo, diff --git a/app/api/partitions.py b/app/api/partitions.py index 2af2f7cf7..d925d1749 100644 --- a/app/api/partitions.py +++ b/app/api/partitions.py @@ -121,13 +121,13 @@ def etud_in_group(group_id: int, etat=None): @bp.route( - "/partitions/set_groups?partition_id=&groups_lists=&" - "groups_to_create=&groups_to_delete=", + "/partitions/set_groups/partition//groups//delete/" + "/create/", methods=["POST"], ) @token_permission_required(Permission.APIEtudChangeGroups) def set_groups( - partition_id: int, groups_lists: int, groups_to_delete: int, groups_to_create: int + partition_id: int, groups_lists: str, groups_to_delete: str, groups_to_create: str ): """ Set les groups diff --git a/tests/api/test_api_etudiants.py b/tests/api/test_api_etudiants.py index e568a4f9b..6ebecbc7a 100644 --- a/tests/api/test_api_etudiants.py +++ b/tests/api/test_api_etudiants.py @@ -23,85 +23,16 @@ import requests from tests.api.setup_test_api import SCODOC_URL, CHECK_CERTIFICATE, HEADERS -# # etudiants -# def test_etudiants(): -# -# fields = [ -# "civilite", -# "code_ine", -# "code_nip", -# "date_naissance", -# "email", -# "emailperso", -# "etudid", -# "nom", -# "prenom", -# "nomprenom", -# "lieu_naissance", -# "dept_naissance", -# "nationalite", -# "boursier", -# "id", -# "domicile", -# "villedomicile", -# "telephone", -# "fax", -# "description", -# "codepostaldomicile", -# "paysdomicile", -# "telephonemobile", -# "typeadresse", -# ] -# -# r = requests.get( -# SCODOC_URL + "/ScoDoc/api/etudiants", -# headers=HEADERS, -# verify=CHECK_CERTIFICATE, -# ) -# assert r.status_code == 200 -# assert len(r.json()) == 16 -# -# # Choisis aléatoirement un étudiant dans la liste des étudiants -# etu = r.json()[randint(0, len(r.json())) - 1] -# -# fields_OK = True -# -# # Vérifie si tous les champs sont bien présents -# for field in etu: -# if field not in fields: -# fields_OK = False -# -# assert fields_OK is True - - # etudiants_courant def test_etudiants_courant(): # XXX TODO pour Seb fields = [ - "civilite", - "code_ine", - "code_nip", - "date_naissance", - "email", - "emailperso", - "etudid", - "nom", - "prenom", - "nomprenom", - "lieu_naissance", - "dept_naissance", - "nationalite", - "boursier", "id", - "domicile", - "villedomicile", - "telephone", - "fax", - "description", - "codepostaldomicile", - "paysdomicile", - "telephonemobile", - "typeadresse", + "nip", + "nom", + "nom_usuel", + "prenom", + "civilite", ] r = requests.get( @@ -124,6 +55,55 @@ def test_etudiants_courant(): # XXX TODO pour Seb assert fields_OK is True + ########## Version long################ + + fields_long = [ + "civilite", + "code_ine", + "code_nip", + "date_naissance", + "email", + "emailperso", + "etudid", + "nom", + "prenom", + "nomprenom", + "lieu_naissance", + "dept_naissance", + "nationalite", + "boursier", + "id", + "codepostaldomicile", + "paysdomicile", + "telephonemobile", + "typeadresse", + "domicile", + "villedomicile", + "telephone", + "fax", + "description", + ] + + r = requests.get( + SCODOC_URL + "/ScoDoc/api/etudiants/courant/long", + headers=HEADERS, + verify=CHECK_CERTIFICATE, + ) + assert r.status_code == 200 + assert len(r.json()) == 16 + + # Choisis aléatoirement un étudiant dans la liste des étudiants + etu = r.json()[randint(0, len(r.json())) - 1] + + fields_OK = True + + # Vérifie si tous les champs sont bien présents + for field in etu: + if field not in fields_long: + fields_OK = False + + assert fields_OK is True + # etudiant def test_etudiant(): # XXX TODO pour Seb From bac99da7263f79d0ff6ed4afe4677354d7eb3ac6 Mon Sep 17 00:00:00 2001 From: leonard_montalbano Date: Mon, 2 May 2022 09:38:41 +0200 Subject: [PATCH 02/32] fixe du format de retour de etudiant_formsemestre et formsemestre --- app/api/etudiants.py | 58 ++++++++-------- app/api/formsemestres.py | 142 ++++++++++++++++++++++++++++++++++++--- 2 files changed, 159 insertions(+), 41 deletions(-) diff --git a/app/api/etudiants.py b/app/api/etudiants.py index 4cab02a09..9f40cf6b5 100644 --- a/app/api/etudiants.py +++ b/app/api/etudiants.py @@ -111,36 +111,34 @@ def etudiant_formsemestres(etudid: int = None, nip: int = None, ine: int = None) Exemple de résultat : [ - { - "titre": "master machine info", - "gestion_semestrielle": false, - "date_debut": "01/09/2021", - "bul_bgcolor": null, - "date_fin": "15/12/2022", - "resp_can_edit": false, - "dept_id": 1, - "etat": true, - "resp_can_change_ens": false, - "id": 1, - "modalite": "FI", - "ens_can_edit_eval": false, - "formation_id": 1, - "gestion_compensation": false, - "elt_sem_apo": null, - "semestre_id": 1, - "bul_hide_xml": false, - "elt_annee_apo": null, - "block_moyennes": false, - "formsemestre_id": 1, - "titre_num": "master machine info semestre 1", - "date_debut_iso": "2021-09-01", - "date_fin_iso": "2022-12-15", - "responsables": [ - 3, - 2 - ] - }, - ... + { + "date_fin": "31/08/2022", + "resp_can_edit": false, + "dept_id": 1, + "etat": true, + "resp_can_change_ens": true, + "id": 1, + "modalite": "FI", + "ens_can_edit_eval": false, + "formation_id": 1, + "gestion_compensation": false, + "elt_sem_apo": null, + "semestre_id": 1, + "bul_hide_xml": false, + "elt_annee_apo": null, + "titre": "Semestre test", + "block_moyennes": false, + "scodoc7_id": null, + "date_debut": "01/09/2021", + "gestion_semestrielle": false, + "bul_bgcolor": "white", + "formsemestre_id": 1, + "titre_num": "Semestre test semestre 1", + "date_debut_iso": "2021-09-01", + "date_fin_iso": "2022-08-31", + "responsables": [] + }, + ... ] """ # Récupération de l'étudiant diff --git a/app/api/formsemestres.py b/app/api/formsemestres.py index 4ab86132e..e4a0a9431 100644 --- a/app/api/formsemestres.py +++ b/app/api/formsemestres.py @@ -107,21 +107,21 @@ def formsemestre_apo(etape_apo: str): @bp.route( - "/formsemestre//departements//etudiant/etudid//bulletin", - methods=["GET"], -) -@bp.route( - "/formsemestre//departements//etudiant/nip//bulletin", - methods=["GET"], -) -@bp.route( - "/formsemestre//departements//etudiant/ine//bulletin", + "/formsemestre//etudiant/etudid//bulletin", methods=["GET"], ) +# @bp.route( +# "/formsemestre//departements//etudiant/nip//bulletin", +# methods=["GET"], +# ) +# @bp.route( +# "/formsemestre//departements//etudiant/ine//bulletin", +# methods=["GET"], +# ) @token_permission_required(Permission.APIView) def etudiant_bulletin( formsemestre_id, - dept, + # dept, etudid: int = None, nip: int = None, ine: int = None, @@ -241,7 +241,15 @@ def etudiant_bulletin( # Fonction utilisée : app.scodoc.sco_bulletins_json.make_json_formsemestre_bulletinetud() try: - app.set_sco_dept(dept) + formsemestre = models.FormSemestre.query.filter_by( + id=formsemestre_id + ).first_or_404() + + dept = models.Departement.query.filter_by( + id=formsemestre.dept_id + ).first_or_404() + + app.set_sco_dept(dept.acronym) except: return error_response( 409, @@ -499,3 +507,115 @@ def jury(formsemestre_id: int): # ) return jsonify(data) + + +@bp.route( + "/formsemestre//programme", + methods=["GET"], +) +@token_permission_required(Permission.APIView) +def semestre_index(formsemestre_id: int): + """ + Retourne la liste des Ues, ressources et SAE d'un semestre + + dept : l'acronym d'un département + formsemestre_id : l'id d'un formesemestre + + Exemple de résultat : + { + "ues": [ + { + "type": 0, + "formation_id": 1, + "ue_code": "UCOD11", + "id": 1, + "ects": 12.0, + "acronyme": "RT1.1", + "is_external": false, + "numero": 1, + "code_apogee": "", + "titre": "Administrer les r\u00e9seaux et l\u2019Internet", + "coefficient": 0.0, + "semestre_idx": 1, + "color": "#B80004", + "ue_id": 1 + }, + ... + ], + "ressources": [ + { + "titre": "Fondamentaux de la programmation", + "coefficient": 1.0, + "module_type": 2, + "id": 17, + "ects": null, + "abbrev": null, + "ue_id": 3, + "code": "R107", + "formation_id": 1, + "heures_cours": 0.0, + "matiere_id": 3, + "heures_td": 0.0, + "semestre_id": 1, + "heures_tp": 0.0, + "numero": 70, + "code_apogee": "", + "module_id": 17 + }, + ... + ], + "saes": [ + { + "titre": "Se pr\u00e9senter sur Internet", + "coefficient": 1.0, + "module_type": 3, + "id": 14, + "ects": null, + "abbrev": null, + "ue_id": 3, + "code": "SAE14", + "formation_id": 1, + "heures_cours": 0.0, + "matiere_id": 3, + "heures_td": 0.0, + "semestre_id": 1, + "heures_tp": 0.0, + "numero": 40, + "code_apogee": "", + "module_id": 14 + }, + ... + ] + } + """ + + formsemestre = models.FormSemestre.query.filter_by( + id=formsemestre_id + ).first_or_404() + + ues = formsemestre.query_ues() + + ues_dict = [] + ressources = [] + saes = [] + + for ue in ues: + ues_dict.append(ue.to_dict()) + ressources = ue.get_ressources() + saes = ue.get_saes() + + data_ressources = [] + for ressource in ressources: + data_ressources.append(ressource.to_dict()) + + data_saes = [] + for sae in saes: + data_saes.append(sae.to_dict()) + + data = { + "ues": ues_dict, + "ressources": data_ressources, + "saes": data_saes, + } + + return data From a7cb96a9fc8980c4ef3ea7304569d14319963a17 Mon Sep 17 00:00:00 2001 From: leonard_montalbano Date: Mon, 2 May 2022 16:29:26 +0200 Subject: [PATCH 03/32] Ajustement sur formsemestre.py et departement.py + renforcement et factorisation des tests departement, etudiant et formsemestre --- app/api/departements.py | 114 ---------------- app/api/formsemestres.py | 17 ++- tests/api/test_api_departements.py | 201 ++++++++++++++--------------- tests/api/test_api_etudiants.py | 137 +++----------------- tests/api/test_api_formations.py | 3 +- tests/api/test_api_formsemestre.py | 101 +++++++++++++-- tests/api/tools_test_api.py | 13 ++ 7 files changed, 226 insertions(+), 360 deletions(-) create mode 100644 tests/api/tools_test_api.py diff --git a/app/api/departements.py b/app/api/departements.py index 924d1e441..482bceca6 100644 --- a/app/api/departements.py +++ b/app/api/departements.py @@ -187,117 +187,3 @@ def referenciel_competences(dept: str, formation_id: int): ) else: return jsonify(ref_comp) - - -@bp.route( - "/departements//formsemestre//programme", - methods=["GET"], -) -@token_permission_required(Permission.APIView) -def semestre_index(dept: str, formsemestre_id: int): - """ - Retourne la liste des Ues, ressources et SAE d'un semestre - - dept : l'acronym d'un département - formsemestre_id : l'id d'un formesemestre - - Exemple de résultat : - { - "ues": [ - { - "type": 0, - "formation_id": 1, - "ue_code": "UCOD11", - "id": 1, - "ects": 12.0, - "acronyme": "RT1.1", - "is_external": false, - "numero": 1, - "code_apogee": "", - "titre": "Administrer les r\u00e9seaux et l\u2019Internet", - "coefficient": 0.0, - "semestre_idx": 1, - "color": "#B80004", - "ue_id": 1 - }, - ... - ], - "ressources": [ - { - "titre": "Fondamentaux de la programmation", - "coefficient": 1.0, - "module_type": 2, - "id": 17, - "ects": null, - "abbrev": null, - "ue_id": 3, - "code": "R107", - "formation_id": 1, - "heures_cours": 0.0, - "matiere_id": 3, - "heures_td": 0.0, - "semestre_id": 1, - "heures_tp": 0.0, - "numero": 70, - "code_apogee": "", - "module_id": 17 - }, - ... - ], - "saes": [ - { - "titre": "Se pr\u00e9senter sur Internet", - "coefficient": 1.0, - "module_type": 3, - "id": 14, - "ects": null, - "abbrev": null, - "ue_id": 3, - "code": "SAE14", - "formation_id": 1, - "heures_cours": 0.0, - "matiere_id": 3, - "heures_td": 0.0, - "semestre_id": 1, - "heures_tp": 0.0, - "numero": 40, - "code_apogee": "", - "module_id": 14 - }, - ... - ] - } - """ - - app.set_sco_dept(dept) - - formsemestre = models.FormSemestre.query.filter_by( - id=formsemestre_id - ).first_or_404() - - ues = formsemestre.query_ues() - - ues_dict = [] - ressources = [] - saes = [] - - for ue in ues: - ues_dict.append(ue.to_dict()) - ressources = ue.get_ressources() - saes = ue.get_saes() - - data_ressources = [] - for ressource in ressources: - data_ressources.append(ressource.to_dict()) - - data_saes = [] - for sae in saes: - data_saes.append(sae.to_dict()) - - data = { - "ues": ues_dict, - "ressources": data_ressources, - "saes": data_saes, - } - - return data diff --git a/app/api/formsemestres.py b/app/api/formsemestres.py index e4a0a9431..122abf148 100644 --- a/app/api/formsemestres.py +++ b/app/api/formsemestres.py @@ -110,18 +110,17 @@ def formsemestre_apo(etape_apo: str): "/formsemestre//etudiant/etudid//bulletin", methods=["GET"], ) -# @bp.route( -# "/formsemestre//departements//etudiant/nip//bulletin", -# methods=["GET"], -# ) -# @bp.route( -# "/formsemestre//departements//etudiant/ine//bulletin", -# methods=["GET"], -# ) +@bp.route( + "/formsemestre//etudiant/nip//bulletin", + methods=["GET"], +) +@bp.route( + "/formsemestre//etudiant/ine//bulletin", + methods=["GET"], +) @token_permission_required(Permission.APIView) def etudiant_bulletin( formsemestre_id, - # dept, etudid: int = None, nip: int = None, ine: int = None, diff --git a/tests/api/test_api_departements.py b/tests/api/test_api_departements.py index 9e04e8d54..e0b82f230 100644 --- a/tests/api/test_api_departements.py +++ b/tests/api/test_api_departements.py @@ -21,9 +21,18 @@ import requests from tests.api.setup_test_api import SCODOC_URL, CHECK_CERTIFICATE, HEADERS +from tests.api.tools_test_api import verify_fields # departements -def test_departements(): #XXX TODO pour Seb +def test_departements(): + fields = [ + "id", + "acronym", + "description", + "visible", + "date_creation", + ] + r = requests.get( SCODOC_URL + "/ScoDoc/api/departements", headers=HEADERS, @@ -32,134 +41,122 @@ def test_departements(): #XXX TODO pour Seb assert r.status_code == 200 assert len(r.json()) == 1 + dept = r.json()[0] + + fields_OK = verify_fields(dept, fields) + + assert fields_OK is True + # liste_etudiants -def test_liste_etudiants(): #XXX TODO pour Seb +def test_liste_etudiants(): + fields = [ + "civilite", + "code_ine", + "code_nip", + "date_naissance", + "email", + "emailperso", + "etudid", + "nom", + "prenom", + "nomprenom", + "lieu_naissance", + "dept_naissance", + "nationalite", + "boursier", + "id", + "codepostaldomicile", + "paysdomicile", + "telephonemobile", + "typeadresse", + "domicile", + "villedomicile", + "telephone", + "fax", + "description", + ] + r = requests.get( SCODOC_URL + "/ScoDoc/api/departements/TAPI/etudiants/liste", headers=HEADERS, verify=CHECK_CERTIFICATE, ) + + etu = r.json()[0] + + fields_OK = verify_fields(etu, fields) + assert r.status_code == 200 assert len(r.json()) == 16 + assert fields_OK is True r = requests.get( SCODOC_URL + "/ScoDoc/api/departements/TAPI/etudiants/liste/1", headers=HEADERS, verify=CHECK_CERTIFICATE, ) + + etu = r.json()[0] + + fields_OK = verify_fields(etu, fields) + assert r.status_code == 200 assert len(r.json()) == 16 + assert fields_OK is True # liste_semestres_courant -def test_semestres_courant(): #XXX TODO pour Seb +def test_semestres_courant(): + fields = [ + "titre", + "gestion_semestrielle", + "scodoc7_id", + "date_debut", + "bul_bgcolor", + "date_fin", + "resp_can_edit", + "dept_id", + "etat", + "resp_can_change_ens", + "id", + "modalite", + "ens_can_edit_eval", + "formation_id", + "gestion_compensation", + "elt_sem_apo", + "semestre_id", + "bul_hide_xml", + "elt_annee_apo", + "block_moyennes", + "formsemestre_id", + "titre_num", + "date_debut_iso", + "date_fin_iso", + "responsables", + ] + r = requests.get( - SCODOC_URL + "/ScoDoc/api/departements/TAPI/semestres_courants", - headers=HEADERS, - verify=CHECK_CERTIFICATE, + SCODOC_URL + "/ScoDoc/api/departements/TAPI/semestres_courants", + headers=HEADERS, + verify=CHECK_CERTIFICATE, ) + + sem = r.json()[0] + + fields_OK = verify_fields(sem, fields) + assert r.status_code == 200 assert len(r.json()) == 1 + assert fields_OK is True # referenciel_competences def test_referenciel_competences(): r = requests.get( - SCODOC_URL - + "/ScoDoc/api/departements/TAPI/formations/1/referentiel_competences", - headers=HEADERS, - verify=CHECK_CERTIFICATE, - ) + SCODOC_URL + + "/ScoDoc/api/departements/TAPI/formations/1/referentiel_competences", + headers=HEADERS, + verify=CHECK_CERTIFICATE, + ) assert r.status_code == 200 or 204 - - -# semestre_index -def test_semestre_index(): #XXX TODO pour Seb - - ue_fields = [ - "semestre_idx", - "type", - "formation_id", - "ue_code", - "id", - "ects", - "acronyme", - "is_external", - "numero", - "code_apogee", - "titre", - "coefficient", - "color", - "ue_id", - ] - - ressource_fields = [ - "heures_tp", - "code_apogee", - "titre", - "coefficient", - "module_type", - "id", - "ects", - "abbrev", - "ue_id", - "code", - "formation_id", - "heures_cours", - "matiere_id", - "heures_td", - "semestre_id", - "numero", - "module_id", - ] - - - sae_fields = [ - "heures_tp", - "code_apogee", - "titre", - "coefficient", - "module_type", - "id", - "ects", - "abbrev", - "ue_id", - "code", - "formation_id", - "heures_cours", - "matiere_id", - "heures_td", - "semestre_id", - "numero", - "module_id", - ] - - r = requests.get( - SCODOC_URL + "/ScoDoc/api/departements/TAPI/formsemestre/1/programme", - headers=HEADERS, - verify=CHECK_CERTIFICATE, - ) - assert r.status_code == 200 - assert len(r.json()) == 3 - - ue = r.json()["ues"][0] - ressource = r.json()["ressources"][0] - sae = r.json()["saes"][0] - - fields_OK = True - - # Vérifie si tous les champs sont bien présents - for field in ue: - if field not in ue_fields: - fields_OK = False - - for field in ressource: - if field not in ressource_fields: - fields_OK = False - - for field in sae: - if field not in sae_fields: - fields_OK = False - - assert fields_OK is True diff --git a/tests/api/test_api_etudiants.py b/tests/api/test_api_etudiants.py index 6ebecbc7a..da40dfc5c 100644 --- a/tests/api/test_api_etudiants.py +++ b/tests/api/test_api_etudiants.py @@ -24,7 +24,10 @@ from tests.api.setup_test_api import SCODOC_URL, CHECK_CERTIFICATE, HEADERS # etudiants_courant -def test_etudiants_courant(): # XXX TODO pour Seb +from tests.api.tools_test_api import verify_fields + + +def test_etudiants_courant(): fields = [ "id", @@ -46,12 +49,7 @@ def test_etudiants_courant(): # XXX TODO pour Seb # Choisis aléatoirement un étudiant dans la liste des étudiants etu = r.json()[randint(0, len(r.json())) - 1] - fields_OK = True - - # Vérifie si tous les champs sont bien présents - for field in etu: - if field not in fields: - fields_OK = False + fields_OK = verify_fields(etu, fields) assert fields_OK is True @@ -95,18 +93,13 @@ def test_etudiants_courant(): # XXX TODO pour Seb # Choisis aléatoirement un étudiant dans la liste des étudiants etu = r.json()[randint(0, len(r.json())) - 1] - fields_OK = True - - # Vérifie si tous les champs sont bien présents - for field in etu: - if field not in fields_long: - fields_OK = False + fields_OK = verify_fields(etu, fields_long) assert fields_OK is True # etudiant -def test_etudiant(): # XXX TODO pour Seb +def test_etudiant(): fields = [ "civilite", @@ -147,12 +140,7 @@ def test_etudiant(): # XXX TODO pour Seb etu = r.json() - fields_OK = True - - # Vérifie si tous les champs sont bien présents - for field in etu: - if field not in fields: - fields_OK = False + fields_OK = verify_fields(etu, fields) assert fields_OK is True @@ -168,12 +156,7 @@ def test_etudiant(): # XXX TODO pour Seb etu = r.json() - fields_OK = True - - # Vérifie si tous les champs sont bien présents - for field in etu: - if field not in fields: - fields_OK = False + fields_OK = verify_fields(etu, fields) assert fields_OK is True @@ -189,18 +172,13 @@ def test_etudiant(): # XXX TODO pour Seb etu = r.json() - fields_OK = True - - # Vérifie si tous les champs sont bien présents - for field in etu: - if field not in fields: - fields_OK = False + fields_OK = verify_fields(etu, fields) assert fields_OK is True # etudiant_formsemestres -def test_etudiant_formsemestres(): # XXX TODO pour Seb +def test_etudiant_formsemestres(): fields = [ "date_fin", @@ -242,12 +220,7 @@ def test_etudiant_formsemestres(): # XXX TODO pour Seb formsemestre = r.json()[0] - fields_OK = True - - # Vérifie si tous les champs sont bien présents - for field in formsemestre: - if field not in fields: - fields_OK = False + fields_OK = verify_fields(formsemestre, fields) assert fields_OK is True @@ -263,12 +236,7 @@ def test_etudiant_formsemestres(): # XXX TODO pour Seb formsemestre = r.json()[0] - fields_OK = True - - # Vérifie si tous les champs sont bien présents - for field in formsemestre: - if field not in fields: - fields_OK = False + fields_OK = verify_fields(formsemestre, fields) assert fields_OK is True @@ -284,12 +252,7 @@ def test_etudiant_formsemestres(): # XXX TODO pour Seb formsemestre = r.json()[0] - fields_OK = True - - # Vérifie si tous les champs sont bien présents - for field in formsemestre: - if field not in fields: - fields_OK = False + fields_OK = verify_fields(formsemestre, fields) assert fields_OK is True @@ -297,24 +260,6 @@ def test_etudiant_formsemestres(): # XXX TODO pour Seb # etudiant_bulletin_semestre def test_etudiant_bulletin_semestre(): - # fields = [ - # "etudid", - # "formsemestre_id", - # "date", - # "publie", - # "etapes", - # "etudiant", - # "note", - # "rang", - # "rang_group", - # "note_max", - # "bonus_sport_culture", - # "ue", - # "ue_capitalisee", - # "absences", - # "appreciation", - # ] - ######### Test etudid ######### r = requests.get( @@ -325,17 +270,6 @@ def test_etudiant_bulletin_semestre(): assert r.status_code == 200 assert len(r.json()) == 13 - # bulletin = r.json() - # - # fields_OK = True - # - # # Vérifie si tous les champs sont bien présents - # for field in bulletin: - # if field not in fields: - # fields_OK = False - # - # assert fields_OK is True - ######### Test code nip ######### r = requests.get( @@ -346,17 +280,6 @@ def test_etudiant_bulletin_semestre(): assert r.status_code == 200 assert len(r.json()) == 13 - # bulletin = r.json() - # - # fields_OK = True - # - # # Vérifie si tous les champs sont bien présents - # for field in bulletin: - # if field not in fields: - # fields_OK = False - # - # assert fields_OK is True - ######### Test code ine ######### r = requests.get( @@ -367,17 +290,6 @@ def test_etudiant_bulletin_semestre(): assert r.status_code == 200 assert len(r.json()) == 13 - # bulletin = r.json() - # - # fields_OK = True - # - # # Vérifie si tous les champs sont bien présents - # for field in bulletin: - # if field not in fields: - # fields_OK = False - # - # assert fields_OK is True - # etudiant_groups def test_etudiant_groups(): @@ -406,12 +318,7 @@ def test_etudiant_groups(): groups = r.json()[0] - fields_OK = True - - # Vérifie si tous les champs sont bien présents - for field in groups: - if field not in fields: - fields_OK = False + fields_OK = verify_fields(groups, fields) assert fields_OK is True @@ -427,12 +334,7 @@ def test_etudiant_groups(): groups = r.json()[0] - fields_OK = True - - # Vérifie si tous les champs sont bien présents - for field in groups: - if field not in fields: - fields_OK = False + fields_OK = verify_fields(groups, fields) assert fields_OK is True @@ -448,11 +350,6 @@ def test_etudiant_groups(): groups = r.json()[0] - fields_OK = True - - # Vérifie si tous les champs sont bien présents - for field in groups: - if field not in fields: - fields_OK = False + fields_OK = verify_fields(groups, fields) assert fields_OK is True diff --git a/tests/api/test_api_formations.py b/tests/api/test_api_formations.py index 983ab3825..aaaa52831 100644 --- a/tests/api/test_api_formations.py +++ b/tests/api/test_api_formations.py @@ -74,8 +74,7 @@ def test_moduleimpls(): # moduleimpls_sem def test_moduleimpls_sem(): r = requests.get( - SCODOC_URL - + "/ScoDoc/api/formations/moduleimpl/formsemestre/1/liste", + SCODOC_URL + "/ScoDoc/api/formations/moduleimpl/formsemestre/1/liste", headers=HEADERS, verify=CHECK_CERTIFICATE, ) diff --git a/tests/api/test_api_formsemestre.py b/tests/api/test_api_formsemestre.py index a16d751ae..044fa856b 100644 --- a/tests/api/test_api_formsemestre.py +++ b/tests/api/test_api_formsemestre.py @@ -22,6 +22,9 @@ import requests from tests.api.setup_test_api import SCODOC_URL, CHECK_CERTIFICATE, HEADERS # formsemestre +from tests.api.tools_test_api import verify_fields + + def test_formsemestre(): r = requests.get( SCODOC_URL + "/ScoDoc/api/formsemestre/1", @@ -60,37 +63,29 @@ def test_formsemestre(): "responsables", ] - fields_OK = True - - # Vérifie si tous les champs sont bien présents - for field in formsemestre: - if field not in fields: - fields_OK = False + fields_OK = verify_fields(formsemestre, fields) assert fields_OK is True # etudiant_bulletin -def test_etudiant_bulletin(): #XXX TODO pour Seb +def test_etudiant_bulletin(): r = requests.get( - SCODOC_URL - + "/ScoDoc/api/formsemestre/1/departements/TAPI/etudiant/etudid/1/bulletin", + SCODOC_URL + "/ScoDoc/api/formsemestre/1/etudiant/etudid/1/bulletin", headers=HEADERS, verify=CHECK_CERTIFICATE, ) assert r.status_code == 200 r = requests.get( - SCODOC_URL - + "/ScoDoc/api/formsemestre/1/departements/TAPI/etudiant/nip/1/bulletin", + SCODOC_URL + "/ScoDoc/api/formsemestre/1/etudiant/nip/1/bulletin", headers=HEADERS, verify=CHECK_CERTIFICATE, ) assert r.status_code == 200 r = requests.get( - SCODOC_URL - + "/ScoDoc/api/formsemestre/1/departements/TAPI/etudiant/ine/1/bulletin", + SCODOC_URL + "/ScoDoc/api/formsemestre/1/etudiant/ine/1/bulletin", headers=HEADERS, verify=CHECK_CERTIFICATE, ) @@ -115,3 +110,83 @@ def test_bulletins(): # verify=CHECK_CERTIFICATE, # ) # assert r.status_code == 200 + +# semestre_index +def test_semestre_index(): + + ue_fields = [ + "semestre_idx", + "type", + "formation_id", + "ue_code", + "id", + "ects", + "acronyme", + "is_external", + "numero", + "code_apogee", + "titre", + "coefficient", + "color", + "ue_id", + ] + + ressource_fields = [ + "heures_tp", + "code_apogee", + "titre", + "coefficient", + "module_type", + "id", + "ects", + "abbrev", + "ue_id", + "code", + "formation_id", + "heures_cours", + "matiere_id", + "heures_td", + "semestre_id", + "numero", + "module_id", + ] + + sae_fields = [ + "heures_tp", + "code_apogee", + "titre", + "coefficient", + "module_type", + "id", + "ects", + "abbrev", + "ue_id", + "code", + "formation_id", + "heures_cours", + "matiere_id", + "heures_td", + "semestre_id", + "numero", + "module_id", + ] + + r = requests.get( + SCODOC_URL + "/ScoDoc/api/formsemestre/1/programme", + headers=HEADERS, + verify=CHECK_CERTIFICATE, + ) + assert r.status_code == 200 + assert len(r.json()) == 3 + + ue = r.json()["ues"][0] + ressource = r.json()["ressources"][0] + sae = r.json()["saes"][0] + + fields_ue_OK = verify_fields(ue, ue_fields) + fields_ressource_OK = verify_fields(ressource, ressource_fields) + fields_sae_OK = verify_fields(sae, sae_fields) + + assert fields_ue_OK is True + assert fields_ressource_OK is True + assert fields_sae_OK is True diff --git a/tests/api/tools_test_api.py b/tests/api/tools_test_api.py new file mode 100644 index 000000000..4501cd3cc --- /dev/null +++ b/tests/api/tools_test_api.py @@ -0,0 +1,13 @@ +def verify_fields(json_response, fields): + """ + Vérifie si les champs de la réponse json sont corrects + + json_response : la réponse de la requête + fields : une liste avec l'ensemble des champs à vérifier + + Retourne True ou False + """ + for field in json_response: + if field not in fields: + return False + return True From a62d8a6f224ce4cb84877c91203a72261360c783 Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Tue, 3 May 2022 01:35:17 +0200 Subject: [PATCH 04/32] Modification bonus St Brieuc --- app/comp/bonus_spo.py | 2 +- sco_version.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/comp/bonus_spo.py b/app/comp/bonus_spo.py index da8cce5d7..86d444c31 100644 --- a/app/comp/bonus_spo.py +++ b/app/comp/bonus_spo.py @@ -946,7 +946,7 @@ class BonusStBrieuc(BonusSportAdditif): name = "bonus_iut_stbrieuc" displayed_name = "IUT de Saint-Brieuc" proportion_point = 1 / 20.0 - classic_use_bonus_ues = True + classic_use_bonus_ues = False def compute_bonus(self, sem_modimpl_moys_inscrits, modimpl_coefs_etuds_no_nan): """calcul du bonus""" diff --git a/sco_version.py b/sco_version.py index ad7f762ec..f4ef3e98c 100644 --- a/sco_version.py +++ b/sco_version.py @@ -1,7 +1,7 @@ # -*- mode: python -*- # -*- coding: utf-8 -*- -SCOVERSION = "9.2.14" +SCOVERSION = "9.2.15" SCONAME = "ScoDoc" From ae3a59172ed2bc74c9bc9038c873b627d1fcf33a Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Tue, 3 May 2022 02:24:09 +0200 Subject: [PATCH 05/32] =?UTF-8?q?Choix=20dates=20d=C3=A9but/fin=20sur=20Et?= =?UTF-8?q?atAbsencesGr?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/views/absences.py | 34 +++++++++++++++++++++++++++++++--- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/app/views/absences.py b/app/views/absences.py index f664333af..721c07369 100644 --- a/app/views/absences.py +++ b/app/views/absences.py @@ -922,9 +922,36 @@ def EtatAbsencesGr( h = groups_infos.groups_titles gr_tit = p + h - title = "État des absences %s" % gr_tit + title = f"État des absences {gr_tit}" if format == "xls" or format == "xml" or format == "json": columns_ids = ["etudid"] + columns_ids + # --- Formulaire choix dates début / fin + form_date = ( + f""" +
+ + Période du + +  au  + + +   + (nombre de demi-journées) +
""" + + """ + + """ + ) tab = GenTable( columns_ids=columns_ids, rows=T, @@ -945,8 +972,9 @@ def EtatAbsencesGr( init_qtip=True, javascripts=["js/etud_info.js"], ), - html_title=html_sco_header.html_sem_header("%s" % title, with_page_header=False) - + "

Période du %s au %s (nombre de demi-journées)
" % (debut, fin), + html_title=html_sco_header.html_sem_header(title, with_page_header=False) + + form_date, + # "

Période du %s au %s (nombre de demi-journées)
" % (debut, fin), base_url="%s&formsemestre_id=%s&debut=%s&fin=%s" % (groups_infos.base_url, formsemestre_id, debut, fin), filename="etat_abs_" From 78b081335b8d243283caf1fafa8599c0a93fd550 Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Tue, 3 May 2022 02:37:14 +0200 Subject: [PATCH 06/32] =?UTF-8?q?EtatAbsencesGr:=20fix=20affichage=20bours?= =?UTF-8?q?ier=20et=20justif.=20non=20utilis=C3=A9s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/views/absences.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/app/views/absences.py b/app/views/absences.py index 721c07369..ea9e2793f 100644 --- a/app/views/absences.py +++ b/app/views/absences.py @@ -871,7 +871,10 @@ def EtatAbsencesGr( ) nbjustifs_noabs = len( sco_abs.list_abs_justifs( - etudid=etud["etudid"], datedebut=datedebut, only_no_abs=True + etudid=etud["etudid"], + datedebut=datedebut, + datefin=datefin, + only_no_abs=True, ) ) # retrouve sem dans etud['sems'] @@ -894,7 +897,7 @@ def EtatAbsencesGr( "nbjustifs_noabs": nbjustifs_noabs, "_nomprenom_target": "CalAbs?etudid=%s" % etud["etudid"], "_nomprenom_td_attrs": 'id="%s" class="etudinfo"' % etud["etudid"], - "boursier": etud["boursier"], + "boursier": "oui" if etud["boursier"] else "non", } ) if s["ins"]["etat"] == "D": From e74d075bda05bbf920b2d53edcf4e611bbe50f8f Mon Sep 17 00:00:00 2001 From: leonard_montalbano Date: Tue, 3 May 2022 16:15:04 +0200 Subject: [PATCH 07/32] Tests unitaires pour les parties formations et partitions + correction d'exemples de resultats --- app/api/etudiants.py | 135 +++++++++++++++-------------- app/api/formations.py | 34 +++++++- tests/api/test_api_departements.py | 1 - tests/api/test_api_etudiants.py | 5 +- tests/api/test_api_formations.py | 120 ++++++++++++++++++++++--- tests/api/test_api_formsemestre.py | 4 +- tests/api/test_api_partitions.py | 97 +++++++++++++++++---- 7 files changed, 295 insertions(+), 101 deletions(-) diff --git a/app/api/etudiants.py b/app/api/etudiants.py index 9f40cf6b5..c04495abe 100644 --- a/app/api/etudiants.py +++ b/app/api/etudiants.py @@ -22,31 +22,23 @@ def etudiants_courant(long=False): Retourne la liste des étudiants courant Exemple de résultat : - { - { - "civilite": "X", - "code_ine": null, - "code_nip": null, - "date_naissance": null, - "email": null, - "emailperso": null, - "etudid": 18, + [ + { + "id": 1, + "nip": 1, "nom": "MOREL", - "prenom": "JACQUES" - }, - { + "prenom": "JACQUES", "civilite": "X", - "code_ine": null, - "code_nip": null, - "date_naissance": null, - "email": null, - "emailperso": null, - "etudid": 19, - "nom": "FOURNIER", - "prenom": "ANNE" - }, - ... - } + }, + { + "id": 2, + "nip": 2, + "nom": "GILLES", + "prenom": "MAXIME", + "civilite": "X", + }, + ... + ] """ # Récupération de tous les étudiants etuds = Identite.query.filter( @@ -76,17 +68,32 @@ def etudiant(etudid: int = None, nip: int = None, ine: int = None): ine : le code ine d'un étudiant Exemple de résultat : - { - "civilite": "X", - "code_ine": null, - "code_nip": null, - "date_naissance": null, - "email": null, - "emailperso": null, - "etudid": 18, - "nom": "MOREL", - "prenom": "JACQUES" - } + { + "civilite": "X", + "code_ine": "1", + "code_nip": "1", + "date_naissance": "", + "email": "SACHA.COSTA@example.com", + "emailperso": "", + "etudid": 1, + "nom": "COSTA", + "prenom": "SACHA", + "nomprenom": "Sacha COSTA", + "lieu_naissance": "", + "dept_naissance": "", + "nationalite": "", + "boursier": "", + "id": 1, + "codepostaldomicile": "", + "paysdomicile": "", + "telephonemobile": "", + "typeadresse": "domicile", + "domicile": "", + "villedomicile": "", + "telephone": "", + "fax": "", + "description": "" + } """ # Récupération de l'étudiant etud = get_etu_from_etudid_or_nip_or_ine(etudid, nip, ine) @@ -110,36 +117,36 @@ def etudiant_formsemestres(etudid: int = None, nip: int = None, ine: int = None) ine : le code ine d'un étudiant Exemple de résultat : - [ - { - "date_fin": "31/08/2022", - "resp_can_edit": false, - "dept_id": 1, - "etat": true, - "resp_can_change_ens": true, - "id": 1, - "modalite": "FI", - "ens_can_edit_eval": false, - "formation_id": 1, - "gestion_compensation": false, - "elt_sem_apo": null, - "semestre_id": 1, - "bul_hide_xml": false, - "elt_annee_apo": null, - "titre": "Semestre test", - "block_moyennes": false, - "scodoc7_id": null, - "date_debut": "01/09/2021", - "gestion_semestrielle": false, - "bul_bgcolor": "white", - "formsemestre_id": 1, - "titre_num": "Semestre test semestre 1", - "date_debut_iso": "2021-09-01", - "date_fin_iso": "2022-08-31", - "responsables": [] - }, - ... - ] + [ + { + "date_fin": "31/08/2022", + "resp_can_edit": false, + "dept_id": 1, + "etat": true, + "resp_can_change_ens": true, + "id": 1, + "modalite": "FI", + "ens_can_edit_eval": false, + "formation_id": 1, + "gestion_compensation": false, + "elt_sem_apo": null, + "semestre_id": 1, + "bul_hide_xml": false, + "elt_annee_apo": null, + "titre": "Semestre test", + "block_moyennes": false, + "scodoc7_id": null, + "date_debut": "01/09/2021", + "gestion_semestrielle": false, + "bul_bgcolor": "white", + "formsemestre_id": 1, + "titre_num": "Semestre test semestre 1", + "date_debut_iso": "2021-09-01", + "date_fin_iso": "2022-08-31", + "responsables": [] + }, + ... + ] """ # Récupération de l'étudiant etud = get_etu_from_etudid_or_nip_or_ine(etudid, nip, ine) diff --git a/app/api/formations.py b/app/api/formations.py index ee017514d..920d9af4c 100644 --- a/app/api/formations.py +++ b/app/api/formations.py @@ -32,7 +32,6 @@ def formations(): }, ... ] - """ # Récupération de toutes les formations list_formations = models.Formation.query.all() @@ -171,7 +170,6 @@ def formation_export_by_formation_id(formation_id: int, export_ids=False): }, ] } - """ # Fonction utilité : app.scodoc.sco_formations.formation_export() @@ -190,11 +188,41 @@ def formation_export_by_formation_id(formation_id: int, export_ids=False): @bp.route("/formations/moduleimpl/", methods=["GET"]) @token_permission_required(Permission.APIView) -def moduleimpls(moduleimpl_id: int): +def moduleimpl(moduleimpl_id: int): """ Retourne un module moduleimpl en fonction de son id moduleimpl_id : l'id d'un moduleimpl + + Exemple de résultat : + { + "id": 1, + "formsemestre_id": 1, + "computation_expr": null, + "module_id": 1, + "responsable_id": 2, + "moduleimpl_id": 1, + "ens": [], + "module": { + "heures_tp": 0, + "code_apogee": "", + "titre": "Initiation aux réseaux informatiques", + "coefficient": 1, + "module_type": 2, + "id": 1, + "ects": null, + "abbrev": "Init aux réseaux informatiques", + "ue_id": 1, + "code": "R101", + "formation_id": 1, + "heures_cours": 0, + "matiere_id": 1, + "heures_td": 0, + "semestre_id": 1, + "numero": 10, + "module_id": 1 + } + } """ # Récupération des tous les moduleimpl moduleimpl = models.ModuleImpl.query.filter_by(id=moduleimpl_id).first_or_404() diff --git a/tests/api/test_api_departements.py b/tests/api/test_api_departements.py index e0b82f230..b9f762b90 100644 --- a/tests/api/test_api_departements.py +++ b/tests/api/test_api_departements.py @@ -20,7 +20,6 @@ Utilisation : import requests from tests.api.setup_test_api import SCODOC_URL, CHECK_CERTIFICATE, HEADERS - from tests.api.tools_test_api import verify_fields # departements diff --git a/tests/api/test_api_etudiants.py b/tests/api/test_api_etudiants.py index da40dfc5c..c0314a1e6 100644 --- a/tests/api/test_api_etudiants.py +++ b/tests/api/test_api_etudiants.py @@ -21,12 +21,9 @@ from random import randint import requests from tests.api.setup_test_api import SCODOC_URL, CHECK_CERTIFICATE, HEADERS - - -# etudiants_courant from tests.api.tools_test_api import verify_fields - +# etudiants_courant def test_etudiants_courant(): fields = [ diff --git a/tests/api/test_api_formations.py b/tests/api/test_api_formations.py index aaaa52831..346e333e0 100644 --- a/tests/api/test_api_formations.py +++ b/tests/api/test_api_formations.py @@ -20,62 +20,162 @@ Utilisation : import requests from tests.api.setup_test_api import SCODOC_URL, CHECK_CERTIFICATE, HEADERS +from tests.api.tools_test_api import verify_fields + # formations def test_formations(): + fields = [ + "id", + "acronyme", + "titre_officiel", + "formation_code", + "code_specialite", + "dept_id", + "titre", + "version", + "type_parcours", + "referentiel_competence_id", + "formation_id", + ] + r = requests.get( SCODOC_URL + "/ScoDoc/api/formations", headers=HEADERS, verify=CHECK_CERTIFICATE, ) + + formation = r.json()[0] + + fields_OK = verify_fields(formation, fields) + assert r.status_code == 200 + assert len(r.json()) == 1 + assert fields_OK is True # formations_by_id def test_formations_by_id(): + fields = [ + "id", + "acronyme", + "titre_officiel", + "formation_code", + "code_specialite", + "dept_id", + "titre", + "version", + "type_parcours", + "referentiel_competence_id", + "formation_id", + ] + r = requests.get( SCODOC_URL + "/ScoDoc/api/formations/1", headers=HEADERS, verify=CHECK_CERTIFICATE, ) + + formation = r.json() + + fields_OK = verify_fields(formation, fields) + assert r.status_code == 200 + assert fields_OK is True # formation_export_by_formation_id def test_formation_export_by_formation_id(): + fields = [ + "id", + "acronyme", + "titre_officiel", + "formation_code", + "code_specialite", + "dept_id", + "titre", + "version", + "type_parcours", + "referentiel_competence_id", + "formation_id", + "ue", + ] r = requests.get( SCODOC_URL + "/ScoDoc/api/formations/formation_export/1", headers=HEADERS, verify=CHECK_CERTIFICATE, ) + + export_formation = r.json() + + fields_OK = verify_fields(export_formation, fields) + assert r.status_code == 200 + assert fields_OK is True # formsemestre_apo -def test_formsemestre_apo(): +# def test_formsemestre_apo(): +# r = requests.get( +# SCODOC_URL + "/ScoDoc/api/formations/apo/", +# headers=HEADERS, +# verify=CHECK_CERTIFICATE, +# ) +# assert r.status_code == 200 + + +# moduleimpl +def test_moduleimpl(): + + fields = [ + "id", + "formsemestre_id", + "computation_expr", + "module_id", + "responsable_id", + "moduleimpl_id", + "ens", + "module", + ] + r = requests.get( - SCODOC_URL + "/ScoDoc/api/formations/apo/", + SCODOC_URL + "/ScoDoc/api/formations/moduleimpl/1", headers=HEADERS, verify=CHECK_CERTIFICATE, ) - assert r.status_code == 200 + moduleimpl = r.json() + + fields_OK = verify_fields(moduleimpl, fields) -# moduleimpls -def test_moduleimpls(): - r = requests.get( - SCODOC_URL + "/ScoDoc/api/formations/moduleimpl/", - headers=HEADERS, - verify=CHECK_CERTIFICATE, - ) assert r.status_code == 200 + assert fields_OK is True # moduleimpls_sem def test_moduleimpls_sem(): + + fields = [ + "id", + "formsemestre_id", + "computation_expr", + "module_id", + "responsable_id", + "moduleimpl_id", + "ens", + "module", + "moduleimpl_id", + "ens", + ] r = requests.get( SCODOC_URL + "/ScoDoc/api/formations/moduleimpl/formsemestre/1/liste", headers=HEADERS, verify=CHECK_CERTIFICATE, ) + moduleimpl = r.json()[0] + + fields_OK = verify_fields(moduleimpl, fields) + assert r.status_code == 200 + assert len(r.json()) == 21 + assert fields_OK is True diff --git a/tests/api/test_api_formsemestre.py b/tests/api/test_api_formsemestre.py index 044fa856b..ac84cbafd 100644 --- a/tests/api/test_api_formsemestre.py +++ b/tests/api/test_api_formsemestre.py @@ -20,11 +20,9 @@ Utilisation : import requests from tests.api.setup_test_api import SCODOC_URL, CHECK_CERTIFICATE, HEADERS - -# formsemestre from tests.api.tools_test_api import verify_fields - +# formsemestre def test_formsemestre(): r = requests.get( SCODOC_URL + "/ScoDoc/api/formsemestre/1", diff --git a/tests/api/test_api_partitions.py b/tests/api/test_api_partitions.py index 51719aa62..f7af82b0a 100644 --- a/tests/api/test_api_partitions.py +++ b/tests/api/test_api_partitions.py @@ -20,40 +20,105 @@ Utilisation : import requests from tests.api.setup_test_api import SCODOC_URL, CHECK_CERTIFICATE, HEADERS +from tests.api.tools_test_api import verify_fields + # partition def test_partition(): + fields = [ + "partition_id", + "id", + "formsemestre_id", + "partition_name", + "numero", + "bul_show_rank", + "show_in_lists", + ] + r = requests.get( SCODOC_URL + "/ScoDoc/api/partitions/1", headers=HEADERS, verify=CHECK_CERTIFICATE, ) + + partition = r.json()[0] + + fields_OK = verify_fields(partition, fields) + assert r.status_code == 200 + assert len(r.json()) == 2 + assert fields_OK is True # etud_in_group def test_etud_in_group(): + + fields = [ + "etudid", + "id", + "dept_id", + "nom", + "prenom", + "nom_usuel", + "civilite", + "date_naissance", + "lieu_naissance", + "dept_naissance", + "nationalite", + "statut", + "boursier", + "photo_filename", + "code_nip", + "code_ine", + "scodoc7_id", + "email", + "emailperso", + "domicile", + "codepostaldomicile", + "villedomicile", + "paysdomicile", + "telephone", + "telephonemobile", + "fax", + "typeadresse", + "description", + "group_id", + "etat", + "civilite_str", + "nom_disp", + "nomprenom", + "ne", + "email_default", + ] + r = requests.get( SCODOC_URL + "/ScoDoc/api/partitions/groups/1", headers=HEADERS, verify=CHECK_CERTIFICATE, ) + + etu = r.json()[0] + + fields_OK = verify_fields(etu, fields) + assert r.status_code == 200 - r = requests.get( - SCODOC_URL + "/ScoDoc/api/partitions/groups//etat/", - headers=HEADERS, - verify=CHECK_CERTIFICATE, - ) - assert r.status_code == 200 + assert len(r.json()) == 16 + assert fields_OK is True + # r = requests.get( + # SCODOC_URL + "/ScoDoc/api/partitions/groups/1/etat/", + # headers=HEADERS, + # verify=CHECK_CERTIFICATE, + # ) + # assert r.status_code == 200 -# set_groups -def test_set_groups(): - r = requests.get( - SCODOC_URL - + "/ScoDoc/api/partitions/set_groups?partition_id=&groups_lists=&" - "groups_to_create=&groups_to_delete=", - headers=HEADERS, - verify=CHECK_CERTIFICATE, - ) - assert r.status_code == 200 +# # set_groups +# def test_set_groups(): +# r = requests.get( +# SCODOC_URL +# + "/partitions/set_groups/partition//groups/" +# "/delete//create/", +# headers=HEADERS, +# verify=CHECK_CERTIFICATE, +# ) +# assert r.status_code == 200 From 259fe0f66b9c4c92eec8d350a33493b5a2d73a19 Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Tue, 3 May 2022 08:55:56 +0200 Subject: [PATCH 08/32] FIX: SECURITY - disable broken API --- app/api/auth.py | 9 ++++++++- app/api/tokens.py | 3 ++- sco_version.py | 2 +- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/app/api/auth.py b/app/api/auth.py index 20dd7ded8..0832e1354 100644 --- a/app/api/auth.py +++ b/app/api/auth.py @@ -30,6 +30,8 @@ from functools import wraps from flask import abort from flask import g from flask_httpauth import HTTPBasicAuth, HTTPTokenAuth + +from app import log from app.auth.models import User from app.api.errors import error_response @@ -71,10 +73,15 @@ def token_permission_required(permission): def decorator(f): @wraps(f) def decorated_function(*args, **kwargs): + abort(501) scodoc_dept = getattr(g, "scodoc_dept", None) - if hasattr(g, "current_user") and not g.current_user.has_permission( + if not hasattr(g, "current_user") or not g.current_user.has_permission( permission, scodoc_dept ): + if hasattr(g, "current_user"): + log(f"API permission denied (user {g.current_user})") + else: + log(f"API permission denied (no user supplied)") abort(403) return f(*args, **kwargs) diff --git a/app/api/tokens.py b/app/api/tokens.py index f36ec7b0e..32f5a8f48 100644 --- a/app/api/tokens.py +++ b/app/api/tokens.py @@ -1,5 +1,5 @@ from flask import jsonify -from app import db +from app import db, log from app.api import bp from app.api.auth import basic_auth, token_auth @@ -8,6 +8,7 @@ from app.api.auth import basic_auth, token_auth @basic_auth.login_required def get_token(): token = basic_auth.current_user().get_token() + log(f"API: giving token to {basic_auth.current_user()}") db.session.commit() return jsonify({"token": token}) diff --git a/sco_version.py b/sco_version.py index f4ef3e98c..f6933b48a 100644 --- a/sco_version.py +++ b/sco_version.py @@ -1,7 +1,7 @@ # -*- mode: python -*- # -*- coding: utf-8 -*- -SCOVERSION = "9.2.15" +SCOVERSION = "9.2.16" SCONAME = "ScoDoc" From 840a221a4c34e7a0cece17aac7bcb686459aa9d1 Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Tue, 3 May 2022 12:50:43 +0200 Subject: [PATCH 09/32] Bulletins JSON: indique type "classic" et version "0" --- app/scodoc/sco_bulletins_json.py | 3 +-- sco_version.py | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/app/scodoc/sco_bulletins_json.py b/app/scodoc/sco_bulletins_json.py index f34923a5f..7a6bbd49e 100644 --- a/app/scodoc/sco_bulletins_json.py +++ b/app/scodoc/sco_bulletins_json.py @@ -31,7 +31,6 @@ import datetime import json -from app.but import bulletin_but from app.comp import res_sem from app.comp.res_compat import NotesTableCompat from app.models.formsemestre import FormSemestre @@ -92,7 +91,7 @@ def formsemestre_bulletinetud_published_dict( sem = sco_formsemestre.get_formsemestre(formsemestre_id) nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre) - d = {} + d = {"type": "classic", "version": "0"} if (not sem["bul_hide_xml"]) or force_publishing: published = True diff --git a/sco_version.py b/sco_version.py index f6933b48a..fe5771ed7 100644 --- a/sco_version.py +++ b/sco_version.py @@ -1,7 +1,7 @@ # -*- mode: python -*- # -*- coding: utf-8 -*- -SCOVERSION = "9.2.16" +SCOVERSION = "9.2.17" SCONAME = "ScoDoc" From 76e9a924f10c5af49cfab4ac6d5bc5239b45a26e Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Tue, 3 May 2022 13:35:17 +0200 Subject: [PATCH 10/32] =?UTF-8?q?Premi=C3=A8re=20relecture=20de=20la=20nou?= =?UTF-8?q?velle=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/api/absences.py | 17 +- app/api/auth.py | 26 +- app/api/departements.py | 50 +-- app/api/etudiants.py | 34 +- app/api/evaluations.py | 23 +- app/api/formations.py | 89 +++-- app/api/formsemestres.py | 452 ++++++++----------------- app/api/jury.py | 58 ++-- app/api/logos.py | 5 +- app/api/partitions.py | 5 +- app/api/remiser.py | 253 -------------- app/api/sco_api.py | 152 --------- app/api/test_api.py | 444 ------------------------ app/api/tokens.py | 2 + app/auth/models.py | 21 +- app/auth/routes.py | 4 +- app/templates/auth/user_info_page.html | 2 +- tests/api/exemple-api-basic.py | 58 ++-- tests/api/test_api_departements.py | 4 +- tests/api/test_api_formations.py | 2 +- 20 files changed, 358 insertions(+), 1343 deletions(-) delete mode 100644 app/api/remiser.py delete mode 100644 app/api/sco_api.py delete mode 100644 app/api/test_api.py diff --git a/app/api/absences.py b/app/api/absences.py index 35056a05b..42838562a 100644 --- a/app/api/absences.py +++ b/app/api/absences.py @@ -4,7 +4,7 @@ from flask import jsonify from app.api import bp from app.api.errors import error_response -from app.api.auth import token_permission_required +from app.api.auth import token_auth, token_permission_required from app.api.tools import get_etu_from_etudid_or_nip_or_ine from app.scodoc import notesdb as ndb @@ -16,6 +16,7 @@ from app.scodoc.sco_permissions import Permission @bp.route("/absences/etudid/", methods=["GET"]) @bp.route("/absences/nip/", methods=["GET"]) @bp.route("/absences/ine/", methods=["GET"]) +@token_auth.login_required @token_permission_required(Permission.APIView) def absences(etudid: int = None, nip: int = None, ine: int = None): """ @@ -69,6 +70,7 @@ def absences(etudid: int = None, nip: int = None, ine: int = None): @bp.route("/absences/etudid//just", methods=["GET"]) @bp.route("/absences/nip//just", methods=["GET"]) @bp.route("/absences/ine//just", methods=["GET"]) +@token_auth.login_required @token_permission_required(Permission.APIView) def absences_just(etudid: int = None, nip: int = None, ine: int = None): """ @@ -113,10 +115,12 @@ def absences_just(etudid: int = None, nip: int = None, ine: int = None): ) # Récupération des absences justifiées de l'étudiant - absences = sco_abs.list_abs_date(etudid) - for absence in [absence for absence in absences if absence["estjust"]]: + abs_just = [ + absence for absence in sco_abs.list_abs_date(etudid) if absence["estjust"] + ] + for absence in abs_just: absence["jour"] = absence["jour"].isoformat() - return jsonify(absences) + return jsonify(abs_just) @bp.route( @@ -127,13 +131,16 @@ def absences_just(etudid: int = None, nip: int = None, ine: int = None): "/absences/abs_group_etat/group_id//date_debut//date_fin/", methods=["GET"], ) +@token_auth.login_required @token_permission_required(Permission.APIView) def abs_groupe_etat( # XXX A REVOIR XXX group_id: int, date_debut, date_fin, with_boursier=True, format="html" ): """ - Retoune la liste des absences d'un ou plusieurs groupes entre deux dates + Liste des absences d'un ou plusieurs groupes entre deux dates """ + return error_response(501, message="Not implemented") + # Fonction utilisée : app.scodoc.sco_groups.get_group_members() et app.scodoc.sco_abs.list_abs_date() try: diff --git a/app/api/auth.py b/app/api/auth.py index 0832e1354..67d6fba14 100644 --- a/app/api/auth.py +++ b/app/api/auth.py @@ -41,19 +41,23 @@ token_auth = HTTPTokenAuth() @basic_auth.verify_password def verify_password(username, password): + "Verify password for this user" user = User.query.filter_by(user_name=username).first() if user and user.check_password(password): g.current_user = user + # note: est aussi basic_auth.current_user() return user @basic_auth.error_handler def basic_auth_error(status): + "error response (401 for invalid auth.)" return error_response(status) @token_auth.verify_token -def verify_token(token): +def verify_token(token) -> User: + "Retrouve l'utilisateur à partir du jeton" user = User.check_token(token) if token else None g.current_user = user return user @@ -61,6 +65,7 @@ def verify_token(token): @token_auth.error_handler def token_auth_error(status): + "rréponse en cas d'erreur d'auth." return error_response(status) @@ -70,21 +75,22 @@ def get_user_roles(user): def token_permission_required(permission): + "Décorateur pour les fontions de l'API ScoDoc" + def decorator(f): @wraps(f) def decorated_function(*args, **kwargs): - abort(501) - scodoc_dept = getattr(g, "scodoc_dept", None) - if not hasattr(g, "current_user") or not g.current_user.has_permission( - permission, scodoc_dept - ): - if hasattr(g, "current_user"): - log(f"API permission denied (user {g.current_user})") + # token_auth.login_required() + current_user = basic_auth.current_user() + if not current_user or not current_user.has_permission(permission, None): + if current_user: + log(f"API permission denied (user {current_user})") else: - log(f"API permission denied (no user supplied)") + log("API permission denied (no user supplied)") abort(403) return f(*args, **kwargs) - return decorated_function # login_required(decorated_function) + # return decorated_function(token_auth.login_required()) + return decorated_function return decorator diff --git a/app/api/departements.py b/app/api/departements.py index 482bceca6..aaf194267 100644 --- a/app/api/departements.py +++ b/app/api/departements.py @@ -1,16 +1,17 @@ ############################################### Departements ########################################################## -import app + +import json +from flask import jsonify from app import models from app.api import bp -from app.api.auth import token_permission_required +from app.api.auth import token_auth, token_permission_required from app.api.errors import error_response from app.scodoc.sco_permissions import Permission -from flask import jsonify - @bp.route("/departements", methods=["GET"]) +@token_auth.login_required @token_permission_required(Permission.APIView) def departements(): """ @@ -44,12 +45,13 @@ def departements(): return jsonify(data) -@bp.route("/departements//etudiants/liste", methods=["GET"]) +@bp.route("/departements//etudiants/list", methods=["GET"]) @bp.route( - "/departements//etudiants/liste/", methods=["GET"] + "/departements//etudiants/list/", methods=["GET"] ) +@token_auth.login_required @token_permission_required(Permission.APIView) -def liste_etudiants(dept: str, formsemestre_id=None): +def list_etudiants(dept: str, formsemestre_id=None): """ Retourne la liste des étudiants d'un département @@ -97,16 +99,14 @@ def liste_etudiants(dept: str, formsemestre_id=None): # Récupération du formsemestre departement = models.Departement.query.filter_by(acronym=dept).first_or_404() - # Récupération des étudiants - etudiants = departement.etudiants.all() - # Mise en forme des données - list_etu = [etu.to_dict_bul(include_urls=False) for etu in etudiants] + list_etu = [etu.to_dict_bul(include_urls=False) for etu in departement.etudiants] return jsonify(list_etu) @bp.route("/departements//semestres_courants", methods=["GET"]) +@token_auth.login_required @token_permission_required(Permission.APIView) def liste_semestres_courant(dept: str): """ @@ -159,31 +159,3 @@ def liste_semestres_courant(dept: str): data = [d.to_dict() for d in semestres] return jsonify(data) - - -@bp.route( - "/departements//formations//referentiel_competences", - methods=["GET"], -) -@token_permission_required(Permission.APIView) -def referenciel_competences(dept: str, formation_id: int): - """ - Retourne le référentiel de compétences - - dept : l'acronym d'un département - formation_id : l'id d'une formation - """ - dept = models.Departement.query.filter_by(acronym=dept).first_or_404() - - formation = models.Formation.query.filter_by( - id=formation_id, dept_id=dept.id - ).first_or_404() - - ref_comp = formation.referentiel_competence_id - - if ref_comp is None: - return error_response( - 204, message="Pas de référenciel de compétences pour cette formation" - ) - else: - return jsonify(ref_comp) diff --git a/app/api/etudiants.py b/app/api/etudiants.py index c04495abe..72f33bf8d 100644 --- a/app/api/etudiants.py +++ b/app/api/etudiants.py @@ -6,7 +6,7 @@ import app from app import models from app.api import bp from app.api.errors import error_response -from app.api.auth import token_permission_required +from app.api.auth import token_auth, token_permission_required from app.api.tools import get_etu_from_etudid_or_nip_or_ine from app.models import FormSemestreInscription, FormSemestre, Identite from app.scodoc import sco_bulletins @@ -16,6 +16,7 @@ from app.scodoc.sco_permissions import Permission @bp.route("/etudiants/courant", defaults={"long": False}) @bp.route("/etudiants/courant/long", defaults={"long": True}) +@token_auth.login_required @token_permission_required(Permission.APIView) def etudiants_courant(long=False): """ @@ -51,13 +52,13 @@ def etudiants_courant(long=False): data = [etud.to_dict_bul(include_urls=False) for etud in etuds] else: data = [etud.to_dict_short() for etud in etuds] - print(jsonify(data)) return jsonify(data) @bp.route("/etudiant/etudid/", methods=["GET"]) @bp.route("/etudiant/nip/", methods=["GET"]) @bp.route("/etudiant/ine/", methods=["GET"]) +@token_auth.login_required @token_permission_required(Permission.APIView) def etudiant(etudid: int = None, nip: int = None, ine: int = None): """ @@ -107,6 +108,7 @@ def etudiant(etudid: int = None, nip: int = None, ine: int = None): @bp.route("/etudiant/etudid//formsemestres") @bp.route("/etudiant/nip//formsemestres") @bp.route("/etudiant/ine//formsemestres") +@token_auth.login_required @token_permission_required(Permission.APIView) def etudiant_formsemestres(etudid: int = None, nip: int = None, ine: int = None): """ @@ -162,18 +164,41 @@ def etudiant_formsemestres(etudid: int = None, nip: int = None, ine: int = None) @bp.route( "/etudiant/etudid//formsemestre//bulletin", methods=["GET"], + defaults={"version": "long"}, ) @bp.route( "/etudiant/nip//formsemestre//bulletin", methods=["GET"], + defaults={"version": "long"}, ) @bp.route( "/etudiant/ine//formsemestre//bulletin", methods=["GET"], + defaults={"version": "long"}, ) +@bp.route( + "/etudiant/etudid//formsemestre//bulletin/short", + methods=["GET"], + defaults={"version": "short"}, +) +@bp.route( + "/etudiant/nip//formsemestre//bulletin/short", + methods=["GET"], + defaults={"version": "short"}, +) +@bp.route( + "/etudiant/ine//formsemestre//bulletin/short", + methods=["GET"], + defaults={"version": "short"}, +) +@token_auth.login_required @token_permission_required(Permission.APIView) def etudiant_bulletin_semestre( - formsemestre_id, etudid: int = None, nip: int = None, ine: int = None + formsemestre_id, + etudid: int = None, + nip: int = None, + ine: int = None, + version="long", ): """ Retourne le bulletin d'un étudiant en fonction de son id et d'un semestre donné @@ -369,7 +394,7 @@ def etudiant_bulletin_semestre( "Veuillez vérifier que l'id de l'étudiant (etudid, nip, ine) est valide", ) - return sco_bulletins.get_formsemestre_bulletin_etud_json(formsemestre, etu) + return sco_bulletins.get_formsemestre_bulletin_etud_json(formsemestre, etu, version) @bp.route( @@ -382,6 +407,7 @@ def etudiant_bulletin_semestre( @bp.route( "/etudiant/ine//semestre//groups", methods=["GET"] ) +@token_auth.login_required @token_permission_required(Permission.APIView) def etudiant_groups( formsemestre_id: int, etudid: int = None, nip: int = None, ine: int = None diff --git a/app/api/evaluations.py b/app/api/evaluations.py index 4cbfa9f33..9e0bb43e4 100644 --- a/app/api/evaluations.py +++ b/app/api/evaluations.py @@ -5,13 +5,14 @@ import app from app import models from app.api import bp -from app.api.auth import token_permission_required +from app.api.auth import token_auth, token_permission_required from app.api.errors import error_response from app.scodoc.sco_evaluation_db import do_evaluation_get_all_notes from app.scodoc.sco_permissions import Permission @bp.route("/evaluations/", methods=["GET"]) +@token_auth.login_required @token_permission_required(Permission.APIView) def evaluations(moduleimpl_id: int): """ @@ -54,6 +55,7 @@ def evaluations(moduleimpl_id: int): @bp.route("/evaluations/eval_notes/", methods=["GET"]) +@token_auth.login_required @token_permission_required(Permission.APIView) def evaluation_notes(evaluation_id: int): """ @@ -86,26 +88,19 @@ def evaluation_notes(evaluation_id: int): """ # Fonction utilisée : app.scodoc.sco_evaluation_db.do_evaluation_get_all_notes() - eval = models.Evaluation.query.filter_by(id=evaluation_id).first_or_404() - - moduleimpl = models.ModuleImpl.query.filter_by(id=eval.moduleimpl_id).first_or_404() - - formsemestre = models.FormSemestre.query.filter_by( - id=moduleimpl.formsemestre_id - ).first_or_404() - - dept = models.Departement.query.filter_by(id=formsemestre.dept_id).first_or_404() - + evaluation = models.Evaluation.query.filter_by(id=evaluation_id).first_or_404() + dept = models.Departement.query.filter_by( + id=evaluation.moduleimpl.formsemestre.dept_id + ).first() app.set_sco_dept(dept.acronym) try: # Utilisation de la fonction do_evaluation_get_all_notes data = do_evaluation_get_all_notes(evaluation_id) - except AttributeError: + except AttributeError: # ??? return error_response( 409, - message="La requête ne peut être traitée en l’état actuel. \n" - "Veillez vérifier la conformité du 'evaluation_id'", + message="La requête ne peut être traitée en l’état actuel.", ) return jsonify(data) diff --git a/app/api/formations.py b/app/api/formations.py index 920d9af4c..3724c96a3 100644 --- a/app/api/formations.py +++ b/app/api/formations.py @@ -1,48 +1,36 @@ ##############################################" Formations ############################################################ from flask import jsonify +import app from app import models from app.api import bp from app.api.errors import error_response -from app.api.auth import token_permission_required -from app.scodoc.sco_formations import formation_export +from app.api.auth import token_auth, token_permission_required +from app.models.formations import Formation +from app.scodoc import sco_formations from app.scodoc.sco_permissions import Permission -@bp.route("/formations", methods=["GET"]) +@bp.route("/formations_ids", methods=["GET"]) +@token_auth.login_required @token_permission_required(Permission.APIView) -def formations(): +def formations_ids(): """ - Retourne la liste des formations + Retourne la liste de toutes les formations (tous départements) - Exemple de résultat : - [ - { - "id": 1, - "acronyme": "BUT R&T", - "titre_officiel": "Bachelor technologique r\u00e9seaux et t\u00e9l\u00e9communications", - "formation_code": "V1RET", - "code_specialite": null, - "dept_id": 1, - "titre": "BUT R&T", - "version": 1, - "type_parcours": 700, - "referentiel_competence_id": null, - "formation_id": 1 - }, - ... - ] + Exemple de résultat : [ 17, 99, 32 ] """ # Récupération de toutes les formations list_formations = models.Formation.query.all() # Mise en forme des données - data = [d.to_dict() for d in list_formations] + data = [d.id for d in list_formations] return jsonify(data) @bp.route("/formations/", methods=["GET"]) +@token_auth.login_required @token_permission_required(Permission.APIView) def formations_by_id(formation_id: int): """ @@ -66,15 +54,25 @@ def formations_by_id(formation_id: int): } """ # Récupération de la formation - forma = models.Formation.query.filter_by(id=formation_id).first_or_404() + formation = models.Formation.query.filter_by(id=formation_id).first_or_404() # Mise en forme des données - data = forma.to_dict() + data = formation.to_dict() return jsonify(data) -@bp.route("/formations/formation_export/", methods=["GET"]) +@bp.route( + "/formations/formation_export/", + methods=["GET"], + defaults={"export_ids": False}, +) +@bp.route( + "/formations/formation_export//with_ids", + methods=["GET"], + defaults={"export_ids": True}, +) +@token_auth.login_required @token_permission_required(Permission.APIView) def formation_export_by_formation_id(formation_id: int, export_ids=False): """ @@ -171,11 +169,12 @@ def formation_export_by_formation_id(formation_id: int, export_ids=False): ] } """ - # Fonction utilité : app.scodoc.sco_formations.formation_export() - + formation = Formation.query.get_or_404(formation_id) + dept = models.Departement.query.filter_by(id=formation.dept_id).first() + app.set_sco_dept(dept.acronym) try: # Utilisation de la fonction formation_export - data = formation_export(formation_id, export_ids) + data = sco_formations.formation_export(formation_id, export_ids) except ValueError: return error_response( 409, @@ -187,6 +186,7 @@ def formation_export_by_formation_id(formation_id: int, export_ids=False): @bp.route("/formations/moduleimpl/", methods=["GET"]) +@token_auth.login_required @token_permission_required(Permission.APIView) def moduleimpl(moduleimpl_id: int): """ @@ -224,19 +224,16 @@ def moduleimpl(moduleimpl_id: int): } } """ - # Récupération des tous les moduleimpl - moduleimpl = models.ModuleImpl.query.filter_by(id=moduleimpl_id).first_or_404() - - # Mise en forme des données - data = moduleimpl.to_dict() - + modimpl = models.ModuleImpl.query.filter_by(id=moduleimpl_id).first_or_404() + data = modimpl.to_dict() return jsonify(data) @bp.route( - "/formations/moduleimpl/formsemestre//liste", + "/formations/moduleimpl/formsemestre//list", methods=["GET"], ) +@token_auth.login_required @token_permission_required(Permission.APIView) def moduleimpls_sem(formsemestre_id: int): """ @@ -286,3 +283,23 @@ def moduleimpls_sem(formsemestre_id: int): data = [moduleimpl.to_dict() for moduleimpl in moduleimpls] return jsonify(data) + + +@bp.route( + "/formations//referentiel_competences", + methods=["GET"], +) +@token_auth.login_required +@token_permission_required(Permission.APIView) +def referentiel_competences(formation_id: int): + """ + Retourne le référentiel de compétences + formation_id : l'id d'une formation + + return json, ou null si pas de référentiel associé. + """ + formation = models.Formation.query.filter_by(id=formation_id).first_or_404() + + if formation.referentiel_competence is None: + return jsonify(None) + return jsonify(formation.referentiel_competence.to_dict()) diff --git a/app/api/formsemestres.py b/app/api/formsemestres.py index 122abf148..24ec5eeb2 100644 --- a/app/api/formsemestres.py +++ b/app/api/formsemestres.py @@ -5,7 +5,7 @@ import app from app import models from app.api import bp from app.api.errors import error_response -from app.api.auth import token_permission_required +from app.api.auth import token_auth, token_permission_required from app.api.tools import get_etu_from_etudid_or_nip_or_ine from app.models import FormSemestre, FormSemestreEtape from app.scodoc.sco_bulletins import get_formsemestre_bulletin_etud_json @@ -15,6 +15,7 @@ from app.scodoc.sco_pvjury import formsemestre_pvjury @bp.route("/formsemestre/", methods=["GET"]) +@token_auth.login_required @token_permission_required(Permission.APIView) def formsemestre(formsemestre_id: int): """ @@ -48,28 +49,28 @@ def formsemestre(formsemestre_id: int): "titre_num": "Semestre test semestre 1", "date_debut_iso": "2021-09-01", "date_fin_iso": "2022-08-31", - "responsables": [] + "responsables": [] <<< A DOCUMENTER XXX } """ - # Récupération de tous les formsemestres - formsemetre = models.FormSemestre.query.filter_by(id=formsemestre_id).first_or_404() - - # Mise en forme des données - data = formsemetre.to_dict() - + formsemestre = models.FormSemestre.query.filter_by( + id=formsemestre_id + ).first_or_404() + data = formsemestre.to_dict() return jsonify(data) @bp.route("/formsemestre/apo/", methods=["GET"]) +@token_auth.login_required @token_permission_required(Permission.APIView) def formsemestre_apo(etape_apo: str): """ - Retourne les informations sur les formsemestres + Retourne les informations sur les formsemestres ayant cette étape Apogée - etape_apo : l'id d'une étape apogée + etape_apo : un code étape apogée Exemple de résultat : + [ { "date_fin": "31/08/2022", "resp_can_edit": false, @@ -96,7 +97,8 @@ def formsemestre_apo(etape_apo: str): "date_debut_iso": "2021-09-01", "date_fin_iso": "2022-08-31", "responsables": [] - } + }, ... + ] """ formsemestres = FormSemestre.query.filter( FormSemestreEtape.etape_apo == etape_apo, @@ -106,173 +108,8 @@ def formsemestre_apo(etape_apo: str): return jsonify([formsemestre.to_dict() for formsemestre in formsemestres]) -@bp.route( - "/formsemestre//etudiant/etudid//bulletin", - methods=["GET"], -) -@bp.route( - "/formsemestre//etudiant/nip//bulletin", - methods=["GET"], -) -@bp.route( - "/formsemestre//etudiant/ine//bulletin", - methods=["GET"], -) -@token_permission_required(Permission.APIView) -def etudiant_bulletin( - formsemestre_id, - etudid: int = None, - nip: int = None, - ine: int = None, -): - """ - Retourne le bulletin de note d'un étudiant - - formsemestre_id : l'id d'un formsemestre - etudid : l'etudid d'un étudiant - nip : le code nip d'un étudiant - ine : le code ine d'un étudiant - - Exemple de résultat : - { - "etudid":1, - "formsemestre_id":1, - "date":"2022-04-27T10:44:47.448094", - "publie":true, - "etapes":[ - - ], - "etudiant":{ - "etudid":1, - "code_nip":"1", - "code_ine":"1", - "nom":"COSTA", - "prenom":"Sacha", - "civilite":"", - "photo_url":"/ScoDoc/TAPI/Scolarite/get_photo_image?etudid=1&size=small", - "email":"SACHA.COSTA@example.com", - "emailperso":"", - "sexe":"" - }, - "note":{ - "value":"10.60", - "min":"-", - "max":"-", - "moy":"-" - }, - "rang":{ - "value":"10", - "ninscrits":16 - }, - "rang_group":[ - { - "group_type":"TD", - "group_name":"", - "value":"", - "ninscrits":"" - } - ], - "note_max":{ - "value":20 - }, - "bonus_sport_culture":{ - "value":0.0 - }, - "ue":[ - { - "id":1, - "numero":"1", - "acronyme":"RT1.1", - "titre":"Administrer les r\u00e9seaux et l\u2019Internet", - "note":{ - "value":"08.50", - "min":"06.00", - "max":"16.50", - "moy":"11.31" - }, - "rang":"12", - "effectif":16, - "ects":"12", - "code_apogee":"", - "module":[ - { - "id":1, - "code":"R101", - "coefficient":1.0, - "numero":10, - "titre":"Initiation aux r\u00e9seaux informatiques", - "abbrev":"Init aux r\u00e9seaux informatiques", - "note":{ - "value":"12.00", - "moy":"-", - "max":"-", - "min":"-", - "nb_notes":"-", - "nb_missing":"-", - "nb_valid_evals":"-" - }, - "code_apogee":"", - "evaluation":[ - { - "jour":"2022-04-20", - "heure_debut":"08:00:00", - "heure_fin":"09:00:00", - "coefficient":1.0, - "evaluation_type":0, - "evaluation_id":1, - "description":"eval1", - "note":"12.00" - } - ] - }, - ... - ] - } - ], - "ue_capitalisee":[], - "absences":{ - "nbabs":2, - "nbabsjust":1 - }, - "appreciation":[] - } - """ - # Fonction utilisée : app.scodoc.sco_bulletins_json.make_json_formsemestre_bulletinetud() - - try: - formsemestre = models.FormSemestre.query.filter_by( - id=formsemestre_id - ).first_or_404() - - dept = models.Departement.query.filter_by( - id=formsemestre.dept_id - ).first_or_404() - - app.set_sco_dept(dept.acronym) - except: - return error_response( - 409, - message="La requête ne peut être traitée en l’état actuel.\n " - "Veilliez vérifier que le nom de département est valide", - ) - if etudid is None: - # Récupération de l'étudiant - try: - etu = get_etu_from_etudid_or_nip_or_ine(etudid, nip, ine) - etudid = etu.etudid - except AttributeError: - return error_response( - 409, - message="La requête ne peut être traitée en l’état actuel.\n " - "Veilliez vérifier que l'id de l'étudiant (etudid, nip, ine) est valide", - ) - - data = make_json_formsemestre_bulletinetud(formsemestre_id, etudid) - - return data - - @bp.route("/formsemestre//bulletins", methods=["GET"]) +@token_auth.login_required @token_permission_required(Permission.APIView) def bulletins(formsemestre_id: int): """ @@ -452,169 +289,166 @@ def bulletins(formsemestre_id: int): ... ] """ - # Fonction utilisée : app.scodoc.sco_bulletins.get_formsemestre_bulletin_etud_json() - formsemestre = models.FormSemestre.query.filter_by( id=formsemestre_id ).first_or_404() - dept = models.Departement.query.filter_by(id=formsemestre.dept_id).first_or_404() - app.set_sco_dept(dept.acronym) - etuds = formsemestre.etuds - data = [] - for etu in etuds: + for etu in formsemestre.etuds: bul_etu = get_formsemestre_bulletin_etud_json(formsemestre, etu) data.append(bul_etu.json) return jsonify(data) -@bp.route("/formsemestre//jury", methods=["GET"]) -@token_permission_required(Permission.APIView) -def jury(formsemestre_id: int): - """ - Retourne le récapitulatif des décisions jury +# XXX Attendre ScoDoc 9.3 +# @bp.route("/formsemestre//jury", methods=["GET"]) +# @token_auth.login_required +# @token_permission_required(Permission.APIView) +# def jury(formsemestre_id: int): +# """ +# Retourne le récapitulatif des décisions jury - formsemestre_id : l'id d'un formsemestre +# formsemestre_id : l'id d'un formsemestre - Exemple de résultat : +# Exemple de résultat : - """ - # Fonction utilisée : app.scodoc.sco_pvjury.formsemestre_pvjury() +# """ +# # Fonction utilisée : app.scodoc.sco_pvjury.formsemestre_pvjury() - formsemestre = models.FormSemestre.query.filter_by( - id=formsemestre_id - ).first_or_404() +# formsemestre = models.FormSemestre.query.filter_by( +# id=formsemestre_id +# ).first_or_404() - dept = models.Departement.query.filter_by(id=formsemestre.dept_id).first_or_404() +# dept = models.Departement.query.filter_by(id=formsemestre.dept_id).first_or_404() - app.set_sco_dept(dept.acronym) +# app.set_sco_dept(dept.acronym) - data = formsemestre_pvjury(formsemestre_id) +# data = formsemestre_pvjury(formsemestre_id) - # try: - # # Utilisation de la fonction formsemestre_pvjury - # data = formsemestre_pvjury(formsemestre_id) - # except AttributeError: - # return error_response( - # 409, - # message="La requête ne peut être traitée en l’état actuel. \n" - # "Veillez vérifier la conformité du 'formation_id'", - # ) +# # try: +# # # Utilisation de la fonction formsemestre_pvjury +# # data = formsemestre_pvjury(formsemestre_id) +# # except AttributeError: +# # return error_response( +# # 409, +# # message="La requête ne peut être traitée en l’état actuel. \n" +# # "Veillez vérifier la conformité du 'formation_id'", +# # ) - return jsonify(data) +# return jsonify(data) -@bp.route( - "/formsemestre//programme", - methods=["GET"], -) -@token_permission_required(Permission.APIView) -def semestre_index(formsemestre_id: int): - """ - Retourne la liste des Ues, ressources et SAE d'un semestre +# XXX A spécifier et compléter TODO +# @bp.route( +# "/formsemestre//programme", +# methods=["GET"], +# ) +# @token_auth.login_required +# @token_permission_required(Permission.APIView) +# def semestre_index(formsemestre_id: int): # XXX nom bizarre ?? +# """ +# Retourne la liste des Ues, ressources et SAE d'un semestre - dept : l'acronym d'un département - formsemestre_id : l'id d'un formesemestre +# formsemestre_id : l'id d'un formsemestre - Exemple de résultat : - { - "ues": [ - { - "type": 0, - "formation_id": 1, - "ue_code": "UCOD11", - "id": 1, - "ects": 12.0, - "acronyme": "RT1.1", - "is_external": false, - "numero": 1, - "code_apogee": "", - "titre": "Administrer les r\u00e9seaux et l\u2019Internet", - "coefficient": 0.0, - "semestre_idx": 1, - "color": "#B80004", - "ue_id": 1 - }, - ... - ], - "ressources": [ - { - "titre": "Fondamentaux de la programmation", - "coefficient": 1.0, - "module_type": 2, - "id": 17, - "ects": null, - "abbrev": null, - "ue_id": 3, - "code": "R107", - "formation_id": 1, - "heures_cours": 0.0, - "matiere_id": 3, - "heures_td": 0.0, - "semestre_id": 1, - "heures_tp": 0.0, - "numero": 70, - "code_apogee": "", - "module_id": 17 - }, - ... - ], - "saes": [ - { - "titre": "Se pr\u00e9senter sur Internet", - "coefficient": 1.0, - "module_type": 3, - "id": 14, - "ects": null, - "abbrev": null, - "ue_id": 3, - "code": "SAE14", - "formation_id": 1, - "heures_cours": 0.0, - "matiere_id": 3, - "heures_td": 0.0, - "semestre_id": 1, - "heures_tp": 0.0, - "numero": 40, - "code_apogee": "", - "module_id": 14 - }, - ... - ] - } - """ +# Exemple de résultat : +# { +# "ues": [ +# { +# "type": 0, +# "formation_id": 1, +# "ue_code": "UCOD11", +# "id": 1, +# "ects": 12.0, +# "acronyme": "RT1.1", +# "is_external": false, +# "numero": 1, +# "code_apogee": "", +# "titre": "Administrer les r\u00e9seaux et l\u2019Internet", +# "coefficient": 0.0, +# "semestre_idx": 1, +# "color": "#B80004", +# "ue_id": 1 +# }, +# ... +# ], +# "ressources": [ +# { +# "titre": "Fondamentaux de la programmation", +# "coefficient": 1.0, +# "module_type": 2, +# "id": 17, +# "ects": null, +# "abbrev": null, +# "ue_id": 3, +# "code": "R107", +# "formation_id": 1, +# "heures_cours": 0.0, +# "matiere_id": 3, +# "heures_td": 0.0, +# "semestre_id": 1, +# "heures_tp": 0.0, +# "numero": 70, +# "code_apogee": "", +# "module_id": 17 +# }, +# ... +# ], +# "saes": [ +# { +# "titre": "Se pr\u00e9senter sur Internet", +# "coefficient": 1.0, +# "module_type": 3, +# "id": 14, +# "ects": null, +# "abbrev": null, +# "ue_id": 3, +# "code": "SAE14", +# "formation_id": 1, +# "heures_cours": 0.0, +# "matiere_id": 3, +# "heures_td": 0.0, +# "semestre_id": 1, +# "heures_tp": 0.0, +# "numero": 40, +# "code_apogee": "", +# "module_id": 14 +# }, +# ... +# ] +# } +# """ - formsemestre = models.FormSemestre.query.filter_by( - id=formsemestre_id - ).first_or_404() +# formsemestre: FormSemestre = models.FormSemestre.query.filter_by( +# id=formsemestre_id +# ).first_or_404() - ues = formsemestre.query_ues() +# ues = formsemestre.query_ues() - ues_dict = [] - ressources = [] - saes = [] +# ues_dict = [] +# ressources = [] +# saes = [] - for ue in ues: - ues_dict.append(ue.to_dict()) - ressources = ue.get_ressources() - saes = ue.get_saes() +# for ue in ues: +# ues_dict.append(ue.to_dict()) +# ressources = ue.get_ressources() +# saes = ue.get_saes() - data_ressources = [] - for ressource in ressources: - data_ressources.append(ressource.to_dict()) +# data_ressources = [] +# for ressource in ressources: +# data_ressources.append(ressource.to_dict()) - data_saes = [] - for sae in saes: - data_saes.append(sae.to_dict()) +# data_saes = [] +# for sae in saes: +# data_saes.append(sae.to_dict()) - data = { - "ues": ues_dict, - "ressources": data_ressources, - "saes": data_saes, - } +# data = { +# "ues": ues_dict, +# "ressources": data_ressources, +# "saes": data_saes, +# } - return data +# return data diff --git a/app/api/jury.py b/app/api/jury.py index 982280776..1131a2847 100644 --- a/app/api/jury.py +++ b/app/api/jury.py @@ -1,41 +1,41 @@ #################################################### Jury ############################################################# -from flask import jsonify +# from flask import jsonify -from app import models -from app.api import bp -from app.api.errors import error_response -from app.api.auth import token_permission_required -from app.scodoc.sco_prepajury import feuille_preparation_jury -from app.scodoc.sco_pvjury import formsemestre_pvjury +# from app import models +# from app.api import bp +# from app.api.errors import error_response +# from app.api.auth import token_auth, token_permission_required +# from app.scodoc.sco_prepajury import feuille_preparation_jury +# from app.scodoc.sco_pvjury import formsemestre_pvjury -@bp.route("/jury/formsemestre//preparation_jury", methods=["GET"]) -# @token_permission_required(Permission.?) -def jury_preparation(formsemestre_id: int): - """ - Retourne la feuille de préparation du jury +# # @bp.route("/jury/formsemestre//preparation_jury", methods=["GET"]) +# # @token_permission_required(Permission.?) +# def jury_preparation(formsemestre_id: int): +# """ +# Retourne la feuille de préparation du jury - formsemestre_id : l'id d'un formsemestre - """ - # Fonction utilisée : app.scodoc.sco_prepajury.feuille_preparation_jury() +# formsemestre_id : l'id d'un formsemestre +# """ +# # Fonction utilisée : app.scodoc.sco_prepajury.feuille_preparation_jury() - # Utilisation de la fonction feuille_preparation_jury - prepa_jury = feuille_preparation_jury(formsemestre_id) +# # Utilisation de la fonction feuille_preparation_jury +# prepa_jury = feuille_preparation_jury(formsemestre_id) - return error_response(501, message="Not implemented") +# return error_response(501, message="Not implemented") -@bp.route("/jury/formsemestre//decisions_jury", methods=["GET"]) -# @token_permission_required(Permission.?) -def jury_decisions(formsemestre_id: int): - """ - Retourne les décisions du jury suivant un formsemestre donné +# # @bp.route("/jury/formsemestre//decisions_jury", methods=["GET"]) +# # @token_permission_required(Permission.?) +# def jury_decisions(formsemestre_id: int): +# """ +# Retourne les décisions du jury suivant un formsemestre donné - formsemestre_id : l'id d'un formsemestre - """ - # Fonction utilisée : app.scodoc.sco_pvjury.formsemestre_pvjury() +# formsemestre_id : l'id d'un formsemestre +# """ +# # Fonction utilisée : app.scodoc.sco_pvjury.formsemestre_pvjury() - # Utilisation de la fonction formsemestre_pvjury - decision_jury = formsemestre_pvjury(formsemestre_id) +# # Utilisation de la fonction formsemestre_pvjury +# decision_jury = formsemestre_pvjury(formsemestre_id) - return error_response(501, message="Not implemented") +# return error_response(501, message="Not implemented") diff --git a/app/api/logos.py b/app/api/logos.py index 663ef602a..a2c1fbde7 100644 --- a/app/api/logos.py +++ b/app/api/logos.py @@ -36,7 +36,6 @@ from app.api import bp from app.api import requested_format from app.api.auth import token_auth from app.api.errors import error_response -from app.decorators import permission_required from app.models import Departement from app.scodoc.sco_logos import list_logos, find_logo from app.api.auth import token_auth, token_permission_required @@ -44,6 +43,7 @@ from app.scodoc.sco_permissions import Permission @bp.route("/logos", methods=["GET"]) +@token_auth.login_required @token_permission_required(Permission.APIView) def api_get_glob_logos(): if not g.current_user.has_permission(Permission.ScoSuperAdmin, None): @@ -56,6 +56,7 @@ def api_get_glob_logos(): @bp.route("/logos/", methods=["GET"]) +@token_auth.login_required @token_permission_required(Permission.APIView) def api_get_glob_logo(logoname): if not g.current_user.has_permission(Permission.ScoSuperAdmin, None): @@ -72,6 +73,7 @@ def api_get_glob_logo(logoname): @bp.route("/departements//logos", methods=["GET"]) +@token_auth.login_required @token_permission_required(Permission.APIView) def api_get_local_logos(departement): dept_id = Departement.from_acronym(departement).id @@ -82,6 +84,7 @@ def api_get_local_logos(departement): @bp.route("/departements//logos/", methods=["GET"]) +@token_auth.login_required @token_permission_required(Permission.APIView) def api_get_local_logo(departement, logoname): # format = requested_format("jpg", ['png', 'jpg']) XXX ? diff --git a/app/api/partitions.py b/app/api/partitions.py index d925d1749..ed75fd113 100644 --- a/app/api/partitions.py +++ b/app/api/partitions.py @@ -5,12 +5,13 @@ from app import models from app.api import bp from app.api.errors import error_response -from app.api.auth import token_permission_required +from app.api.auth import token_auth, token_permission_required from app.scodoc.sco_groups import get_group_members, setGroups, get_partitions_list from app.scodoc.sco_permissions import Permission @bp.route("/partitions/", methods=["GET"]) +@token_auth.login_required @token_permission_required(Permission.APIView) def partition(formsemestre_id: int): """ @@ -53,6 +54,7 @@ def partition(formsemestre_id: int): @bp.route("/partitions/groups/", methods=["GET"]) @bp.route("/partitions/groups//etat/", methods=["GET"]) +@token_auth.login_required @token_permission_required(Permission.APIView) def etud_in_group(group_id: int, etat=None): """ @@ -125,6 +127,7 @@ def etud_in_group(group_id: int, etat=None): "/create/", methods=["POST"], ) +@token_auth.login_required @token_permission_required(Permission.APIEtudChangeGroups) def set_groups( partition_id: int, groups_lists: str, groups_to_delete: str, groups_to_create: str diff --git a/app/api/remiser.py b/app/api/remiser.py deleted file mode 100644 index 156a4c5fd..000000000 --- a/app/api/remiser.py +++ /dev/null @@ -1,253 +0,0 @@ -# @bp.route("/etudiants", methods=["GET"]) -# @token_permission_required(Permission.APIView) -# def etudiants(): -# """ -# Retourne la liste de tous les étudiants -# -# Exemple de résultat : -# { -# "civilite": "X", -# "code_ine": null, -# "code_nip": null, -# "date_naissance": null, -# "email": null, -# "emailperso": null, -# "etudid": 18, -# "nom": "MOREL", -# "prenom": "JACQUES" -# }, -# { -# "civilite": "X", -# "code_ine": null, -# "code_nip": null, -# "date_naissance": null, -# "email": null, -# "emailperso": null, -# "etudid": 19, -# "nom": "FOURNIER", -# "prenom": "ANNE" -# }, -# ... -# """ -# # Récupération de tous les étudiants -# etu = models.Identite.query.all() -# -# # Mise en forme des données -# data = [d.to_dict_bul(include_urls=False) for d in etu] -# -# return jsonify(data) - - -# @bp.route( -# "/evaluations/eval_set_notes?eval_id=&etudid=¬e=", -# methods=["POST"], -# ) -# @bp.route( -# "/evaluations/eval_set_notes?eval_id=&nip=¬e=", -# methods=["POST"], -# ) -# @bp.route( -# "/evaluations/eval_set_notes?eval_id=&ine=¬e=", -# methods=["POST"], -# ) -# @token_permission_required(Permission.APIEditAllNotes) -# def evaluation_set_notes( -# eval_id: int, note: float, etudid: int = None, nip: int = None, ine: int = None -# ): -# """ -# Set les notes d'une évaluation pour un étudiant donnée -# -# eval_id : l'id d'une évaluation -# note : la note à attribuer -# etudid : l'etudid d'un étudiant -# nip : le code nip d'un étudiant -# ine : le code ine d'un étudiant -# """ -# # Fonction utilisée : app.scodoc.sco_saisie_notes.notes_add() -# -# # Qu'est ce qu'un user ??? -# # notes_add() -# return error_response(501, message="Not implemented") - - -# ### Inutil en définitif ### -# @bp.route( -# "/absences/abs_signale?etudid=&date=&matin=&justif=" -# "&description=", -# methods=["POST"], -# ) -# @bp.route( -# "/absences/abs_signale?nip=&date=&matin=&justif=" -# "&description=", -# methods=["POST"], -# ) -# @bp.route( -# "/absences/abs_signale?ine=&date=&matin=&justif=" -# "&description=", -# methods=["POST"], -# ) -# @bp.route( -# "/absences/abs_signale?ine=&date=&matin=&justif=" -# "&description=&moduleimpl_id=", -# methods=["POST"], -# ) -# @token_permission_required(Permission.APIAbsChange) -# def abs_signale( -# date: datetime, -# matin: bool, -# justif: bool, -# etudid: int = None, -# nip: int = None, -# ine: int = None, ### Inutil en définitif -# description: str = None, -# moduleimpl_id: int = None, -# ): -# """ -# Permet d'ajouter une absence en base -# -# date : la date de l'absence -# matin : True ou False -# justif : True ou False -# etudid : l'etudid d'un étudiant -# nip: le code nip d'un étudiant -# ine : le code ine d'un étudiant -# description : description possible à ajouter sur l'absence -# moduleimpl_id : l'id d'un moduleimpl -# """ -# # Fonctions utilisées : app.scodoc.sco_abs.add_absence() et app.scodoc.sco_abs.add_justif() -# -# if etudid is None: -# # Récupération de l'étudiant -# try: -# etu = get_etu_from_request(etudid, nip, ine) -# etudid = etu.etudid -# except AttributeError: -# return error_response( -# 409, -# message="La requête ne peut être traitée en l’état actuel.\n " -# "Veilliez vérifier que l'id de l'étudiant (etudid, nip, ine) est valide", -# ) -# try: -# # Utilisation de la fonction add_absence -# add_absence(etudid, date, matin, justif, description, moduleimpl_id) -# if justif == True: -# # Utilisation de la fonction add_justif -# add_justif(etudid, date, matin, description) -# except ValueError: -# return error_response( -# 409, message="La requête ne peut être traitée en l’état actuel" -# ) -# @bp.route( -# "/absences/abs_annule_justif?etudid=&jour=&matin=", -# methods=["POST"], -# ) -# @bp.route( -# "/absences/abs_annule_justif?nip=&jour=&matin=", -# methods=["POST"], -# ) -# @bp.route( -# "/absences/abs_annule_justif?ine=&jour=&matin=", -# methods=["POST"], -# ) -# @token_permission_required(Permission.APIAbsChange) -# def abs_annule_justif( -# jour: datetime, matin: str, etudid: int = None, nip: int = None, ine: int = None -# ): -# """ -# Retourne un html - -# jour : la date de l'absence a annulé -# matin : True ou False -# etudid : l'etudid d'un étudiant -# nip: le code nip d'un étudiant -# ine : le code ine d'un étudiant -# """ -# # Fonction utilisée : app.scodoc.sco_abs.annule_justif() - -# if etudid is None: -# # Récupération de l'étudiant -# try: -# etu = get_etu_from_etudid_or_nip_or_ine(etudid, nip, ine) -# etudid = etu.etudid -# except AttributeError: -# return error_response( -# 409, -# message="La requête ne peut être traitée en l’état actuel.\n " -# "Veilliez vérifier que l'id de l'étudiant (etudid, nip, ine) est valide", -# ) -# try: -# # Utilisation de la fonction annule_justif -# annule_justif(etudid, jour, matin) -# except ValueError: -# return error_response( -# 409, -# message="La requête ne peut être traitée en l’état actuel.\n " -# "Veilliez vérifier que le 'jour' et le 'matin' sont valides", -# ) - -# return error_response(200, message="OK") - -# @bp.route( -# "/jury/set_decision/etudid?etudid=&formsemestre_id=" -# "&jury=&devenir=&assiduite=", -# methods=["POST"], -# ) -# @bp.route( -# "/jury/set_decision/nip?etudid=&formsemestre_id=" -# "&jury=&devenir=&assiduite=", -# methods=["POST"], -# ) -# @bp.route( -# "/jury/set_decision/ine?etudid=&formsemestre_id=" -# "&jury=&devenir=&assiduite=", -# methods=["POST"], -# ) -# # @token_permission_required(Permission.) -# def set_decision_jury( -# formsemestre_id: int, -# decision_jury: str, -# devenir_jury: str, -# assiduite: bool, -# etudid: int = None, -# nip: int = None, -# ine: int = None, -# ): -# """ -# Attribuer la décision du jury et le devenir à un etudiant -# -# formsemestre_id : l'id d'un formsemestre -# decision_jury : la décision du jury -# devenir_jury : le devenir du jury -# assiduite : True ou False -# etudid : l'etudid d'un étudiant -# nip: le code nip d'un étudiant -# ine : le code ine d'un étudiant -# """ -# return error_response(501, message="Not implemented") -# -# -# @bp.route( -# "/jury/etudid//formsemestre//annule_decision", -# methods=["DELETE"], -# ) -# @bp.route( -# "/jury/nip//formsemestre//annule_decision", -# methods=["DELETE"], -# ) -# @bp.route( -# "/jury/ine//formsemestre//annule_decision", -# methods=["DELETE"], -# ) -# # @token_permission_required(Permission.) -# def annule_decision_jury( -# formsemestre_id: int, etudid: int = None, nip: int = None, ine: int = None -# ): -# """ -# Supprime la déciosion du jury pour un étudiant donné -# -# formsemestre_id : l'id d'un formsemestre -# etudid : l'etudid d'un étudiant -# nip: le code nip d'un étudiant -# ine : le code ine d'un étudiant -# """ -# return error_response(501, message="Not implemented") diff --git a/app/api/sco_api.py b/app/api/sco_api.py deleted file mode 100644 index 8440aa556..000000000 --- a/app/api/sco_api.py +++ /dev/null @@ -1,152 +0,0 @@ -# -*- mode: python -*- -# -*- coding: utf-8 -*- - -############################################################################## -# -# Gestion scolarite IUT -# -# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -# -# Emmanuel Viennet emmanuel.viennet@viennet.net -# -############################################################################## - -"""API ScoDoc 9 -""" -# PAS ENCORE IMPLEMENTEE, juste un essai -# Pour P. Bouron, il faudrait en priorité l'équivalent de -# Scolarite/Notes/moduleimpl_withmodule_list (alias scodoc7 do_moduleimpl_withmodule_list) -# Scolarite/Notes/evaluation_create -# Scolarite/Notes/evaluation_delete -# Scolarite/Notes/formation_list -# Scolarite/Notes/formsemestre_list -# Scolarite/Notes/formsemestre_partition_list -# Scolarite/Notes/groups_view -# Scolarite/Notes/moduleimpl_status -# Scolarite/setGroups -from datetime import datetime - -from flask import jsonify, request, g, send_file -from sqlalchemy.sql import func - -from app import db, log -from app.api import bp, requested_format -from app.api.auth import token_auth -from app.api.errors import error_response -from app import models -from app.models import FormSemestre, FormSemestreInscription, Identite -from app.models import ApcReferentielCompetences -from app.scodoc.sco_abs import ( - annule_absence, - annule_justif, - add_absence, - add_justif, - list_abs_date, -) -from app.scodoc.sco_bulletins import formsemestre_bulletinetud_dict -from app.scodoc.sco_bulletins_json import make_json_formsemestre_bulletinetud -from app.scodoc.sco_evaluation_db import do_evaluation_get_all_notes -from app.scodoc.sco_formations import formation_export -from app.scodoc.sco_formsemestre_inscriptions import ( - do_formsemestre_inscription_listinscrits, -) -from app.scodoc.sco_groups import setGroups, get_etud_groups, get_group_members -from app.scodoc.sco_logos import list_logos, find_logo, _list_dept_logos -from app.scodoc.sco_moduleimpl import moduleimpl_list -from app.scodoc.sco_permissions import Permission - - -# ###################################################### Logos ########################################################## -# -# # XXX TODO voir get_logo déjà existant dans app/views/scodoc.py -# -# @bp.route("/logos", methods=["GET"]) -# def liste_logos(format="json"): -# """ -# Liste des logos définis pour le site scodoc. -# """ -# # fonction to use : list_logos() -# # try: -# # res = list_logos() -# # except ValueError: -# # return error_response(409, message="La requête ne peut être traitée en l’état actuel") -# # -# # if res is None: -# # return error_response(200, message="Aucun logo trouvé correspondant aux informations renseignés") -# # -# # return res -# -# -# -# @bp.route("/logos/", methods=["GET"]) -# def recup_logo_global(logo_name: str): -# """ -# Retourne l'image au format png ou jpg -# -# logo_name : le nom du logo rechercher -# """ -# # fonction to use find_logo -# # try: -# # res = find_logo(logo_name) -# # except ValueError: -# # return error_response(409, message="La requête ne peut être traitée en l’état actuel") -# # -# # if res is None: -# # return error_response(200, message="Aucun logo trouvé correspondant aux informations renseignés") -# # -# # return res -# -# -# @bp.route("/departements//logos", methods=["GET"]) -# def logo_dept(dept: str): -# """ -# Liste des logos définis pour le département visé. -# -# dept : l'id d'un département -# """ -# # fonction to use: _list_dept_logos -# # dept_id = models.Departement.query.filter_by(acronym=dept).first() -# # try: -# # res = _list_dept_logos(dept_id.id) -# # except ValueError: -# # return error_response(409, message="La requête ne peut être traitée en l’état actuel") -# # -# # if res is None: -# # return error_response(200, message="Aucun logo trouvé correspondant aux informations renseignés") -# # -# # return res -# -# -# @bp.route("/departement//logos/", methods=["GET"]) -# def recup_logo_dept_global(dept: str, logo_name: str): -# """ -# L'image format png ou jpg -# -# dept : l'id d'un département -# logo_name : le nom du logo rechercher -# """ -# # fonction to use find_logo -# # dept_id = models.Departement.query.filter_by(acronym=dept).first() -# # try: -# # res = find_logo(logo_name, dept_id.id) -# # except ValueError: -# # return error_response(409, message="La requête ne peut être traitée en l’état actuel") -# # -# # if res is None: -# # return error_response(200, message="Aucun logo trouvé correspondant aux informations renseignés") -# # -# # return res diff --git a/app/api/test_api.py b/app/api/test_api.py deleted file mode 100644 index 821e3a01d..000000000 --- a/app/api/test_api.py +++ /dev/null @@ -1,444 +0,0 @@ -################################################## Tests ############################################################## - - -# XXX OBSOLETE ??? XXX - -import requests -import os - -from app import models -from app.api import bp, requested_format -from app.api.auth import token_auth -from app.api.errors import error_response - -SCODOC_USER = "test" -SCODOC_PASSWORD = "test" -SCODOC_URL = "http://192.168.1.12:5000" -CHECK_CERTIFICATE = bool(int(os.environ.get("CHECK_CERTIFICATE", False))) - -HEADERS = None - - -def get_token(): - """ - Permet de set le token dans le header - """ - global HEADERS - global SCODOC_USER - global SCODOC_PASSWORD - - r0 = requests.post( - SCODOC_URL + "/ScoDoc/api/tokens", auth=(SCODOC_USER, SCODOC_PASSWORD) - ) - token = r0.json()["token"] - HEADERS = {"Authorization": f"Bearer {token}"} - - -DEPT = None -FORMSEMESTRE = None -ETU = None - - -@bp.route("/test_dept", methods=["GET"]) -def get_departement(): - """ - Permet de tester departements() mais également de set un département dans DEPT pour la suite des tests - """ - - get_token() - - global HEADERS - global CHECK_CERTIFICATE - global SCODOC_USER - global SCODOC_PASSWORD - - # print(HEADERS) - # departements - r = requests.get( - SCODOC_URL + "/ScoDoc/api/departements", - headers=HEADERS, - verify=CHECK_CERTIFICATE, - ) - - if r.status_code == 200: - dept_id = r.json()[0] - # print(dept_id) - - dept = models.Departement.query.filter_by(id=dept_id).first() - dept = dept.to_dict() - - fields = ["id", "acronym", "description", "visible", "date_creation"] - - for field in dept: - if field not in fields: - return error_response(501, field + " field missing") - - global DEPT - DEPT = dept - - return error_response(200, "OK") - - return error_response(409, "La requête ne peut être traitée en l’état actuel") - - -@bp.route("/test_formsemestre", methods=["GET"]) -def get_formsemestre(): - """ - Permet de tester liste_semestres_courant() mais également de set un formsemestre dans FORMSEMESTRE - pour la suite des tests - """ - get_departement() - - global DEPT - dept_acronym = DEPT["acronym"] - - # liste_semestres_courant - r = requests.get( - SCODOC_URL + "/ScoDoc/api/departements/" + dept_acronym + "/semestres_courants", - auth=(SCODOC_USER, SCODOC_PASSWORD), - ) - - if r.status_code == 200: - formsemestre = r.json()[0] - print(r.json()[0]) - - fields = [ - "gestion_semestrielle", - "titre", - "scodoc7_id", - "date_debut", - "bul_bgcolor", - "date_fin", - "resp_can_edit", - "dept_id", - "etat", - "resp_can_change_ens", - "id", - "modalite", - "ens_can_edit_eval", - "formation_id", - "gestion_compensation", - "elt_sem_apo", - "semestre_id", - "bul_hide_xml", - "elt_annee_apo", - "block_moyennes", - "formsemestre_id", - "titre_num", - "date_debut_iso", - "date_fin_iso", - "responsables", - ] - - for field in formsemestre: - if field not in fields: - return error_response(501, field + " field missing") - - global FORMSEMESTRE - FORMSEMESTRE = formsemestre - - return error_response(200, "OK") - - return error_response(409, "La requête ne peut être traitée en l’état actuel") - - -@bp.route("/test_etu", methods=["GET"]) -def get_etudiant(): - """ - Permet de tester etudiants() mais également de set un etudiant dans ETU pour la suite des tests - """ - - # etudiants - r = requests.get( - SCODOC_URL + "/ScoDoc/api/etudiants/courant", - auth=(SCODOC_USER, SCODOC_PASSWORD), - ) - - if r.status_code == 200: - etu = r.json()[0] - - fields = [ - "civilite", - "code_ine", - "code_nip", - "date_naissance", - "email", - "emailperso", - "etudid", - "nom", - "prenom", - ] - - for field in etu: - if field not in fields: - return error_response(501, field + " field missing") - - global ETU - ETU = etu - print(etu) - - return error_response(200, "OK") - - return error_response(409, "La requête ne peut être traitée en l’état actuel") - - -############################################### Departements ########################################################## - - -@bp.route("/test_liste_etudiants") -def test_departements_liste_etudiants(): - """ - Test la route liste_etudiants - """ - # Set un département et un formsemestre pour les tests - get_departement() - get_formsemestre() - - global DEPT - global FORMSEMESTRE - - # Set les fields à vérifier - fields = [ - "civilite", - "code_ine", - "code_nip", - "date_naissance", - "email", - "emailperso", - "etudid", - "nom", - "prenom", - ] - - # liste_etudiants (sans formsemestre) - r1 = requests.get( - SCODOC_URL + "/ScoDoc/api/departements/" + DEPT["acronym"] + "/etudiants/liste", - auth=(SCODOC_USER, SCODOC_PASSWORD), - ) - - if r1.status_code == 200: # Si la requête est "OK" - # On récupère la liste des étudiants - etudiants = r1.json() - - # Vérification que tous les étudiants ont bien tous les bons champs - for etu in etudiants: - for field in etu: - if field not in fields: - return error_response(501, field + " field missing") - - # liste_etudiants (avec formsemestre) - r2 = requests.get( - SCODOC_URL - + "/ScoDoc/api/departements/" - + DEPT["acronym"] - + "/etudiants/liste/" - + str(FORMSEMESTRE["formsemestre_id"]), - auth=(SCODOC_USER, SCODOC_PASSWORD), - ) - - if r2.status_code == 200: # Si la requête est "OK" - # On récupère la liste des étudiants - etudiants = r2.json() - - # Vérification que tous les étudiants ont bien tous les bons champs - for etu in etudiants: - for field in etu: - if field not in fields: - return error_response(501, field + " field missing") - - return error_response(200, "OK") - - return error_response(409, "La requête ne peut être traitée en l’état actuel") - - -@bp.route("/test_referenciel_competences") -def test_departements_referenciel_competences(): - """ - Test la route referenciel_competences - """ - get_departement() - get_formsemestre() - - global DEPT - global FORMSEMESTRE - - # referenciel_competences - r = requests.post( - SCODOC_URL - + "/ScoDoc/api/departements/" - + DEPT["acronym"] - + "/formations/" - + FORMSEMESTRE["formation_id"] - + "/referentiel_competences", - auth=(SCODOC_USER, SCODOC_PASSWORD), - ) - - -@bp.route("/test_liste_semestre_index") -def test_departements_semestre_index(): - """ - Test la route semestre_index - """ - # semestre_index - r5 = requests.post( - SCODOC_URL - + "/ScoDoc/api/departements/" - + DEPT["acronym"] - + "/formsemestre/" - + FORMSEMESTRE["formation_id"] - + "/programme", - auth=(SCODOC_USER, SCODOC_PASSWORD), - ) - - -#################################################### Etudiants ######################################################## - - -def test_routes_etudiants(): - """ - Test les routes de la partie Etudiants - """ - # etudiants - r1 = requests.get( - SCODOC_URL + "/ScoDoc/api/etudiants", auth=(SCODOC_USER, SCODOC_PASSWORD) - ) - - # etudiants_courant - r2 = requests.post(SCODOC_URL + "/ScoDoc/api", auth=(SCODOC_USER, SCODOC_PASSWORD)) - - # etudiant - r3 = requests.post(SCODOC_URL + "/ScoDoc/api", auth=(SCODOC_USER, SCODOC_PASSWORD)) - - # etudiant_formsemestres - r4 = requests.post(SCODOC_URL + "/ScoDoc/api", auth=(SCODOC_USER, SCODOC_PASSWORD)) - - # etudiant_bulletin_semestre - r5 = requests.post(SCODOC_URL + "/ScoDoc/api", auth=(SCODOC_USER, SCODOC_PASSWORD)) - - # etudiant_groups - r6 = requests.post(SCODOC_URL + "/ScoDoc/api", auth=(SCODOC_USER, SCODOC_PASSWORD)) - - -def test_routes_formation(): - """ - Test les routes de la partie Formation - """ - # formations - r1 = requests.post(SCODOC_URL + "/ScoDoc/api", auth=(SCODOC_USER, SCODOC_PASSWORD)) - - # formations_by_id - r2 = requests.post(SCODOC_URL + "/ScoDoc/api", auth=(SCODOC_USER, SCODOC_PASSWORD)) - - # formation_export_by_formation_id - r3 = requests.post(SCODOC_URL + "/ScoDoc/api", auth=(SCODOC_USER, SCODOC_PASSWORD)) - - # formsemestre_apo - r4 = requests.post(SCODOC_URL + "/ScoDoc/api", auth=(SCODOC_USER, SCODOC_PASSWORD)) - - # moduleimpls - r5 = requests.post(SCODOC_URL + "/ScoDoc/api", auth=(SCODOC_USER, SCODOC_PASSWORD)) - - # moduleimpls_sem - r6 = requests.post(SCODOC_URL + "/ScoDoc/api", auth=(SCODOC_USER, SCODOC_PASSWORD)) - - -def test_routes_formsemestres(): - """ - Test les routes de la partie Formsemestres - """ - # formsemestre - r1 = requests.post(SCODOC_URL + "/ScoDoc/api", auth=(SCODOC_USER, SCODOC_PASSWORD)) - - # etudiant_bulletin - r2 = requests.post(SCODOC_URL + "/ScoDoc/api", auth=(SCODOC_USER, SCODOC_PASSWORD)) - - # bulletins - r3 = requests.post(SCODOC_URL + "/ScoDoc/api", auth=(SCODOC_USER, SCODOC_PASSWORD)) - - # jury - r4 = requests.post(SCODOC_URL + "/ScoDoc/api", auth=(SCODOC_USER, SCODOC_PASSWORD)) - - -def test_routes_partitions(): - """ - Test les routes de la partie Partitions - """ - # partition - r1 = requests.post(SCODOC_URL + "/ScoDoc/api", auth=(SCODOC_USER, SCODOC_PASSWORD)) - - # etud_in_group - r2 = requests.post(SCODOC_URL + "/ScoDoc/api", auth=(SCODOC_USER, SCODOC_PASSWORD)) - - # set_groups - r3 = requests.post(SCODOC_URL + "/ScoDoc/api", auth=(SCODOC_USER, SCODOC_PASSWORD)) - - -def test_routes_evaluations(): - """ - Test les routes de la partie Evaluations - """ - # evaluations - r1 = requests.post(SCODOC_URL + "/ScoDoc/api", auth=(SCODOC_USER, SCODOC_PASSWORD)) - - # evaluation_notes - r2 = requests.post(SCODOC_URL + "/ScoDoc/api", auth=(SCODOC_USER, SCODOC_PASSWORD)) - - # evaluation_set_notes - r3 = requests.post(SCODOC_URL + "/ScoDoc/api", auth=(SCODOC_USER, SCODOC_PASSWORD)) - - -def test_routes_jury(): - """ - Test les routes de la partie Jury - """ - # jury_preparation - r1 = requests.post(SCODOC_URL + "/ScoDoc/api", auth=(SCODOC_USER, SCODOC_PASSWORD)) - - # jury_decisions - r2 = requests.post(SCODOC_URL + "/ScoDoc/api", auth=(SCODOC_USER, SCODOC_PASSWORD)) - - # set_decision_jury - r3 = requests.post(SCODOC_URL + "/ScoDoc/api", auth=(SCODOC_USER, SCODOC_PASSWORD)) - - # annule_decision_jury - r4 = requests.post(SCODOC_URL + "/ScoDoc/api", auth=(SCODOC_USER, SCODOC_PASSWORD)) - - -def test_routes_absences(): - """ - Test les routes de la partie Absences - """ - # absences - r1 = requests.post(SCODOC_URL + "/ScoDoc/api", auth=(SCODOC_USER, SCODOC_PASSWORD)) - - # absences_justify - r2 = requests.post(SCODOC_URL + "/ScoDoc/api", auth=(SCODOC_USER, SCODOC_PASSWORD)) - - # abs_signale - r3 = requests.post(SCODOC_URL + "/ScoDoc/api", auth=(SCODOC_USER, SCODOC_PASSWORD)) - - # abs_annule - r4 = requests.post(SCODOC_URL + "/ScoDoc/api", auth=(SCODOC_USER, SCODOC_PASSWORD)) - - # abs_annule_justif - r5 = requests.post(SCODOC_URL + "/ScoDoc/api", auth=(SCODOC_USER, SCODOC_PASSWORD)) - - # abs_groupe_etat - r6 = requests.post(SCODOC_URL + "/ScoDoc/api", auth=(SCODOC_USER, SCODOC_PASSWORD)) - - -def test_routes_logos(): - """ - Test les routes de la partie Logos - """ - # liste_logos - r1 = requests.post(SCODOC_URL + "/ScoDoc/api", auth=(SCODOC_USER, SCODOC_PASSWORD)) - - # recup_logo_global - r2 = requests.post(SCODOC_URL + "/ScoDoc/api", auth=(SCODOC_USER, SCODOC_PASSWORD)) - - # logo_dept - r3 = requests.post(SCODOC_URL + "/ScoDoc/api", auth=(SCODOC_USER, SCODOC_PASSWORD)) - - # recup_logo_dept_global - r4 = requests.post(SCODOC_URL + "/ScoDoc/api", auth=(SCODOC_USER, SCODOC_PASSWORD)) diff --git a/app/api/tokens.py b/app/api/tokens.py index 32f5a8f48..f5c11b096 100644 --- a/app/api/tokens.py +++ b/app/api/tokens.py @@ -7,6 +7,7 @@ from app.api.auth import basic_auth, token_auth @bp.route("/tokens", methods=["POST"]) @basic_auth.login_required def get_token(): + "renvoie un jeton jwt pour l'utilisateur courant" token = basic_auth.current_user().get_token() log(f"API: giving token to {basic_auth.current_user()}") db.session.commit() @@ -16,6 +17,7 @@ def get_token(): @bp.route("/tokens", methods=["DELETE"]) @token_auth.login_required def revoke_token(): + "révoque le jeton de l'utilisateur courant" token_auth.current_user().revoke_token() db.session.commit() return "", 204 diff --git a/app/auth/models.py b/app/auth/models.py index cfab21a9c..4ed1e41d5 100644 --- a/app/auth/models.py +++ b/app/auth/models.py @@ -18,7 +18,7 @@ from werkzeug.security import generate_password_hash, check_password_hash import jwt -from app import db, login +from app import db, log, login from app.models import Departement from app.models import SHORT_STR_LEN from app.scodoc.sco_exceptions import ScoValueError @@ -150,11 +150,22 @@ class User(UserMixin, db.Model): def verify_reset_password_token(token): "Vérification du token de reéinitialisation du mot de passe" try: - user_id = jwt.decode( + token = jwt.decode( token, current_app.config["SECRET_KEY"], algorithms=["HS256"] - )["reset_password"] + ) + except jwt.exceptions.ExpiredSignatureError: + log(f"verify_reset_password_token: token expired") except: - return + return None + try: + user_id = token["reset_password"] + # double check en principe inutile car déjà fait dans decode() + expire = float(token["exp"]) + if time() > expire: + log(f"verify_reset_password_token: token expired for uid={user_id}") + return None + except (TypeError, KeyError): + return None return User.query.get(user_id) def to_dict(self, include_email=True): @@ -214,6 +225,7 @@ class User(UserMixin, db.Model): self.add_role(role, dept) def get_token(self, expires_in=3600): + "Un jeton pour cet user. Stocké en base, non commité." now = datetime.utcnow() if self.token and self.token_expiration > now + timedelta(seconds=60): return self.token @@ -223,6 +235,7 @@ class User(UserMixin, db.Model): return self.token def revoke_token(self): + "Révoque le jeton de cet utilisateur" self.token_expiration = datetime.utcnow() - timedelta(seconds=1) @staticmethod diff --git a/app/auth/routes.py b/app/auth/routes.py index 24daa8ca0..1f0259ab5 100644 --- a/app/auth/routes.py +++ b/app/auth/routes.py @@ -71,7 +71,7 @@ def create_user(): flash("User {} created".format(user.user_name)) return redirect(url_for("scodoc.index")) return render_template( - "auth/register.html", title=u"Création utilisateur", form=form + "auth/register.html", title="Création utilisateur", form=form ) @@ -112,7 +112,7 @@ def reset_password(token): if current_user.is_authenticated: return redirect(url_for("scodoc.index")) user = User.verify_reset_password_token(token) - if not user: + if user is None: return redirect(url_for("scodoc.index")) form = ResetPasswordForm() if form.validate_on_submit(): diff --git a/app/templates/auth/user_info_page.html b/app/templates/auth/user_info_page.html index 8a287c01d..f344fb115 100644 --- a/app/templates/auth/user_info_page.html +++ b/app/templates/auth/user_info_page.html @@ -45,7 +45,7 @@ {# Liste des permissions #}

-

Permissions de cet utilisateur dans le département {dept}:

+

Permissions de cet utilisateur dans le département {{dept}}:

    {% for p in Permission.description %}
  • {{Permission.description[p]}} : diff --git a/tests/api/exemple-api-basic.py b/tests/api/exemple-api-basic.py index 88ba892f6..a9f109289 100644 --- a/tests/api/exemple-api-basic.py +++ b/tests/api/exemple-api-basic.py @@ -16,12 +16,11 @@ export CHECK_CERTIFICATE=0 # ou 1 si serveur de production avec certif SSL valid (on peut aussi placer ces valeurs dans un fichier .env du répertoire tests/api). -Travail en cours, un seul point d'API (list_depts). +Travail en cours. """ from dotenv import load_dotenv import os -import pdb import requests import urllib3 from pprint import pprint as pp @@ -29,16 +28,16 @@ from pprint import pprint as pp # --- Lecture configuration (variables d'env ou .env) BASEDIR = os.path.abspath(os.path.dirname(__file__)) load_dotenv(os.path.join(BASEDIR, ".env")) -CHECK_CERTIFICATE = bool(int(os.environ.get("CHECK_CERTIFICATE", False))) +CHK_CERT = bool(int(os.environ.get("CHECK_CERTIFICATE", False))) SCODOC_URL = os.environ["SCODOC_URL"] -SCODOC_DEPT = os.environ["SCODOC_DEPT"] -DEPT_URL = SCODOC_URL + "/ScoDoc/" + SCODOC_DEPT + "/Scolarite/" +API_URL = SCODOC_URL + "/ScoDoc/api" SCODOC_USER = os.environ["SCODOC_USER"] SCODOC_PASSWORD = os.environ["SCODOC_PASSWD"] print(f"SCODOC_URL={SCODOC_URL}") +print(f"API URL={API_URL}") # --- -if not CHECK_CERTIFICATE: +if not CHK_CERT: urllib3.disable_warnings() @@ -48,9 +47,7 @@ class ScoError(Exception): def GET(path: str, headers={}, errmsg=None): """Get and returns as JSON""" - r = requests.get( - DEPT_URL + "/" + path, headers=headers or HEADERS, verify=CHECK_CERTIFICATE - ) + r = requests.get(API_URL + "/" + path, headers=headers or HEADERS, verify=CHK_CERT) if r.status_code != 200: raise ScoError(errmsg or "erreur !") return r.json() # decode la reponse JSON @@ -58,39 +55,40 @@ def GET(path: str, headers={}, errmsg=None): def POST(s, path: str, data: dict, errmsg=None): """Post""" - r = s.post(DEPT_URL + "/" + path, data=data, verify=CHECK_CERTIFICATE) + r = s.post(API_URL + "/" + path, data=data, verify=CHK_CERT) if r.status_code != 200: raise ScoError(errmsg or "erreur !") return r.text # --- Obtention du jeton (token) -r = requests.post( - SCODOC_URL + "/ScoDoc/api/tokens", auth=(SCODOC_USER, SCODOC_PASSWORD) -) +r = requests.post(API_URL + "/tokens", auth=(SCODOC_USER, SCODOC_PASSWORD)) assert r.status_code == 200 token = r.json()["token"] HEADERS = {"Authorization": f"Bearer {token}"} -r = requests.get( - SCODOC_URL + "/ScoDoc/api/list_depts", - headers=HEADERS, - verify=CHECK_CERTIFICATE, -) +r = requests.get(API_URL + "/departements", headers=HEADERS, verify=CHK_CERT) if r.status_code != 200: raise ScoError("erreur de connexion: vérifier adresse et identifiants") pp(r.json()) -# Liste des tous les étudiants en cours (de tous les depts) -r = requests.get( - SCODOC_URL + "/ScoDoc/api/etudiants/courant", - headers=HEADERS, - verify=CHECK_CERTIFICATE, -) +# Liste de tous les étudiants en cours (de tous les depts) +r = requests.get(API_URL + "/etudiants/courant", headers=HEADERS, verify=CHK_CERT) if r.status_code != 200: raise ScoError("erreur de connexion: vérifier adresse et identifiants") +print(f"{len(r.json())} étudiants courants") + +# Bulletin d'un BUT +formsemestre_id = 1052 # A adapter +etudid = 16400 +bul = GET(f"/etudiant/etudid/{etudid}/formsemestre/{formsemestre_id}/bulletin") + +# d'un DUT +formsemestre_id = 1028 # A adapter +etudid = 14721 +bul_dut = GET(f"/etudiant/etudid/{etudid}/formsemestre/{formsemestre_id}/bulletin") # # --- Recupere la liste de tous les semestres: # sems = GET(s, "Notes/formsemestre_list?format=json", "Aucun semestre !") @@ -146,15 +144,3 @@ if r.status_code != 200: # print( # f"Pour vérifier, aller sur: {DEPT_URL}/Notes/moduleimpl_status?moduleimpl_id={mod['moduleimpl_id']}", # ) - -# # ---- Saisie d'une note -# junk = POST( -# s, -# "/Notes/save_note", -# data={ -# "etudid": etudid, -# "evaluation_id": evaluation_id, -# "value": 16.66, # la note ! -# "comment": "test API", -# }, -# ) diff --git a/tests/api/test_api_departements.py b/tests/api/test_api_departements.py index b9f762b90..2dab4614f 100644 --- a/tests/api/test_api_departements.py +++ b/tests/api/test_api_departements.py @@ -77,7 +77,7 @@ def test_liste_etudiants(): ] r = requests.get( - SCODOC_URL + "/ScoDoc/api/departements/TAPI/etudiants/liste", + SCODOC_URL + "/ScoDoc/api/departements/TAPI/etudiants/list", headers=HEADERS, verify=CHECK_CERTIFICATE, ) @@ -91,7 +91,7 @@ def test_liste_etudiants(): assert fields_OK is True r = requests.get( - SCODOC_URL + "/ScoDoc/api/departements/TAPI/etudiants/liste/1", + SCODOC_URL + "/ScoDoc/api/departements/TAPI/etudiants/list/1", headers=HEADERS, verify=CHECK_CERTIFICATE, ) diff --git a/tests/api/test_api_formations.py b/tests/api/test_api_formations.py index 346e333e0..29f0301ff 100644 --- a/tests/api/test_api_formations.py +++ b/tests/api/test_api_formations.py @@ -168,7 +168,7 @@ def test_moduleimpls_sem(): "ens", ] r = requests.get( - SCODOC_URL + "/ScoDoc/api/formations/moduleimpl/formsemestre/1/liste", + SCODOC_URL + "/ScoDoc/api/formations/moduleimpl/formsemestre/1/list", headers=HEADERS, verify=CHECK_CERTIFICATE, ) From 22b0e29d7217e697b42418a92403947ee8ea32ba Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Tue, 3 May 2022 22:07:28 +0200 Subject: [PATCH 11/32] Fix: remove useless api imports --- app/api/__init__.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/api/__init__.py b/app/api/__init__.py index 36da5c561..9be4d3b69 100644 --- a/app/api/__init__.py +++ b/app/api/__init__.py @@ -22,8 +22,6 @@ def requested_format(default_format="json", allowed_formats=None): from app.api import tokens -from app.api import sco_api -from app.api import test_api from app.api import departements from app.api import etudiants from app.api import formations From 4981094fbed392d18fa4fc2e9ade64c95ab53203 Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Wed, 4 May 2022 05:45:46 +0200 Subject: [PATCH 12/32] =?UTF-8?q?Fixes=20#374:=20bulletins=20BUT=20si=20no?= =?UTF-8?q?n=20publi=C3=A9s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/views/notes.py | 2 +- tests/api/__init__.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 tests/api/__init__.py diff --git a/app/views/notes.py b/app/views/notes.py index adde8b6a1..e66a42f7b 100644 --- a/app/views/notes.py +++ b/app/views/notes.py @@ -314,7 +314,7 @@ def formsemestre_bulletinetud( ) if format == "json": return sco_bulletins.get_formsemestre_bulletin_etud_json( - formsemestre, etud, version=version + formsemestre, etud, version=version, force_publishing=force_publishing ) if formsemestre.formation.is_apc() and format == "html": return render_template( diff --git a/tests/api/__init__.py b/tests/api/__init__.py new file mode 100644 index 000000000..a365074d5 --- /dev/null +++ b/tests/api/__init__.py @@ -0,0 +1 @@ +# API tests From fbe480902b8161748ae2840b4780bf47d9bafb0d Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Wed, 4 May 2022 20:26:45 +0200 Subject: [PATCH 13/32] typo --- app/scodoc/sco_cache.py | 4 ++-- app/scodoc/sco_synchro_etuds.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/scodoc/sco_cache.py b/app/scodoc/sco_cache.py index 2e9792e41..8fcc6f7ef 100644 --- a/app/scodoc/sco_cache.py +++ b/app/scodoc/sco_cache.py @@ -228,7 +228,7 @@ def invalidate_formsemestre( # was inval_cache(formsemestre_id=None, pdfonly=Fa if getattr(g, "defer_cache_invalidation", False): g.sem_to_invalidate.add(formsemestre_id) return - log("inval_cache, formsemestre_id={formsemestre_id} pdfonly={pdfonly}") + log(f"inval_cache, formsemestre_id={formsemestre_id} pdfonly={pdfonly}") if formsemestre_id is None: # clear all caches log("----- invalidate_formsemestre: clearing all caches -----") @@ -272,7 +272,7 @@ def invalidate_formsemestre( # was inval_cache(formsemestre_id=None, pdfonly=Fa SemBulletinsPDFCache.invalidate_sems(formsemestre_ids) -class DefferedSemCacheManager: +class DeferredSemCacheManager: """Contexte pour effectuer des opérations indépendantes dans la même requete qui invalident le cache. Par exemple, quand on inscrit des étudiants un par un à un semestre, chaque inscription va invalider diff --git a/app/scodoc/sco_synchro_etuds.py b/app/scodoc/sco_synchro_etuds.py index 67ac5a6c7..9a13a8f5c 100644 --- a/app/scodoc/sco_synchro_etuds.py +++ b/app/scodoc/sco_synchro_etuds.py @@ -252,7 +252,7 @@ def formsemestre_synchro_etuds( etudids_a_desinscrire = [nip2etudid(x) for x in a_desinscrire] etudids_a_desinscrire += a_desinscrire_without_key # - with sco_cache.DefferedSemCacheManager(): + with sco_cache.DeferredSemCacheManager(): do_import_etuds_from_portal(sem, a_importer, etudsapo_ident) sco_inscr_passage.do_inscrit(sem, etudids_a_inscrire) sco_inscr_passage.do_desinscrit(sem, etudids_a_desinscrire) From b930406be9214909cf1885a9b4aecd7831ece23a Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Wed, 4 May 2022 20:40:20 +0200 Subject: [PATCH 14/32] Fix: display role permissions --- app/scodoc/sco_permissions.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/scodoc/sco_permissions.py b/app/scodoc/sco_permissions.py index 512caa8a5..ccf46e8e8 100644 --- a/app/scodoc/sco_permissions.py +++ b/app/scodoc/sco_permissions.py @@ -70,7 +70,8 @@ class Permission(object): setattr(Permission, symbol, perm) Permission.description[symbol] = description Permission.permission_by_name[symbol] = perm - Permission.NBITS = len(_SCO_PERMISSIONS) + max_perm = max(p[0] for p in _SCO_PERMISSIONS) + Permission.NBITS = max_perm.bit_length() @staticmethod def get_by_name(permission_name: str) -> int: From 95becc172be6cc78baab3ca45cfb615c48fee238 Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Wed, 4 May 2022 23:11:20 +0200 Subject: [PATCH 15/32] =?UTF-8?q?API:=20corrections,=20refonte=20compl?= =?UTF-8?q?=C3=A8te=20des=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/api/absences.py | 65 ++--- app/api/etudiants.py | 8 +- app/api/evaluations.py | 2 +- config.py | 20 +- scodoc.py | 3 +- tests/api/dotenv_exemple | 11 + tests/api/setup_test_api.py | 34 +-- tests/api/test_api_absences.py | 63 +++-- tests/api/test_api_departements.py | 43 ++-- tests/api/test_api_etudiants.py | 229 +++++++++--------- tests/api/test_api_evaluations.py | 34 ++- tests/api/test_api_formations.py | 137 ++++++----- tests/api/test_api_formsemestre.py | 61 +++-- tests/api/test_api_jury.py | 42 ++-- tests/api/test_api_logos.py | 31 ++- tests/api/test_api_partitions.py | 50 ++-- tests/api/tools_test_api.py | 10 +- tools/create_database.sh | 12 +- .../fakedatabase/create_test_api_database.py | 122 +++++++--- 19 files changed, 554 insertions(+), 423 deletions(-) create mode 100644 tests/api/dotenv_exemple diff --git a/app/api/absences.py b/app/api/absences.py index 42838562a..b37a256aa 100644 --- a/app/api/absences.py +++ b/app/api/absences.py @@ -123,39 +123,40 @@ def absences_just(etudid: int = None, nip: int = None, ine: int = None): return jsonify(abs_just) -@bp.route( - "/absences/abs_group_etat/", - methods=["GET"], -) -@bp.route( - "/absences/abs_group_etat/group_id//date_debut//date_fin/", - methods=["GET"], -) -@token_auth.login_required -@token_permission_required(Permission.APIView) -def abs_groupe_etat( # XXX A REVOIR XXX - group_id: int, date_debut, date_fin, with_boursier=True, format="html" -): - """ - Liste des absences d'un ou plusieurs groupes entre deux dates - """ - return error_response(501, message="Not implemented") +# XXX TODO INACHEVEE +# @bp.route( +# "/absences/abs_group_etat/", +# methods=["GET"], +# ) +# @bp.route( +# "/absences/abs_group_etat/group_id//date_debut//date_fin/", +# methods=["GET"], +# ) +# @token_auth.login_required +# @token_permission_required(Permission.APIView) +# def abs_groupe_etat( # XXX A REVOIR XXX +# group_id: int, date_debut, date_fin, with_boursier=True, format="html" +# ): +# """ +# Liste des absences d'un ou plusieurs groupes entre deux dates +# """ +# return error_response(501, message="Not implemented") - # Fonction utilisée : app.scodoc.sco_groups.get_group_members() et app.scodoc.sco_abs.list_abs_date() +# # Fonction utilisée : app.scodoc.sco_groups.get_group_members() et app.scodoc.sco_abs.list_abs_date() - try: - # Utilisation de la fonction get_group_members - members = get_group_members(group_id) - except ValueError: - return error_response( - 409, message="La requête ne peut être traitée en l’état actuel" - ) +# try: +# # Utilisation de la fonction get_group_members +# members = get_group_members(group_id) +# except ValueError: +# return error_response( +# 409, message="La requête ne peut être traitée en l’état actuel" +# ) - data = [] - # Filtre entre les deux dates renseignées - for member in members: - abs = sco_abs.list_abs_date(member.id, date_debut, date_fin) - data.append(abs) +# data = [] +# # Filtre entre les deux dates renseignées +# for member in members: +# abs = sco_abs.list_abs_date(member.id, date_debut, date_fin) +# data.append(abs) - # return jsonify(data) # XXX TODO faire en sorte de pouvoir renvoyer sa (ex to_dict() dans absences) - return error_response(501, message="Not implemented") +# # return jsonify(data) # XXX TODO faire en sorte de pouvoir renvoyer sa (ex to_dict() dans absences) +# return error_response(501, message="Not implemented") diff --git a/app/api/etudiants.py b/app/api/etudiants.py index 72f33bf8d..a6baa2d88 100644 --- a/app/api/etudiants.py +++ b/app/api/etudiants.py @@ -398,14 +398,14 @@ def etudiant_bulletin_semestre( @bp.route( - "/etudiant/etudid//semestre//groups", + "/etudiant/etudid//formsemestre//groups", methods=["GET"], ) @bp.route( - "/etudiant/nip//semestre//groups", methods=["GET"] + "/etudiant/nip//formsemestre//groups", methods=["GET"] ) @bp.route( - "/etudiant/ine//semestre//groups", methods=["GET"] + "/etudiant/ine//formsemestre//groups", methods=["GET"] ) @token_auth.login_required @token_permission_required(Permission.APIView) @@ -413,7 +413,7 @@ def etudiant_groups( formsemestre_id: int, etudid: int = None, nip: int = None, ine: int = None ): """ - Retourne la liste des groupes auxquels appartient l'étudiant dans le semestre indiqué + Retourne la liste des groupes auxquels appartient l'étudiant dans le formsemestre indiqué formsemestre_id : l'id d'un formsemestre etudid : l'etudid d'un étudiant diff --git a/app/api/evaluations.py b/app/api/evaluations.py index 9e0bb43e4..7fb1e6c54 100644 --- a/app/api/evaluations.py +++ b/app/api/evaluations.py @@ -54,7 +54,7 @@ def evaluations(moduleimpl_id: int): return jsonify(data) -@bp.route("/evaluations/eval_notes/", methods=["GET"]) +@bp.route("/evaluation/eval_notes/", methods=["GET"]) @token_auth.login_required @token_permission_required(Permission.APIView) def evaluation_notes(evaluation_id: int): diff --git a/config.py b/config.py index fca2fc514..d78c69a91 100755 --- a/config.py +++ b/config.py @@ -13,7 +13,7 @@ class Config: SQLALCHEMY_DATABASE_URI = None # set in subclass FLASK_ENV = None # # set in subclass - SECRET_KEY = os.environ.get("SECRET_KEY") or "90e01e75831e4176a3c70d29564b425f" + SECRET_KEY = os.environ.get("SECRET_KEY") or "90e01e75831e4276a4c70d29564b425f" SQLALCHEMY_TRACK_MODIFICATIONS = False LOG_TO_STDOUT = os.environ.get("LOG_TO_STDOUT") MAIL_SERVER = os.environ.get("MAIL_SERVER", "localhost") @@ -46,6 +46,7 @@ class Config: class ProdConfig(Config): + "mode production, normalement derrière nginx/gunicorn" FLASK_ENV = "production" DEBUG = False TESTING = False @@ -56,6 +57,7 @@ class ProdConfig(Config): class DevConfig(Config): + "mode développement" FLASK_ENV = "development" DEBUG = True TESTING = False @@ -66,6 +68,7 @@ class DevConfig(Config): class TestConfig(DevConfig): + "Pour les tests unitaires" TESTING = True DEBUG = True SQLALCHEMY_DATABASE_URI = ( @@ -76,6 +79,19 @@ class TestConfig(DevConfig): SECRET_KEY = os.environ.get("TEST_SECRET_KEY") or "c7ecff5db1594c208f573ff30e0f6bca" +class TestAPIConfig(Config): + "Pour les tests de l'API" + FLASK_ENV = "test_api" + TESTING = False + DEBUG = True + SQLALCHEMY_DATABASE_URI = ( + os.environ.get("SCODOC_TEST_API_DATABASE_URI") + or "postgresql:///SCODOC_TEST_API" + ) + DEPT_TEST = "TAPI_" # nom du département, ne pas l'utiliser pour un "vrai" + SECRET_KEY = os.environ.get("TEST_SECRET_KEY") or "c7ecff5db15946789Hhahbh88aja175" + + mode = os.environ.get("FLASK_ENV", "production") if mode == "production": RunningConfig = ProdConfig @@ -83,3 +99,5 @@ elif mode == "development": RunningConfig = DevConfig elif mode == "test": RunningConfig = TestConfig +elif mode == "test_api": + RunningConfig = TestAPIConfig diff --git a/scodoc.py b/scodoc.py index 1d3b17262..01881bf7f 100755 --- a/scodoc.py +++ b/scodoc.py @@ -496,12 +496,11 @@ def clear_cache(sanitize): # clear-cache @app.cli.command() -def init_test_database(): +def init_test_database(): # init-test-database """Initialise les objets en base pour les tests API (à appliquer sur SCODOC_TEST ou SCODOC_DEV) """ click.echo("Initialisation base de test API...") - # import app as mapp # le package app ctx = app.test_request_context() ctx.push() diff --git a/tests/api/dotenv_exemple b/tests/api/dotenv_exemple new file mode 100644 index 000000000..e1857bd81 --- /dev/null +++ b/tests/api/dotenv_exemple @@ -0,0 +1,11 @@ +# Configuration du _client_ test API +# A renommer .env +# and /opt/scodoc/tests/api/ +# et à remplir. + +# URL du serveur ScoDoc à interroger +SCODOC_URL = "http://localhost:5000/" + +# Le client (python) doit-il vérifier le certificat SSL du serveur ? +# ou True si serveur de production avec certif SSL valide +CHECK_CERTIFICATE = False diff --git a/tests/api/setup_test_api.py b/tests/api/setup_test_api.py index a413f6fdc..4a9d45393 100644 --- a/tests/api/setup_test_api.py +++ b/tests/api/setup_test_api.py @@ -1,13 +1,13 @@ # -*- coding: utf-8 -*- -"""Test Logos +"""Test API Utilisation : créer les variables d'environnement: (indiquer les valeurs pour le serveur ScoDoc que vous voulez interroger) export SCODOC_URL="https://scodoc.xxx.net/" - export SCODOC_USER="xxx" + export API_USER="xxx" export SCODOC_PASSWD="xxx" export CHECK_CERTIFICATE=0 # ou 1 si serveur de production avec certif SSL valide @@ -15,23 +15,25 @@ Utilisation : """ import os import requests +from dotenv import load_dotenv +import pytest + +BASEDIR = "/opt/scodoc/tests/api" +load_dotenv(os.path.join(BASEDIR, ".env")) +CHECK_CERTIFICATE = bool(os.environ.get("CHECK_CERTIFICATE", False)) +SCODOC_URL = os.environ["SCODOC_URL"] +API_URL = SCODOC_URL + "/ScoDoc/api" +API_USER = os.environ.get("API_USER", "test") +API_PASSWORD = os.environ.get("API_PASSWD", "test") +print(f"SCODOC_URL={SCODOC_URL}") +print(f"API URL={API_URL}") -SCODOC_USER = "test" -SCODOC_PASSWORD = "test" -SCODOC_URL = "http://192.168.1.12:5000" -CHECK_CERTIFICATE = bool(int(os.environ.get("CHECK_CERTIFICATE", False))) - - -def get_token(): +@pytest.fixture +def api_headers() -> dict: """ - Permet de set le token dans le header + Demande un jeton et renvoie un dict à utiliser dans les en-têtes de requêtes http """ - r0 = requests.post( - SCODOC_URL + "/ScoDoc/api/tokens", auth=(SCODOC_USER, SCODOC_PASSWORD) - ) + r0 = requests.post(API_URL + "/tokens", auth=(API_USER, API_PASSWORD)) token = r0.json()["token"] return {"Authorization": f"Bearer {token}"} - - -HEADERS = get_token() diff --git a/tests/api/test_api_absences.py b/tests/api/test_api_absences.py index 0918af536..a0a90c576 100644 --- a/tests/api/test_api_absences.py +++ b/tests/api/test_api_absences.py @@ -18,63 +18,78 @@ Utilisation : """ import requests -from tests.api.setup_test_api import SCODOC_URL, CHECK_CERTIFICATE, HEADERS +from tests.api.setup_test_api import API_URL, CHECK_CERTIFICATE, api_headers +# Etudiant pour les tests +ETUDID = 1 +INE = "1" +NIP = "1" # absences -def test_absences(): +def test_absences(api_headers): + """ + Route: /absences/etudid/ + """ r = requests.get( - SCODOC_URL + "/ScoDoc/api/absences/etudid/", - headers=HEADERS, + f"{API_URL}/absences/etudid/{ETUDID}", + headers=api_headers, verify=CHECK_CERTIFICATE, ) assert r.status_code == 200 r = requests.get( - SCODOC_URL + "/ScoDoc/api/absences/nip/", - headers=HEADERS, + f"{API_URL}/absences/nip/{NIP}", + headers=api_headers, verify=CHECK_CERTIFICATE, ) assert r.status_code == 200 r = requests.get( - SCODOC_URL + "/ScoDoc/api/absences/ine/", - headers=HEADERS, + f"{API_URL}/absences/ine/{INE}", + headers=api_headers, verify=CHECK_CERTIFICATE, ) assert r.status_code == 200 # absences_justify -def test_absences_justify(): +def test_absences_justify(api_headers): + """ + Route: /absences/etudid//just + """ r = requests.get( - SCODOC_URL + "/ScoDoc/api/absences/etudid/1/just", - headers=HEADERS, + API_URL + f"/absences/etudid/{ETUDID}/just", + headers=api_headers, verify=CHECK_CERTIFICATE, ) assert r.status_code == 200 + # TODO vérifier résultat r = requests.get( - SCODOC_URL + "/ScoDoc/api/absences/nip/1/just", - headers=HEADERS, + API_URL + f"/absences/nip/{NIP}/just", + headers=api_headers, verify=CHECK_CERTIFICATE, ) assert r.status_code == 200 + # TODO vérifier résultat r = requests.get( - SCODOC_URL + "/ScoDoc/api/absences/ine/1/just", - headers=HEADERS, + API_URL + f"/absences/ine/{INE}/just", + headers=api_headers, verify=CHECK_CERTIFICATE, ) assert r.status_code == 200 + # TODO vérifier résultat -# abs_groupe_etat -def test_abs_groupe_etat(): - r = requests.get( - SCODOC_URL - + "/ScoDoc/api/absences/abs_group_etat/?group_id=&date_debut=date_debut&date_fin=date_fin", - headers=HEADERS, - verify=CHECK_CERTIFICATE, - ) - assert r.status_code == 200 +# XXX TODO +# def test_abs_groupe_etat(api_headers): +# """ +# Route: +# """ +# r = requests.get( +# API_URL + "/absences/abs_group_etat/?group_id=&date_debut=date_debut&date_fin=date_fin", +# headers=api_headers, +# verify=CHECK_CERTIFICATE, +# ) +# assert r.status_code == 200 diff --git a/tests/api/test_api_departements.py b/tests/api/test_api_departements.py index 2dab4614f..872aa1044 100644 --- a/tests/api/test_api_departements.py +++ b/tests/api/test_api_departements.py @@ -19,11 +19,12 @@ Utilisation : import requests -from tests.api.setup_test_api import SCODOC_URL, CHECK_CERTIFICATE, HEADERS +from tests.api.setup_test_api import API_URL, CHECK_CERTIFICATE, api_headers from tests.api.tools_test_api import verify_fields -# departements -def test_departements(): + +def test_departements(api_headers): + "check liste de sdépartements" fields = [ "id", "acronym", @@ -33,8 +34,8 @@ def test_departements(): ] r = requests.get( - SCODOC_URL + "/ScoDoc/api/departements", - headers=HEADERS, + API_URL + "/departements", + headers=api_headers, verify=CHECK_CERTIFICATE, ) assert r.status_code == 200 @@ -47,9 +48,8 @@ def test_departements(): assert fields_OK is True -# liste_etudiants -def test_liste_etudiants(): - fields = [ +def test_list_etudiants(api_headers): + fields = { "civilite", "code_ine", "code_nip", @@ -74,11 +74,11 @@ def test_liste_etudiants(): "telephone", "fax", "description", - ] + } r = requests.get( - SCODOC_URL + "/ScoDoc/api/departements/TAPI/etudiants/list", - headers=HEADERS, + API_URL + "/departements/TAPI/etudiants/list", + headers=api_headers, verify=CHECK_CERTIFICATE, ) @@ -91,8 +91,8 @@ def test_liste_etudiants(): assert fields_OK is True r = requests.get( - SCODOC_URL + "/ScoDoc/api/departements/TAPI/etudiants/list/1", - headers=HEADERS, + API_URL + "/departements/TAPI/etudiants/list/1", + headers=api_headers, verify=CHECK_CERTIFICATE, ) @@ -106,7 +106,7 @@ def test_liste_etudiants(): # liste_semestres_courant -def test_semestres_courant(): +def test_semestres_courant(api_headers): fields = [ "titre", "gestion_semestrielle", @@ -136,8 +136,8 @@ def test_semestres_courant(): ] r = requests.get( - SCODOC_URL + "/ScoDoc/api/departements/TAPI/semestres_courants", - headers=HEADERS, + API_URL + "/departements/TAPI/semestres_courants", + headers=api_headers, verify=CHECK_CERTIFICATE, ) @@ -148,14 +148,3 @@ def test_semestres_courant(): assert r.status_code == 200 assert len(r.json()) == 1 assert fields_OK is True - - -# referenciel_competences -def test_referenciel_competences(): - r = requests.get( - SCODOC_URL - + "/ScoDoc/api/departements/TAPI/formations/1/referentiel_competences", - headers=HEADERS, - verify=CHECK_CERTIFICATE, - ) - assert r.status_code == 200 or 204 diff --git a/tests/api/test_api_etudiants.py b/tests/api/test_api_etudiants.py index c0314a1e6..ec6a23d18 100644 --- a/tests/api/test_api_etudiants.py +++ b/tests/api/test_api_etudiants.py @@ -20,12 +20,14 @@ from random import randint import requests -from tests.api.setup_test_api import SCODOC_URL, CHECK_CERTIFICATE, HEADERS +from tests.api.setup_test_api import API_URL, CHECK_CERTIFICATE, api_headers from tests.api.tools_test_api import verify_fields # etudiants_courant -def test_etudiants_courant(): - +def test_etudiants_courant(api_headers): + """ + Route: /etudiants/courant + """ fields = [ "id", "nip", @@ -36,21 +38,21 @@ def test_etudiants_courant(): ] r = requests.get( - SCODOC_URL + "/ScoDoc/api/etudiants/courant", - headers=HEADERS, + API_URL + "/etudiants/courant", + headers=api_headers, verify=CHECK_CERTIFICATE, ) assert r.status_code == 200 - assert len(r.json()) == 16 + etudiants = r.json() + assert len(etudiants) == 16 # XXX HARDCODED - # Choisis aléatoirement un étudiant dans la liste des étudiants - etu = r.json()[randint(0, len(r.json())) - 1] + etud = etudiants[-1] - fields_OK = verify_fields(etu, fields) + fields_ok = verify_fields(etud, fields) - assert fields_OK is True + assert fields_ok is True - ########## Version long################ + ########## Version long ################ fields_long = [ "civilite", @@ -80,24 +82,24 @@ def test_etudiants_courant(): ] r = requests.get( - SCODOC_URL + "/ScoDoc/api/etudiants/courant/long", - headers=HEADERS, + API_URL + "/etudiants/courant/long", + headers=api_headers, verify=CHECK_CERTIFICATE, ) assert r.status_code == 200 - assert len(r.json()) == 16 + etudiants = r.json() + assert len(etudiants) == 16 # HARDCODED - # Choisis aléatoirement un étudiant dans la liste des étudiants - etu = r.json()[randint(0, len(r.json())) - 1] + etud = etudiants[-1] + fields_ok = verify_fields(etud, fields_long) - fields_OK = verify_fields(etu, fields_long) - - assert fields_OK is True + assert fields_ok is True -# etudiant -def test_etudiant(): - +def test_etudiant(api_headers): + """ + Route: + """ fields = [ "civilite", "code_ine", @@ -126,57 +128,49 @@ def test_etudiant(): ] ######### Test etudid ######### - r = requests.get( - SCODOC_URL + "/ScoDoc/api/etudiant/etudid/1", - headers=HEADERS, + API_URL + "/etudiant/etudid/1", + headers=api_headers, verify=CHECK_CERTIFICATE, ) assert r.status_code == 200 - assert len(r.json()) == 24 + etud = r.json() + assert len(etud) == 24 # ? HARDCODED - etu = r.json() - - fields_OK = verify_fields(etu, fields) - - assert fields_OK is True + fields_ok = verify_fields(etud, fields) + assert fields_ok is True ######### Test code nip ######### r = requests.get( - SCODOC_URL + "/ScoDoc/api/etudiant/nip/1", - headers=HEADERS, + API_URL + "/etudiant/nip/1", + headers=api_headers, verify=CHECK_CERTIFICATE, ) assert r.status_code == 200 - assert len(r.json()) == 24 - - etu = r.json() - - fields_OK = verify_fields(etu, fields) - - assert fields_OK is True + etud = r.json() + assert len(etud) == 24 + fields_ok = verify_fields(etud, fields) + assert fields_ok is True ######### Test code ine ######### r = requests.get( - SCODOC_URL + "/ScoDoc/api/etudiant/ine/1", - headers=HEADERS, + API_URL + "/etudiant/ine/1", + headers=api_headers, verify=CHECK_CERTIFICATE, ) assert r.status_code == 200 - assert len(r.json()) == 24 - - etu = r.json() - - fields_OK = verify_fields(etu, fields) - - assert fields_OK is True + etud = r.json() + assert len(etud) == 24 + fields_ok = verify_fields(etud, fields) + assert fields_ok is True -# etudiant_formsemestres -def test_etudiant_formsemestres(): - +def test_etudiant_formsemestres(api_headers): + """ + Route: /etudiant/etudid//formsemestres + """ fields = [ "date_fin", "resp_can_edit", @@ -208,89 +202,92 @@ def test_etudiant_formsemestres(): ######### Test etudid ######### r = requests.get( - SCODOC_URL + "/ScoDoc/api/etudiant/etudid/1/formsemestres", - headers=HEADERS, + API_URL + "/etudiant/etudid/1/formsemestres", + headers=api_headers, verify=CHECK_CERTIFICATE, ) assert r.status_code == 200 - assert len(r.json()) == 1 + formsemestres = r.json() + assert len(formsemestres) == 1 - formsemestre = r.json()[0] + formsemestre = formsemestres[0] - fields_OK = verify_fields(formsemestre, fields) - - assert fields_OK is True + fields_ok = verify_fields(formsemestre, fields) + assert fields_ok is True ######### Test code nip ######### - r = requests.get( - SCODOC_URL + "/ScoDoc/api/etudiant/nip/1/formsemestres", - headers=HEADERS, + API_URL + "/etudiant/nip/1/formsemestres", + headers=api_headers, verify=CHECK_CERTIFICATE, ) assert r.status_code == 200 - assert len(r.json()) == 1 + formsemestres = r.json() + assert len(formsemestres) == 1 - formsemestre = r.json()[0] + formsemestre = formsemestres[0] - fields_OK = verify_fields(formsemestre, fields) - - assert fields_OK is True + fields_ok = verify_fields(formsemestre, fields) + assert fields_ok is True ######### Test code ine ######### - r = requests.get( - SCODOC_URL + "/ScoDoc/api/etudiant/ine/1/formsemestres", - headers=HEADERS, + API_URL + "/etudiant/ine/1/formsemestres", + headers=api_headers, verify=CHECK_CERTIFICATE, ) assert r.status_code == 200 - assert len(r.json()) == 1 + formsemestres = r.json() + assert len(formsemestres) == 1 - formsemestre = r.json()[0] + formsemestre = formsemestres[0] - fields_OK = verify_fields(formsemestre, fields) - - assert fields_OK is True + fields_ok = verify_fields(formsemestre, fields) + assert fields_ok is True -# etudiant_bulletin_semestre -def test_etudiant_bulletin_semestre(): - +def test_etudiant_bulletin_semestre(api_headers): + """ + Route: /etudiant/etudid//formsemestre//bulletin + """ ######### Test etudid ######### r = requests.get( - SCODOC_URL + "/ScoDoc/api/etudiant/etudid/1/formsemestre/1/bulletin", - headers=HEADERS, + API_URL + "/etudiant/etudid/1/formsemestre/1/bulletin", + headers=api_headers, verify=CHECK_CERTIFICATE, ) assert r.status_code == 200 - assert len(r.json()) == 13 + bul = r.json() + assert len(bul) == 13 # HARDCODED ######### Test code nip ######### r = requests.get( - SCODOC_URL + "/ScoDoc/api/etudiant/nip/1/formsemestre/1/bulletin", - headers=HEADERS, + API_URL + "/etudiant/nip/1/formsemestre/1/bulletin", + headers=api_headers, verify=CHECK_CERTIFICATE, ) assert r.status_code == 200 - assert len(r.json()) == 13 + bul = r.json() + assert len(bul) == 13 # HARDCODED ######### Test code ine ######### - r = requests.get( - SCODOC_URL + "/ScoDoc/api/etudiant/ine/1/formsemestre/1/bulletin", - headers=HEADERS, + API_URL + "/etudiant/ine/1/formsemestre/1/bulletin", + headers=api_headers, verify=CHECK_CERTIFICATE, ) assert r.status_code == 200 - assert len(r.json()) == 13 + bul = r.json() + assert len(bul) == 13 # HARDCODED -# etudiant_groups -def test_etudiant_groups(): - +def test_etudiant_groups(api_headers): + """ + Route: + /etudiant/etudid//formsemestre//groups + """ fields = [ "partition_id", "id", @@ -306,47 +303,39 @@ def test_etudiant_groups(): ######### Test etudid ######### r = requests.get( - SCODOC_URL + "/ScoDoc/api/etudiant/etudid/1/semestre/1/groups", - headers=HEADERS, + API_URL + "/etudiant/etudid/1/formsemestre/1/groups", + headers=api_headers, verify=CHECK_CERTIFICATE, ) assert r.status_code == 200 - assert len(r.json()) == 1 - - groups = r.json()[0] - - fields_OK = verify_fields(groups, fields) - - assert fields_OK is True + groups = r.json() + assert len(groups) == 1 # dans un seul groupe + group = groups[0] + fields_ok = verify_fields(group, fields) + assert fields_ok is True ######### Test code nip ######### - r = requests.get( - SCODOC_URL + "/ScoDoc/api/etudiant/nip/1/semestre/1/groups", - headers=HEADERS, + API_URL + "/etudiant/nip/1/formsemestre/1/groups", + headers=api_headers, verify=CHECK_CERTIFICATE, ) assert r.status_code == 200 - assert len(r.json()) == 1 - - groups = r.json()[0] - - fields_OK = verify_fields(groups, fields) - - assert fields_OK is True + groups = r.json() + assert len(groups) == 1 # dans un seul groupe + group = groups[0] + fields_ok = verify_fields(group, fields) + assert fields_ok is True ######### Test code ine ######### - r = requests.get( - SCODOC_URL + "/ScoDoc/api/etudiant/ine/1/semestre/1/groups", - headers=HEADERS, + API_URL + "/etudiant/ine/1/formsemestre/1/groups", + headers=api_headers, verify=CHECK_CERTIFICATE, ) assert r.status_code == 200 - assert len(r.json()) == 1 - - groups = r.json()[0] - - fields_OK = verify_fields(groups, fields) - - assert fields_OK is True + groups = r.json() + assert len(groups) == 1 # dans un seul groupe + group = groups[0] + fields_ok = verify_fields(group, fields) + assert fields_ok is True diff --git a/tests/api/test_api_evaluations.py b/tests/api/test_api_evaluations.py index 1fb6ffbdf..976eef61c 100644 --- a/tests/api/test_api_evaluations.py +++ b/tests/api/test_api_evaluations.py @@ -19,23 +19,31 @@ Utilisation : import requests -from tests.api.setup_test_api import SCODOC_URL, CHECK_CERTIFICATE, HEADERS +from tests.api.setup_test_api import API_URL, CHECK_CERTIFICATE, api_headers -# evaluations -def test_evaluations(): + +def test_evaluations(api_headers): + """ + Route: /evaluation/ + """ r = requests.get( - SCODOC_URL + "/ScoDoc/api/evaluations/1", - headers=HEADERS, + API_URL + "/evaluations/1", + headers=api_headers, verify=CHECK_CERTIFICATE, ) assert r.status_code == 200 + # TODO -# evaluation_notes -def test_evaluation_notes(): - r = requests.get( - SCODOC_URL + "/ScoDoc/api/evaluations/eval_notes/1", - headers=HEADERS, - verify=CHECK_CERTIFICATE, - ) - assert r.status_code == 200 +# TODO car pas d'évaluations créées à ce stade +# def test_evaluation_notes(api_headers): +# """ +# Route: /evaluation/eval_notes/ +# """ +# r = requests.get( +# API_URL + "/evaluation/eval_notes/1", +# headers=api_headers, +# verify=CHECK_CERTIFICATE, +# ) +# assert r.status_code == 200 +# # TODO diff --git a/tests/api/test_api_formations.py b/tests/api/test_api_formations.py index 29f0301ff..b8f159ada 100644 --- a/tests/api/test_api_formations.py +++ b/tests/api/test_api_formations.py @@ -19,43 +19,33 @@ Utilisation : import requests -from tests.api.setup_test_api import SCODOC_URL, CHECK_CERTIFICATE, HEADERS +from tests.api.setup_test_api import API_URL, CHECK_CERTIFICATE, api_headers from tests.api.tools_test_api import verify_fields # formations -def test_formations(): - fields = [ - "id", - "acronyme", - "titre_officiel", - "formation_code", - "code_specialite", - "dept_id", - "titre", - "version", - "type_parcours", - "referentiel_competence_id", - "formation_id", - ] - +def test_formations_ids(api_headers): + """ + Route: /formations_ids + """ r = requests.get( - SCODOC_URL + "/ScoDoc/api/formations", - headers=HEADERS, + API_URL + "/formations_ids", + headers=api_headers, verify=CHECK_CERTIFICATE, ) - - formation = r.json()[0] - - fields_OK = verify_fields(formation, fields) - assert r.status_code == 200 - assert len(r.json()) == 1 - assert fields_OK is True + formations_ids = r.json() + # Une liste non vide d'entiers + assert isinstance(formations_ids, list) + assert len(formations_ids) > 0 + assert all(isinstance(x, int) for x in formations_ids) # formations_by_id -def test_formations_by_id(): +def test_formations_by_id(api_headers): + """ + Route: /formations/ + """ fields = [ "id", "acronyme", @@ -71,21 +61,22 @@ def test_formations_by_id(): ] r = requests.get( - SCODOC_URL + "/ScoDoc/api/formations/1", - headers=HEADERS, + API_URL + "/formations/1", + headers=api_headers, verify=CHECK_CERTIFICATE, ) - + assert r.status_code == 200 formation = r.json() - fields_OK = verify_fields(formation, fields) - - assert r.status_code == 200 - assert fields_OK is True + fields_ok = verify_fields(formation, fields) + assert fields_ok is True + # TODO tester le contenu de certains champs -# formation_export_by_formation_id -def test_formation_export_by_formation_id(): +def test_formation_export(api_headers): + """ + Route: /formations/formation_export/ + """ fields = [ "id", "acronyme", @@ -101,32 +92,33 @@ def test_formation_export_by_formation_id(): "ue", ] r = requests.get( - SCODOC_URL + "/ScoDoc/api/formations/formation_export/1", - headers=HEADERS, + API_URL + "/formations/formation_export/1", + headers=api_headers, verify=CHECK_CERTIFICATE, ) + assert r.status_code == 200 export_formation = r.json() - fields_OK = verify_fields(export_formation, fields) - - assert r.status_code == 200 - assert fields_OK is True + fields_ok = verify_fields(export_formation, fields) + assert fields_ok is True + # TODO tester le contenu de certains champs -# formsemestre_apo -# def test_formsemestre_apo(): +# TODO +# def test_formsemestre_apo(api_headers): # r = requests.get( -# SCODOC_URL + "/ScoDoc/api/formations/apo/", -# headers=HEADERS, +# API_URL + "/formations/apo/", +# headers=api_headers, # verify=CHECK_CERTIFICATE, # ) # assert r.status_code == 200 -# moduleimpl -def test_moduleimpl(): - +def test_moduleimpl(api_headers): + """ + Route: /formations/moduleimpl/ + """ fields = [ "id", "formsemestre_id", @@ -139,22 +131,22 @@ def test_moduleimpl(): ] r = requests.get( - SCODOC_URL + "/ScoDoc/api/formations/moduleimpl/1", - headers=HEADERS, + API_URL + "/formations/moduleimpl/1", + headers=api_headers, verify=CHECK_CERTIFICATE, ) - + assert r.status_code == 200 moduleimpl = r.json() - fields_OK = verify_fields(moduleimpl, fields) - - assert r.status_code == 200 - assert fields_OK is True + fields_ok = verify_fields(moduleimpl, fields) + assert fields_ok is True + # TODO tester le contenu de certains champs -# moduleimpls_sem -def test_moduleimpls_sem(): - +def test_moduleimpls_sem(api_headers): + """ + Route: /formations/moduleimpl/formsemestre//list + """ fields = [ "id", "formsemestre_id", @@ -168,14 +160,27 @@ def test_moduleimpls_sem(): "ens", ] r = requests.get( - SCODOC_URL + "/ScoDoc/api/formations/moduleimpl/formsemestre/1/list", - headers=HEADERS, + API_URL + "/formations/moduleimpl/formsemestre/1/list", + headers=api_headers, verify=CHECK_CERTIFICATE, ) - moduleimpl = r.json()[0] - - fields_OK = verify_fields(moduleimpl, fields) - assert r.status_code == 200 - assert len(r.json()) == 21 - assert fields_OK is True + moduleimpls = r.json() + moduleimpl = moduleimpls[0] + + fields_ok = verify_fields(moduleimpl, fields) + assert len(moduleimpls) == 21 # XXX HARDCODED ! + assert fields_ok is True + + +def test_referentiel_competences(api_headers): + """ + Route: "/formations//referentiel_competences", + """ + r = requests.get( + API_URL + "/formations/1/referentiel_competences", + headers=api_headers, + verify=CHECK_CERTIFICATE, + ) + assert r.status_code == 200 + # XXX A compléter diff --git a/tests/api/test_api_formsemestre.py b/tests/api/test_api_formsemestre.py index ac84cbafd..724713a10 100644 --- a/tests/api/test_api_formsemestre.py +++ b/tests/api/test_api_formsemestre.py @@ -19,14 +19,17 @@ Utilisation : import requests -from tests.api.setup_test_api import SCODOC_URL, CHECK_CERTIFICATE, HEADERS +from tests.api.setup_test_api import API_URL, CHECK_CERTIFICATE, api_headers from tests.api.tools_test_api import verify_fields -# formsemestre -def test_formsemestre(): + +def test_formsemestre(api_headers): + """ + Route: + """ r = requests.get( - SCODOC_URL + "/ScoDoc/api/formsemestre/1", - headers=HEADERS, + API_URL + "/formsemestre/1", + headers=api_headers, verify=CHECK_CERTIFICATE, ) assert r.status_code == 200 @@ -61,40 +64,44 @@ def test_formsemestre(): "responsables", ] - fields_OK = verify_fields(formsemestre, fields) + fields_ok = verify_fields(formsemestre, fields) - assert fields_OK is True + assert fields_ok is True -# etudiant_bulletin -def test_etudiant_bulletin(): +def test_etudiant_bulletin(api_headers): + """ + Route: + """ r = requests.get( - SCODOC_URL + "/ScoDoc/api/formsemestre/1/etudiant/etudid/1/bulletin", - headers=HEADERS, + API_URL + "/formsemestre/1/etudiant/etudid/1/bulletin", + headers=api_headers, verify=CHECK_CERTIFICATE, ) assert r.status_code == 200 r = requests.get( - SCODOC_URL + "/ScoDoc/api/formsemestre/1/etudiant/nip/1/bulletin", - headers=HEADERS, + API_URL + "/formsemestre/1/etudiant/nip/1/bulletin", + headers=api_headers, verify=CHECK_CERTIFICATE, ) assert r.status_code == 200 r = requests.get( - SCODOC_URL + "/ScoDoc/api/formsemestre/1/etudiant/ine/1/bulletin", - headers=HEADERS, + API_URL + "/formsemestre/1/etudiant/ine/1/bulletin", + headers=api_headers, verify=CHECK_CERTIFICATE, ) assert r.status_code == 200 -# bulletins -def test_bulletins(): +def test_bulletins(api_headers): + """ + Route: + """ r = requests.get( - SCODOC_URL + "/ScoDoc/api/formsemestre/1/bulletins", - headers=HEADERS, + API_URL + "/formsemestre/1/bulletins", + headers=api_headers, verify=CHECK_CERTIFICATE, ) assert r.status_code == 200 @@ -103,15 +110,17 @@ def test_bulletins(): # # jury # def test_jury(): # r = requests.get( -# SCODOC_URL + "/ScoDoc/api/formsemestre/1/jury", -# headers=HEADERS, +# API_URL + "/formsemestre/1/jury", +# headers=api_headers, # verify=CHECK_CERTIFICATE, # ) # assert r.status_code == 200 -# semestre_index -def test_semestre_index(): - +# TODO A revoir +def test_semestre_index(api_headers): + """ + Route: TODO + """ ue_fields = [ "semestre_idx", "type", @@ -170,8 +179,8 @@ def test_semestre_index(): ] r = requests.get( - SCODOC_URL + "/ScoDoc/api/formsemestre/1/programme", - headers=HEADERS, + API_URL + "/formsemestre/1/programme", + headers=api_headers, verify=CHECK_CERTIFICATE, ) assert r.status_code == 200 diff --git a/tests/api/test_api_jury.py b/tests/api/test_api_jury.py index e9be6a017..13b0a0f1e 100644 --- a/tests/api/test_api_jury.py +++ b/tests/api/test_api_jury.py @@ -19,37 +19,41 @@ Utilisation : import requests -from tests.api.setup_test_api import SCODOC_URL, CHECK_CERTIFICATE, HEADERS +from tests.api.setup_test_api import API_URL, CHECK_CERTIFICATE, api_headers -# jury_preparation -def test_jury_preparation(): + +def test_jury_preparation(api_headers): + """ + Route: + """ r = requests.get( SCODOC_URL + "/ScoDoc/api/jury/formsemestre//preparation_jury", - headers=HEADERS, + headers=api_headers, verify=CHECK_CERTIFICATE, ) assert r.status_code == 200 -# jury_decisions -def test_jury_decisions(): +def test_jury_decisions(api_headers): + """ + Route: + """ r = requests.get( - SCODOC_URL - + "/ScoDoc/api/jury/formsemestre//decisions_jury", - headers=HEADERS, + API_URL + "/jury/formsemestre//decisions_jury", + headers=api_headers, verify=CHECK_CERTIFICATE, ) assert r.status_code == 200 # set_decision_jury -def test_set_decision_jury(): +def test_set_decision_jury(api_headers): r = requests.get( SCODOC_URL + "/ScoDoc/api/jury/set_decision/etudid?etudid=&formsemestre_id=" "&jury=&devenir=&assiduite=", - headers=HEADERS, + headers=api_headers, verify=CHECK_CERTIFICATE, ) assert r.status_code == 200 @@ -58,7 +62,7 @@ def test_set_decision_jury(): SCODOC_URL + "/ScoDoc/api/jury/set_decision/nip?etudid=&formsemestre_id=" "&jury=&devenir=&assiduite=", - headers=HEADERS, + headers=api_headers, verify=CHECK_CERTIFICATE, ) assert r.status_code == 200 @@ -67,18 +71,20 @@ def test_set_decision_jury(): SCODOC_URL + "/ScoDoc/api/jury/set_decision/ine?etudid=&formsemestre_id=" "&jury=&devenir=&assiduite=", - headers=HEADERS, + headers=api_headers, verify=CHECK_CERTIFICATE, ) assert r.status_code == 200 -# annule_decision_jury -def test_annule_decision_jury(): +def test_annule_decision_jury(api_headers): + """ + Route: + """ r = requests.get( SCODOC_URL + "/ScoDoc/api/jury/etudid//formsemestre//annule_decision", - headers=HEADERS, + headers=api_headers, verify=CHECK_CERTIFICATE, ) assert r.status_code == 200 @@ -86,7 +92,7 @@ def test_annule_decision_jury(): r = requests.get( SCODOC_URL + "/ScoDoc/api/jury/nip//formsemestre//annule_decision", - headers=HEADERS, + headers=api_headers, verify=CHECK_CERTIFICATE, ) assert r.status_code == 200 @@ -94,7 +100,7 @@ def test_annule_decision_jury(): r = requests.get( SCODOC_URL + "/ScoDoc/api/jury/ine//formsemestre//annule_decision", - headers=HEADERS, + headers=api_headers, verify=CHECK_CERTIFICATE, ) assert r.status_code == 200 diff --git a/tests/api/test_api_logos.py b/tests/api/test_api_logos.py index 0e2250ffd..9e3ba5a31 100644 --- a/tests/api/test_api_logos.py +++ b/tests/api/test_api_logos.py @@ -5,11 +5,16 @@ """Exemple utilisation API ScoDoc 9 avec jeton obtenu par basic authentication utilisation: - à faire fonctionner en environnment de test (FLASK_ENV=test dans le fichier .env) + à faire fonctionner en environnment de test (FLASK_ENV=test_api dans le fichier .env) pytest tests/api/test_api_logos.py """ -from tests.api.setup_test_api import SCODOC_URL, CHECK_CERTIFICATE, HEADERS + +# XXX TODO +# Ce test a une logique très différente des autres : A UNIFIER + + +from tests.api.setup_test_api import API_URL from scodoc import app from tests.unit.config_test_logos import ( @@ -22,35 +27,47 @@ from tests.unit.config_test_logos import ( def test_super_access(create_super_token): + """ + Route: + """ dept1, dept2, dept3, token = create_super_token - HEADERS = {"Authorization": f"Bearer {token}"} + headers = {"Authorization": f"Bearer {token}"} with app.test_client() as client: - response = client.get("/ScoDoc/api/logos", headers=HEADERS) + response = client.get(API_URL + "/logos", headers=headers) assert response.status_code == 200 assert response.json is not None def test_admin_access(create_admin_token): + """ + Route: + """ dept1, dept2, dept3, token = create_admin_token headers = {"Authorization": f"Bearer {token}"} with app.test_client() as client: - response = client.get("/ScoDoc/api/logos", headers=headers) + response = client.get(API_URL + "/logos", headers=headers) assert response.status_code == 401 def test_lambda_access(create_lambda_token): + """ + Route: + """ dept1, dept2, dept3, token = create_lambda_token headers = {"Authorization": f"Bearer {token}"} with app.test_client() as client: - response = client.get("/ScoDoc/api/logos", headers=headers) + response = client.get(API_URL + "/logos", headers=headers) assert response.status_code == 401 def test_initial_with_header_and_footer(create_super_token): + """ + Route: + """ dept1, dept2, dept3, token = create_super_token headers = {"Authorization": f"Bearer {token}"} with app.test_client() as client: - response = client.get("/ScoDoc/api/logos", headers=headers) + response = client.get(API_URL + "/logos", headers=headers) assert response.status_code == 200 assert response.json is not None assert len(response.json) == 7 diff --git a/tests/api/test_api_partitions.py b/tests/api/test_api_partitions.py index f7af82b0a..0ceb4e339 100644 --- a/tests/api/test_api_partitions.py +++ b/tests/api/test_api_partitions.py @@ -19,12 +19,14 @@ Utilisation : import requests -from tests.api.setup_test_api import SCODOC_URL, CHECK_CERTIFICATE, HEADERS +from tests.api.setup_test_api import API_URL, CHECK_CERTIFICATE, api_headers from tests.api.tools_test_api import verify_fields -# partition -def test_partition(): +def test_partition(api_headers): + """ + Route: + """ fields = [ "partition_id", "id", @@ -36,23 +38,22 @@ def test_partition(): ] r = requests.get( - SCODOC_URL + "/ScoDoc/api/partitions/1", - headers=HEADERS, + API_URL + "/partitions/1", + headers=api_headers, verify=CHECK_CERTIFICATE, ) - - partition = r.json()[0] - - fields_OK = verify_fields(partition, fields) - assert r.status_code == 200 - assert len(r.json()) == 2 - assert fields_OK is True + partitions = r.json() + assert len(partitions) == 1 + partition = partitions[0] + fields_ok = verify_fields(partition, fields) + assert fields_ok is True -# etud_in_group -def test_etud_in_group(): - +def test_etud_in_group(api_headers): + """ + Route: + """ fields = [ "etudid", "id", @@ -92,33 +93,36 @@ def test_etud_in_group(): ] r = requests.get( - SCODOC_URL + "/ScoDoc/api/partitions/groups/1", - headers=HEADERS, + API_URL + "/partitions/groups/1", + headers=api_headers, verify=CHECK_CERTIFICATE, ) etu = r.json()[0] - fields_OK = verify_fields(etu, fields) + fields_ok = verify_fields(etu, fields) assert r.status_code == 200 assert len(r.json()) == 16 - assert fields_OK is True + assert fields_ok is True # r = requests.get( - # SCODOC_URL + "/ScoDoc/api/partitions/groups/1/etat/", - # headers=HEADERS, + # API_URL + "/partitions/groups/1/etat/", + # headers=api_headers, # verify=CHECK_CERTIFICATE, # ) # assert r.status_code == 200 # # set_groups -# def test_set_groups(): +# def test_set_groups(api_headers): +# """ +# Route: +# """ # r = requests.get( # SCODOC_URL # + "/partitions/set_groups/partition//groups/" # "/delete//create/", -# headers=HEADERS, +# headers=api_headers, # verify=CHECK_CERTIFICATE, # ) # assert r.status_code == 200 diff --git a/tests/api/tools_test_api.py b/tests/api/tools_test_api.py index 4501cd3cc..d0a224c50 100644 --- a/tests/api/tools_test_api.py +++ b/tests/api/tools_test_api.py @@ -1,9 +1,13 @@ -def verify_fields(json_response, fields): +"""Utilitaires pour les tests de l'API +""" + + +def verify_fields(json_response: dict, fields: set) -> bool: """ - Vérifie si les champs de la réponse json sont corrects + Vérifie si les champs attendu de la réponse json sont présents json_response : la réponse de la requête - fields : une liste avec l'ensemble des champs à vérifier + fields : ensemble des champs à vérifier Retourne True ou False """ diff --git a/tools/create_database.sh b/tools/create_database.sh index 1fd4599f0..e1d7ffb95 100755 --- a/tools/create_database.sh +++ b/tools/create_database.sh @@ -9,8 +9,16 @@ die() { echo exit 1 } -[ $# = 1 ] || die "Usage $0 db_name" -db_name="$1" +[ $# = 1 ] || [ $# = 2 ] || die "Usage $0 [--drop] db_name" + +if [ "$1" = "--drop" ] +then + db_name="$2" + echo "Dropping database $db_name..." + dropdb "$db_name" +else + db_name="$1" +fi # Le répertoire de ce script: SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" diff --git a/tools/fakedatabase/create_test_api_database.py b/tools/fakedatabase/create_test_api_database.py index aba8cec76..ad1198f81 100644 --- a/tools/fakedatabase/create_test_api_database.py +++ b/tools/fakedatabase/create_test_api_database.py @@ -5,60 +5,59 @@ Création des départements, formations, semestres, étudiants, groupes... utilisation: - 1) modifier le .env pour indiquer - SCODOC_DATABASE_URI="postgresql:///SCO_TEST_API" + 1) modifier /opt/scodoc/.env pour indiquer + FLASK_ENV=test_api + FLASK_DEBUG=1 2) En tant qu'utilisateur scodoc, lancer: - tools/create_database.sh SCO_TEST_API + tools/create_database.sh SCODOC_TEST_API flask db upgrade flask sco-db-init --erase flask init-test-database - flask user-role -a Admin -d TAPI test - flask user-password test - flask create-role APIUserViewer - flask edit-role APIUserViewer -a APIView - flask user-role test -a APIUserViewer + 3) relancer ScoDoc: flask run --host 0.0.0.0 - 4) lancer client de test (ou vérifier dans le navigateur) + 4) lancer client de test """ import datetime import random +import sys -random.seed(12345678) # tests reproductibles - -from flask_login import login_user - -from app import auth +from app.auth.models import Role, User from app import models +from app.models import Departement, Formation, FormSemestre from app import db from app.scodoc import ( + sco_cache, + sco_evaluation_db, sco_formations, - sco_formsemestre, sco_formsemestre_inscriptions, sco_groups, ) +from app.scodoc.sco_permissions import Permission from tools.fakeportal.gen_nomprenoms import nomprenom +random.seed(12345678) # tests reproductibles + # La formation à utiliser: FORMATION_XML_FILENAME = "tests/ressources/formations/scodoc_formation_RT_BUT_RT_v1.xml" -def init_departement(acronym): +def init_departement(acronym: str) -> Departement: "Create dept, and switch context into it." import app as mapp - dept = models.Departement(acronym=acronym) + dept = Departement(acronym=acronym) db.session.add(dept) mapp.set_sco_dept(acronym) db.session.commit() return dept -def import_formation() -> models.Formation: +def import_formation() -> Formation: """Import formation from XML. Returns formation_id """ @@ -66,20 +65,37 @@ def import_formation() -> models.Formation: doc = f.read() # --- Création de la formation f = sco_formations.formation_import_xml(doc) - return models.Formation.query.get(f[0]) + return Formation.query.get(f[0]) -def create_user(dept): - """créé les utilisaterurs nécessaires aux tests""" - user = auth.models.User( - user_name="test", nom="Doe", prenom="John", dept=dept.acronym - ) +def create_users(dept: Departement) -> tuple: + """créé les utilisateurs nécessaires aux tests""" + # Un utilisateur "test" (passwd test) pouvant lire l'API + user = User(user_name="test", nom="Doe", prenom="John", dept=dept.acronym) + user.set_password("test") db.session.add(user) + + # Le rôle standard LecteurAPI existe déjà + role = Role.query.filter_by(name="LecteurAPI").first() + if role is None: + print("Erreur: rôle LecteurAPI non existant") + sys.exit(1) + perm_api_view = Permission.get_by_name("APIView") + role.add_permission(perm_api_view) + db.session.add(role) + + user.add_role(role, 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) + db.session.commit() - return user + return user, other -def create_fake_etud(dept): +def create_fake_etud(dept: Departement) -> models.Identite: """Créé un faux étudiant et l'insère dans la base""" civilite = random.choice(("M", "F", "X")) nom, prenom = nomprenom(civilite) @@ -100,14 +116,18 @@ def create_fake_etud(dept): return etud -def create_etuds(dept, nb=16): +def create_etuds(dept: Departement, nb=16) -> list: "create nb etuds" return [create_fake_etud(dept) for _ in range(nb)] -def create_formsemestre(formation, user, semestre_idx=1): - """Create formsemestre and moduleimpls""" - formsemestre = models.FormSemestre( +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", @@ -121,7 +141,9 @@ def create_formsemestre(formation, user, semestre_idx=1): # 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=user.id + module_id=module.id, + formsemestre_id=formsemestre.id, + responsable_id=responsable.id, ) db.session.add(modimpl) db.session.commit() @@ -132,7 +154,7 @@ def create_formsemestre(formation, user, semestre_idx=1): return formsemestre -def inscrit_etudiants(etuds, formsemestre): +def inscrit_etudiants(etuds: list, formsemestre: FormSemestre): """Inscrit les etudiants aux semestres et à tous ses modules""" for etud in etuds: sco_formsemestre_inscriptions.do_formsemestre_inscription_with_modules( @@ -144,14 +166,38 @@ def inscrit_etudiants(etuds, formsemestre): ) -def init_test_database(): +def create_evaluations(formsemestre: FormSemestre): + "creation d'une evaluation dans cahque modimpl du semestre" + for modimpl in formsemestre.modimpls: + args = { + "moduleimpl_id": modimpl.id, + "jour": None, + "heure_debut": "8h00", + "heure_fin": "9h00", + "description": None, + "note_max": 20, + "coefficient": 1.0, + "visibulletin": True, + "publish_incomplete": True, + "evaluation_type": None, + "numero": None, + } + evaluation_id = sco_evaluation_db.do_evaluation_create(**args) + +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 + """ dept = init_departement("TAPI") - user = create_user(dept) - etuds = create_etuds(dept) - formation = import_formation() - formsemestre = create_formsemestre(formation, user) - inscrit_etudiants(etuds, formsemestre) + user_lecteur, user_autre = create_users(dept) + with sco_cache.DeferredSemCacheManager(): + etuds = create_etuds(dept) + formation = import_formation() + formsemestre = create_formsemestre(formation, user_lecteur) + create_evaluations(formsemestre) + inscrit_etudiants(etuds, formsemestre) # à compléter # - groupes # - absences From 443eb72687e99edd6bf9ef7941005909beaf0054 Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Wed, 4 May 2022 23:12:03 +0200 Subject: [PATCH 16/32] API: test des permissions de toutes les routes GET (sauf logos) --- tests/api/test_api_permissions.py | 98 +++++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 tests/api/test_api_permissions.py diff --git a/tests/api/test_api_permissions.py b/tests/api/test_api_permissions.py new file mode 100644 index 000000000..aa80f36fb --- /dev/null +++ b/tests/api/test_api_permissions.py @@ -0,0 +1,98 @@ +# -*- coding: utf-8 -*- + +"""Test permissions + + On a deux utilisateurs dans la base test API: + - "test", avec le rôle LecteurAPI qui a APIView, + - et "other", qui n'a aucune permission. + + + Lancer : + pytest tests/api/test_api_permissions.py +""" + +import requests + +import flask +from tests.api.setup_test_api import API_URL, SCODOC_URL, CHECK_CERTIFICATE, api_headers +from tests.api.tools_test_api import verify_fields + +from app import create_app +from config import RunningConfig + + +def test_permissions(api_headers): + """ + vérification de la permissions APIView et du non accès sans role + de toutes les routes de l'API + """ + # Ce test va récupérer toutes les routes de l'API + app = create_app(RunningConfig) + assert app + # Les routes de l'API avec GET, excluant les logos pour le momeent XXX + api_rules = [ + r + for r in app.url_map.iter_rules() + if str(r).startswith("/ScoDoc/api") + and not "logo" in str(r) # ignore logos + and "GET" in r.methods + ] + assert len(api_rules) > 0 + args = { + "etudid": 1, + # "date_debut": + # "date_fin": + "dept": "TAPI", + "etape_apo": "???", + "etat": "I", + "evaluation_id": 1, + "formation_id": 1, + "formsemestre_id": 1, + "group_id": 1, + "ine": "1", + "module_id": 1, + "moduleimpl_id": 1, + "nip": 1, + "partition_id": 1, + } + for rule in api_rules: + path = rule.build(args)[1] + if not "GET" in rule.methods: + # skip all POST routes + continue + r = requests.get( + SCODOC_URL + path, + headers=api_headers, + verify=CHECK_CERTIFICATE, + ) + assert r.status_code == 200 + + # Même chose sans le jeton: + for rule in api_rules: + path = rule.build(args)[1] + if not "GET" in rule.methods: + # skip all POST routes + continue + r = requests.get( + SCODOC_URL + path, + verify=CHECK_CERTIFICATE, + ) + assert r.status_code == 401 + + # Demande un jeton pour "other" + r = requests.post(API_URL + "/tokens", auth=("other", "other")) + assert r.status_code == 200 + token = r.json()["token"] + headers = {"Authorization": f"Bearer {token}"} + # Vérifie que tout est interdit + for rule in api_rules: + path = rule.build(args)[1] + if not "GET" in rule.methods: + # skip all POST routes + continue + r = requests.get( + SCODOC_URL + path, + headers=headers, + verify=CHECK_CERTIFICATE, + ) + assert r.status_code == 403 From b740f403898c7adda536b085b3ac2af1c92df06c Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Thu, 5 May 2022 08:21:42 +0200 Subject: [PATCH 17/32] API: ajout session_id au formsemestre. formsemestre/programme --- app/api/formsemestres.py | 251 ++++++++++++++++++--------------------- 1 file changed, 113 insertions(+), 138 deletions(-) diff --git a/app/api/formsemestres.py b/app/api/formsemestres.py index 24ec5eeb2..655250afc 100644 --- a/app/api/formsemestres.py +++ b/app/api/formsemestres.py @@ -4,14 +4,11 @@ from flask import jsonify import app from app import models from app.api import bp -from app.api.errors import error_response from app.api.auth import token_auth, token_permission_required -from app.api.tools import get_etu_from_etudid_or_nip_or_ine -from app.models import FormSemestre, FormSemestreEtape +from app.models import Departement, FormSemestre, FormSemestreEtape from app.scodoc.sco_bulletins import get_formsemestre_bulletin_etud_json -from app.scodoc.sco_bulletins_json import make_json_formsemestre_bulletinetud from app.scodoc.sco_permissions import Permission -from app.scodoc.sco_pvjury import formsemestre_pvjury +from app.scodoc.sco_utils import ModuleType @bp.route("/formsemestre/", methods=["GET"]) @@ -53,10 +50,15 @@ def formsemestre(formsemestre_id: int): } """ - formsemestre = models.FormSemestre.query.filter_by( + formsemestre: FormSemestre = models.FormSemestre.query.filter_by( id=formsemestre_id ).first_or_404() data = formsemestre.to_dict() + # Pour le moment on a besoin de fixer le departement + # pour accéder aux préferences + dept = Departement.query.get(formsemestre.dept_id) + app.set_sco_dept(dept.acronym) + data["session_id"] = formsemestre.session_id() return jsonify(data) @@ -71,32 +73,7 @@ def formsemestre_apo(etape_apo: str): Exemple de résultat : [ - { - "date_fin": "31/08/2022", - "resp_can_edit": false, - "dept_id": 1, - "etat": true, - "resp_can_change_ens": true, - "id": 1, - "modalite": "FI", - "ens_can_edit_eval": false, - "formation_id": 1, - "gestion_compensation": false, - "elt_sem_apo": null, - "semestre_id": 1, - "bul_hide_xml": false, - "elt_annee_apo": null, - "titre": "Semestre test", - "block_moyennes": false, - "scodoc7_id": null, - "date_debut": "01/09/2021", - "gestion_semestrielle": false, - "bul_bgcolor": "white", - "formsemestre_id": 1, - "titre_num": "Semestre test semestre 1", - "date_debut_iso": "2021-09-01", - "date_fin_iso": "2022-08-31", - "responsables": [] + { ...formsemestre... }, ... ] """ @@ -341,114 +318,112 @@ def bulletins(formsemestre_id: int): # return jsonify(data) -# XXX A spécifier et compléter TODO -# @bp.route( -# "/formsemestre//programme", -# methods=["GET"], -# ) -# @token_auth.login_required -# @token_permission_required(Permission.APIView) -# def semestre_index(formsemestre_id: int): # XXX nom bizarre ?? -# """ -# Retourne la liste des Ues, ressources et SAE d'un semestre +@bp.route( + "/formsemestre//programme", + methods=["GET"], +) +@token_auth.login_required +@token_permission_required(Permission.APIView) +def formsemestre_programme(formsemestre_id: int): # XXX nom bizarre ?? + """ + Retourne la liste des Ues, ressources et SAE d'un semestre -# formsemestre_id : l'id d'un formsemestre + formsemestre_id : l'id d'un formsemestre -# Exemple de résultat : -# { -# "ues": [ -# { -# "type": 0, -# "formation_id": 1, -# "ue_code": "UCOD11", -# "id": 1, -# "ects": 12.0, -# "acronyme": "RT1.1", -# "is_external": false, -# "numero": 1, -# "code_apogee": "", -# "titre": "Administrer les r\u00e9seaux et l\u2019Internet", -# "coefficient": 0.0, -# "semestre_idx": 1, -# "color": "#B80004", -# "ue_id": 1 -# }, -# ... -# ], -# "ressources": [ -# { -# "titre": "Fondamentaux de la programmation", -# "coefficient": 1.0, -# "module_type": 2, -# "id": 17, -# "ects": null, -# "abbrev": null, -# "ue_id": 3, -# "code": "R107", -# "formation_id": 1, -# "heures_cours": 0.0, -# "matiere_id": 3, -# "heures_td": 0.0, -# "semestre_id": 1, -# "heures_tp": 0.0, -# "numero": 70, -# "code_apogee": "", -# "module_id": 17 -# }, -# ... -# ], -# "saes": [ -# { -# "titre": "Se pr\u00e9senter sur Internet", -# "coefficient": 1.0, -# "module_type": 3, -# "id": 14, -# "ects": null, -# "abbrev": null, -# "ue_id": 3, -# "code": "SAE14", -# "formation_id": 1, -# "heures_cours": 0.0, -# "matiere_id": 3, -# "heures_td": 0.0, -# "semestre_id": 1, -# "heures_tp": 0.0, -# "numero": 40, -# "code_apogee": "", -# "module_id": 14 -# }, -# ... -# ] -# } -# """ + Exemple de résultat : + { + "ues": [ + { + "type": 0, + "formation_id": 1, + "ue_code": "UCOD11", + "id": 1, + "ects": 12.0, + "acronyme": "RT1.1", + "is_external": false, + "numero": 1, + "code_apogee": "", + "titre": "Administrer les r\u00e9seaux et l\u2019Internet", + "coefficient": 0.0, + "semestre_idx": 1, + "color": "#B80004", + "ue_id": 1 + }, + ... + ], + "ressources": [ + { + "titre": "Fondamentaux de la programmation", + "coefficient": 1.0, + "module_type": 2, + "id": 17, + "ects": null, + "abbrev": null, + "ue_id": 3, + "code": "R107", + "formation_id": 1, + "heures_cours": 0.0, + "matiere_id": 3, + "heures_td": 0.0, + "semestre_id": 1, + "heures_tp": 0.0, + "numero": 70, + "code_apogee": "", + "module_id": 17 + }, + ... + ], + "saes": [ + { + "titre": "Se pr\u00e9senter sur Internet", + "coefficient": 1.0, + "module_type": 3, + "id": 14, + "ects": null, + "abbrev": null, + "ue_id": 3, + "code": "SAE14", + "formation_id": 1, + "heures_cours": 0.0, + "matiere_id": 3, + "heures_td": 0.0, + "semestre_id": 1, + "heures_tp": 0.0, + "numero": 40, + "code_apogee": "", + "module_id": 14 + }, + ... + ], + "modules" : [ ... les modules qui ne sont niu des SAEs ni des ressources ... ] + } + """ + formsemestre: FormSemestre = models.FormSemestre.query.filter_by( + id=formsemestre_id + ).first_or_404() -# formsemestre: FormSemestre = models.FormSemestre.query.filter_by( -# id=formsemestre_id -# ).first_or_404() + ues = formsemestre.query_ues() + ressources = [ + modimpl.module + for modimpl in formsemestre.modimpls_sorted + if modimpl.module.module_type == ModuleType.RESSOURCE + ] + saes = [ + modimpl.module + for modimpl in formsemestre.modimpls_sorted + if modimpl.module.module_type == ModuleType.SAE + ] + modules = [ + modimpl.module + for modimpl in formsemestre.modimpls_sorted + if modimpl.module.module_type == ModuleType.STANDARD + ] -# ues = formsemestre.query_ues() + data = { + "ues": [ue.to_dict() for ue in ues], + "ressources": [m.to_dict() for m in ressources], + "saes": [m.to_dict() for m in saes], + "modules": [m.to_dict() for m in modules], + } -# ues_dict = [] -# ressources = [] -# saes = [] - -# for ue in ues: -# ues_dict.append(ue.to_dict()) -# ressources = ue.get_ressources() -# saes = ue.get_saes() - -# data_ressources = [] -# for ressource in ressources: -# data_ressources.append(ressource.to_dict()) - -# data_saes = [] -# for sae in saes: -# data_saes.append(sae.to_dict()) - -# data = { -# "ues": ues_dict, -# "ressources": data_ressources, -# "saes": data_saes, -# } - -# return data + return jsonify(data) From 69e6b045db710e7df472868d176379087f7ed552 Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Thu, 5 May 2022 18:09:32 +0200 Subject: [PATCH 18/32] =?UTF-8?q?Preparation=20=C3=A9vol.=20sco=5Fpreferen?= =?UTF-8?q?ces?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/scodoc/sco_preferences.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/app/scodoc/sco_preferences.py b/app/scodoc/sco_preferences.py index 9ccb63628..49445bc95 100644 --- a/app/scodoc/sco_preferences.py +++ b/app/scodoc/sco_preferences.py @@ -132,9 +132,12 @@ def clear_base_preferences(): g._SCO_BASE_PREFERENCES = {} # { dept_id: BasePreferences instance } -def get_base_preferences(): - """Return global preferences for the current department""" - dept_id = g.scodoc_dept_id +def get_base_preferences(dept_id: int = None): + """Return global preferences for the specified department + or the current departement + """ + if dept_id is None: + dept_id = g.scodoc_dept_id if not hasattr(g, "_SCO_BASE_PREFERENCES"): g._SCO_BASE_PREFERENCES = {} if not dept_id in g._SCO_BASE_PREFERENCES: @@ -142,12 +145,12 @@ def get_base_preferences(): return g._SCO_BASE_PREFERENCES[dept_id] -def get_preference(name, formsemestre_id=None): +def get_preference(name, formsemestre_id=None, dept_id=None): """Returns value of named preference. All preferences have a sensible default value, so this function always returns a usable value for all defined preferences names. """ - return get_base_preferences().get(formsemestre_id, name) + return get_base_preferences(dept_id=dept_id).get(formsemestre_id, name) def _convert_pref_type(p, pref_spec): @@ -2145,9 +2148,9 @@ class BasePreferences(object): class SemPreferences: """Preferences for a formsemestre""" - def __init__(self, formsemestre_id=None): + def __init__(self, formsemestre_id=None, dept_id=None): self.formsemestre_id = formsemestre_id - self.base_prefs = get_base_preferences() + self.base_prefs = get_base_preferences(dept_id=dept_id) def __getitem__(self, name): return self.base_prefs.get(self.formsemestre_id, name) From c55a7dcbb4cfb95d3433e033770d2fe0a17feefb Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Thu, 5 May 2022 18:10:22 +0200 Subject: [PATCH 19/32] Ajout titre_formation --- app/models/formsemestre.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/app/models/formsemestre.py b/app/models/formsemestre.py index fda723839..dee03510e 100644 --- a/app/models/formsemestre.py +++ b/app/models/formsemestre.py @@ -139,6 +139,7 @@ class FormSemestre(db.Model): else: d["date_fin"] = d["date_fin_iso"] = "" d["responsables"] = [u.id for u in self.responsables] + d["titre_formation"] = self.titre_formation() return d def get_infos_dict(self) -> dict: @@ -329,9 +330,10 @@ class FormSemestre(db.Model): ANNEE=annee universitaire de debut (exemple: un S2 de 2013-2014 sera S2-2013) """ - imputation_dept = sco_preferences.get_preference("ImputationDept", self.id) + prefs = sco_preferences.SemPreferences(dept_id=self.dept_id) + imputation_dept = prefs["ImputationDept"] if not imputation_dept: - imputation_dept = sco_preferences.get_preference("DeptName") + imputation_dept = prefs["DeptName"] imputation_dept = imputation_dept.upper() parcours_name = self.formation.get_parcours().NAME modalite = self.modalite @@ -346,7 +348,7 @@ class FormSemestre(db.Model): scu.annee_scolaire_debut(self.date_debut.year, self.date_debut.month) ) return scu.sanitize_string( - "-".join((imputation_dept, parcours_name, modalite, semestre_id, annee_sco)) + f"{imputation_dept}-{parcours_name}-{modalite}-{semestre_id}-{annee_sco}" ) def titre_annee(self) -> str: @@ -358,6 +360,12 @@ class FormSemestre(db.Model): titre_annee += "-" + str(self.date_fin.year) return titre_annee + def titre_formation(self): + """Titre avec formation, court, pour passerelle: "BUT R&T" + (méthode de formsemestre car on pourrait ajouter le semestre, ou d'autres infos, à voir) + """ + return self.formation.acronyme + def titre_mois(self) -> str: """Le titre et les dates du semestre, pour affichage dans des listes Ex: "BUT QLIO (PN 2022) semestre 1 FI (Sept 2022 - Jan 2023)" From 16d968ef95421a30a1163fbceabc8164077c49dd Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Thu, 5 May 2022 18:11:10 +0200 Subject: [PATCH 20/32] =?UTF-8?q?Ajout=20code=20INE=20=C3=A0=20l'export=20?= =?UTF-8?q?court=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/etudiants.py | 1 + 1 file changed, 1 insertion(+) diff --git a/app/models/etudiants.py b/app/models/etudiants.py index 917b01363..912136e61 100644 --- a/app/models/etudiants.py +++ b/app/models/etudiants.py @@ -146,6 +146,7 @@ class Identite(db.Model): return { "id": self.id, "nip": self.code_nip, + "ine": self.code_ine, "nom": self.nom, "nom_usuel": self.nom_usuel, "prenom": self.prenom, From ba6b275973665054cd6a97d82e626f8f750d92da Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Thu, 5 May 2022 18:11:44 +0200 Subject: [PATCH 21/32] API: WIP --- app/api/departements.py | 131 +++++++++++++-------------- app/api/etudiants.py | 4 +- app/api/formations.py | 69 ++------------ app/api/formsemestres.py | 139 +++++++++++++---------------- tests/api/test_api_departements.py | 20 ++++- tests/api/test_api_formations.py | 22 ++--- 6 files changed, 163 insertions(+), 222 deletions(-) diff --git a/app/api/departements.py b/app/api/departements.py index aaf194267..abea73b37 100644 --- a/app/api/departements.py +++ b/app/api/departements.py @@ -1,57 +1,58 @@ ############################################### Departements ########################################################## -import json from flask import jsonify +import app from app import models from app.api import bp from app.api.auth import token_auth, token_permission_required -from app.api.errors import error_response +from app.models import FormSemestre from app.scodoc.sco_permissions import Permission -@bp.route("/departements", methods=["GET"]) +@bp.route("/departements_ids", methods=["GET"]) @token_auth.login_required @token_permission_required(Permission.APIView) -def departements(): +def departements_ids(): + """Liste des ids de départements""" + return jsonify([dept.id for dept in models.Departement.query]) + + +@bp.route("/departement/", methods=["GET"]) +@token_auth.login_required +@token_permission_required(Permission.APIView) +def departement(dept_id: int): """ - Retourne la liste des ids de départements visibles + Info sur un département. Exemple de résultat : - [ { "id": 1, "acronym": "TAPI", "description": null, "visible": true, "date_creation": "Fri, 15 Apr 2022 12:19:28 GMT" - }, - { - "id": 2, - "acronym": "MMI", - "description": null, - "visible": false, - "date_creation": "Fri, 18 Apr 2022 11:20:8 GMT" - }, - ... - ] + } """ - # Récupération de tous les départements - depts = models.Departement.query.all() - - # Mise en place de la liste avec tous les départements - data = [d.to_dict() for d in depts] - - return jsonify(data) + dept = models.Departement.query.filter_by(dept_id=dept_id).first_or_404() + return jsonify(dept.to_dict()) -@bp.route("/departements//etudiants/list", methods=["GET"]) -@bp.route( - "/departements//etudiants/list/", methods=["GET"] -) +@bp.route("/departements", methods=["GET"]) @token_auth.login_required @token_permission_required(Permission.APIView) -def list_etudiants(dept: str, formsemestre_id=None): +def departements(): + """Liste les départements""" + return jsonify([dept.to_dict() for dept in models.Departement.query]) + + +@bp.route("/departement//etudiants", methods=["GET"]) +# @bp.route( +# "/departement//etudiants/list/", methods=["GET"] +# ) +@token_auth.login_required +@token_permission_required(Permission.APIView) +def list_etudiants(dept_ident: str): """ Retourne la liste des étudiants d'un département @@ -61,54 +62,38 @@ def list_etudiants(dept: str, formsemestre_id=None): Exemple de résultat : [ { - "civilite": "X", - "code_ine": null, - "code_nip": null, + "civilite": "M", + "ine": "7899X61616", + "nip": "F6777H88", "date_naissance": null, - "email": null, + "email": "toto@toto.fr", "emailperso": null, "etudid": 18, "nom": "MOREL", "prenom": "JACQUES" }, - { - "civilite": "X", - "code_ine": null, - "code_nip": null, - "date_naissance": null, - "email": null, - "emailperso": null, - "etudid": 19, - "nom": "FOURNIER", - "prenom": "ANNE" - }, ... ] """ - # Si le formsemestre_id a été renseigné - if formsemestre_id is not None: - # Récupération du formsemestre - formsemestre = models.FormSemestre.query.filter_by( - id=formsemestre_id + # Le département, spécifié par un id ou un acronyme + try: + dept_id = int(dept_ident) + except ValueError: + dept_id = None + if dept_id is None: + departement = models.Departement.query.filter_by( + acronym=dept_ident ).first_or_404() - # Récupération du département - departement = formsemestre.departement - - # Si le formsemestre_id n'a pas été renseigné else: - # Récupération du formsemestre - departement = models.Departement.query.filter_by(acronym=dept).first_or_404() + departement = models.Departement.query.get_or_404(dept_id) - # Mise en forme des données - list_etu = [etu.to_dict_bul(include_urls=False) for etu in departement.etudiants] - - return jsonify(list_etu) + return jsonify([etud.to_dict_short() for etud in departement.etudiants]) -@bp.route("/departements//semestres_courants", methods=["GET"]) +@bp.route("/departement//formsemestres_courants", methods=["GET"]) @token_auth.login_required @token_permission_required(Permission.APIView) -def liste_semestres_courant(dept: str): +def liste_semestres_courant(dept_ident: str): """ Liste des semestres actifs d'un départements donné @@ -149,13 +134,23 @@ def liste_semestres_courant(dept: str): ... ] """ - # Récupération des départements comportant l'acronym mit en paramètre - dept = models.Departement.query.filter_by(acronym=dept).first_or_404() + # Le département, spécifié par un id ou un acronyme + try: + dept_id = int(dept_ident) + except ValueError: + dept_id = None + if dept_id is None: + departement = models.Departement.query.filter_by( + acronym=dept_ident + ).first_or_404() + else: + departement = models.Departement.query.get_or_404(dept_id) - # Récupération des semestres suivant id_dept - semestres = models.FormSemestre.query.filter_by(dept_id=dept.id, etat=True) + # Les semestres en cours de ce département + formsemestres = models.FormSemestre.query.filter( + dept_id == departement.id, + FormSemestre.date_debut <= app.db.func.now(), + FormSemestre.date_fin >= app.db.func.now(), + ) - # Mise en forme des données - data = [d.to_dict() for d in semestres] - - return jsonify(data) + return jsonify([d.to_dict() for d in formsemestres]) diff --git a/app/api/etudiants.py b/app/api/etudiants.py index a6baa2d88..ab41fc319 100644 --- a/app/api/etudiants.py +++ b/app/api/etudiants.py @@ -20,7 +20,8 @@ from app.scodoc.sco_permissions import Permission @token_permission_required(Permission.APIView) def etudiants_courant(long=False): """ - Retourne la liste des étudiants courant + Retourne la liste des étudiants inscrits dans un + formsemestre actuellement en cours. Exemple de résultat : [ @@ -41,7 +42,6 @@ def etudiants_courant(long=False): ... ] """ - # Récupération de tous les étudiants etuds = Identite.query.filter( Identite.id == FormSemestreInscription.etudid, FormSemestreInscription.formsemestre_id == FormSemestre.id, diff --git a/app/api/formations.py b/app/api/formations.py index 3724c96a3..f24438177 100644 --- a/app/api/formations.py +++ b/app/api/formations.py @@ -29,10 +29,10 @@ def formations_ids(): return jsonify(data) -@bp.route("/formations/", methods=["GET"]) +@bp.route("/formation/", methods=["GET"]) @token_auth.login_required @token_permission_required(Permission.APIView) -def formations_by_id(formation_id: int): +def formation_by_id(formation_id: int): """ Retourne une formation en fonction d'un id donné @@ -63,12 +63,12 @@ def formations_by_id(formation_id: int): @bp.route( - "/formations/formation_export/", + "/formation/formation_export/", methods=["GET"], defaults={"export_ids": False}, ) @bp.route( - "/formations/formation_export//with_ids", + "/formation/formation_export//with_ids", methods=["GET"], defaults={"export_ids": True}, ) @@ -185,7 +185,7 @@ def formation_export_by_formation_id(formation_id: int, export_ids=False): return jsonify(data) -@bp.route("/formations/moduleimpl/", methods=["GET"]) +@bp.route("/formation/moduleimpl/", methods=["GET"]) @token_auth.login_required @token_permission_required(Permission.APIView) def moduleimpl(moduleimpl_id: int): @@ -198,7 +198,6 @@ def moduleimpl(moduleimpl_id: int): { "id": 1, "formsemestre_id": 1, - "computation_expr": null, "module_id": 1, "responsable_id": 2, "moduleimpl_id": 1, @@ -230,63 +229,7 @@ def moduleimpl(moduleimpl_id: int): @bp.route( - "/formations/moduleimpl/formsemestre//list", - methods=["GET"], -) -@token_auth.login_required -@token_permission_required(Permission.APIView) -def moduleimpls_sem(formsemestre_id: int): - """ - Retourne la liste des moduleimpl d'un semestre - - formsemestre_id : l'id d'un formsemestre - - Exemple d'utilisation : - [ - { - "id": 1, - "formsemestre_id": 1, - "computation_expr": null, - "module_id": 1, - "responsable_id": 2, - "module": { - "heures_tp": 0.0, - "code_apogee": "", - "titre": "Initiation aux r\u00e9seaux informatiques", - "coefficient": 1.0, - "module_type": 2, - "id": 1, - "ects": null, - "abbrev": "Init aux r\u00e9seaux informatiques", - "ue_id": 1, - "code": "R101", - "formation_id": 1, - "heures_cours": 0.0, - "matiere_id": 1, - "heures_td": 0.0, - "semestre_id": 1, - "numero": 10, - "module_id": 1 - }, - "moduleimpl_id": 1, - "ens": [] - }, - ... - ] - """ - formsemestre = models.FormSemestre.query.filter_by( - id=formsemestre_id - ).first_or_404() - - moduleimpls = formsemestre.modimpls_sorted - - data = [moduleimpl.to_dict() for moduleimpl in moduleimpls] - - return jsonify(data) - - -@bp.route( - "/formations//referentiel_competences", + "/formation//referentiel_competences", methods=["GET"], ) @token_auth.login_required diff --git a/app/api/formsemestres.py b/app/api/formsemestres.py index 655250afc..8bd5a108f 100644 --- a/app/api/formsemestres.py +++ b/app/api/formsemestres.py @@ -22,31 +22,32 @@ def formsemestre(formsemestre_id: int): Exemple de résultat : { + "block_moyennes": false, + "bul_bgcolor": "white", + "bul_hide_xml": false, + "date_debut_iso": "2021-09-01", + "date_debut": "01/09/2021", + "date_fin_iso": "2022-08-31", "date_fin": "31/08/2022", - "resp_can_edit": false, "dept_id": 1, + "elt_annee_apo": null, + "elt_sem_apo": null, + "ens_can_edit_eval": false, "etat": true, - "resp_can_change_ens": true, + "formation_id": 1, + "formsemestre_id": 1, + "gestion_compensation": false, + "gestion_semestrielle": false, "id": 1, "modalite": "FI", - "ens_can_edit_eval": false, - "formation_id": 1, - "gestion_compensation": false, - "elt_sem_apo": null, - "semestre_id": 1, - "bul_hide_xml": false, - "elt_annee_apo": null, - "titre": "Semestre test", - "block_moyennes": false, + "resp_can_change_ens": true, + "resp_can_edit": false, + "responsables": [1, 99], // uids "scodoc7_id": null, - "date_debut": "01/09/2021", - "gestion_semestrielle": false, - "bul_bgcolor": "white", - "formsemestre_id": 1, - "titre_num": "Semestre test semestre 1", - "date_debut_iso": "2021-09-01", - "date_fin_iso": "2022-08-31", - "responsables": [] <<< A DOCUMENTER XXX + "semestre_id": 1, + "titre_formation" : "BUT GEA", + "titre_num": "BUT GEA semestre 1", + "titre": "BUT GEA", } """ @@ -324,7 +325,7 @@ def bulletins(formsemestre_id: int): ) @token_auth.login_required @token_permission_required(Permission.APIView) -def formsemestre_programme(formsemestre_id: int): # XXX nom bizarre ?? +def formsemestre_programme(formsemestre_id: int): """ Retourne la liste des Ues, ressources et SAE d'un semestre @@ -353,49 +354,40 @@ def formsemestre_programme(formsemestre_id: int): # XXX nom bizarre ?? ], "ressources": [ { - "titre": "Fondamentaux de la programmation", - "coefficient": 1.0, - "module_type": 2, - "id": 17, - "ects": null, - "abbrev": null, - "ue_id": 3, - "code": "R107", - "formation_id": 1, - "heures_cours": 0.0, - "matiere_id": 3, - "heures_td": 0.0, - "semestre_id": 1, - "heures_tp": 0.0, - "numero": 70, - "code_apogee": "", - "module_id": 17 + "ens": [ 10, 18 ], + "formsemestre_id": 1, + "id": 15, + "module": { + "abbrev": "Programmer", + "code": "SAE15", + "code_apogee": "V7GOP", + "coefficient": 1.0, + "formation_id": 1, + "heures_cours": 0.0, + "heures_td": 0.0, + "heures_tp": 0.0, + "id": 15, + "matiere_id": 3, + "module_id": 15, + "module_type": 3, + "numero": 50, + "semestre_id": 1, + "titre": "Programmer en Python", + "ue_id": 3 }, + "module_id": 15, + "moduleimpl_id": 15, + "responsable_id": 2 + }, ... ], "saes": [ { - "titre": "Se pr\u00e9senter sur Internet", - "coefficient": 1.0, - "module_type": 3, - "id": 14, - "ects": null, - "abbrev": null, - "ue_id": 3, - "code": "SAE14", - "formation_id": 1, - "heures_cours": 0.0, - "matiere_id": 3, - "heures_td": 0.0, - "semestre_id": 1, - "heures_tp": 0.0, - "numero": 40, - "code_apogee": "", - "module_id": 14 + ... }, ... ], - "modules" : [ ... les modules qui ne sont niu des SAEs ni des ressources ... ] + "modules" : [ ... les modules qui ne sont ni des SAEs ni des ressources ... ] } """ formsemestre: FormSemestre = models.FormSemestre.query.filter_by( @@ -403,27 +395,20 @@ def formsemestre_programme(formsemestre_id: int): # XXX nom bizarre ?? ).first_or_404() ues = formsemestre.query_ues() - ressources = [ - modimpl.module - for modimpl in formsemestre.modimpls_sorted - if modimpl.module.module_type == ModuleType.RESSOURCE - ] - saes = [ - modimpl.module - for modimpl in formsemestre.modimpls_sorted - if modimpl.module.module_type == ModuleType.SAE - ] - modules = [ - modimpl.module - for modimpl in formsemestre.modimpls_sorted - if modimpl.module.module_type == ModuleType.STANDARD - ] - - data = { - "ues": [ue.to_dict() for ue in ues], - "ressources": [m.to_dict() for m in ressources], - "saes": [m.to_dict() for m in saes], - "modules": [m.to_dict() for m in modules], + m_list = { + ModuleType.RESSOURCE: [], + ModuleType.SAE: [], + ModuleType.STANDARD: [], } + for modimpl in formsemestre.modimpls_sorted: + d = modimpl.to_dict() + m_list[modimpl.module.module_type].append(d) - return jsonify(data) + return jsonify( + { + "ues": [ue.to_dict() for ue in ues], + "ressources": m_list[ModuleType.RESSOURCE], + "saes": m_list[ModuleType.SAE], + "modules": m_list[ModuleType.STANDARD], + } + ) diff --git a/tests/api/test_api_departements.py b/tests/api/test_api_departements.py index 872aa1044..91bac8257 100644 --- a/tests/api/test_api_departements.py +++ b/tests/api/test_api_departements.py @@ -23,8 +23,26 @@ from tests.api.setup_test_api import API_URL, CHECK_CERTIFICATE, api_headers from tests.api.tools_test_api import verify_fields +def test_departements_ids(api_headers): + """ " + Route: /departements_ids + """ + r = requests.get( + API_URL + "/departements_ids", + headers=api_headers, + verify=CHECK_CERTIFICATE, + ) + assert r.status_code == 200 + departements_ids = r.json() + assert isinstance(departements_ids, list) + assert len(departements_ids) > 0 + assert all(isinstance(x, int) for x in departements_ids) + + def test_departements(api_headers): - "check liste de sdépartements" + """ " + Route: /departements + """ fields = [ "id", "acronym", diff --git a/tests/api/test_api_formations.py b/tests/api/test_api_formations.py index b8f159ada..e14fcb2c9 100644 --- a/tests/api/test_api_formations.py +++ b/tests/api/test_api_formations.py @@ -44,7 +44,7 @@ def test_formations_ids(api_headers): # formations_by_id def test_formations_by_id(api_headers): """ - Route: /formations/ + Route: /formation/ """ fields = [ "id", @@ -61,7 +61,7 @@ def test_formations_by_id(api_headers): ] r = requests.get( - API_URL + "/formations/1", + API_URL + "/formation/1", headers=api_headers, verify=CHECK_CERTIFICATE, ) @@ -75,7 +75,7 @@ def test_formations_by_id(api_headers): def test_formation_export(api_headers): """ - Route: /formations/formation_export/ + Route: /formation/formation_export/ """ fields = [ "id", @@ -92,7 +92,7 @@ def test_formation_export(api_headers): "ue", ] r = requests.get( - API_URL + "/formations/formation_export/1", + API_URL + "/formation/formation_export/1", headers=api_headers, verify=CHECK_CERTIFICATE, ) @@ -108,7 +108,7 @@ def test_formation_export(api_headers): # TODO # def test_formsemestre_apo(api_headers): # r = requests.get( -# API_URL + "/formations/apo/", +# API_URL + "/formation/apo/", # headers=api_headers, # verify=CHECK_CERTIFICATE, # ) @@ -117,7 +117,7 @@ def test_formation_export(api_headers): def test_moduleimpl(api_headers): """ - Route: /formations/moduleimpl/ + Route: /formation/moduleimpl/ """ fields = [ "id", @@ -131,7 +131,7 @@ def test_moduleimpl(api_headers): ] r = requests.get( - API_URL + "/formations/moduleimpl/1", + API_URL + "/formation/moduleimpl/1", headers=api_headers, verify=CHECK_CERTIFICATE, ) @@ -145,7 +145,7 @@ def test_moduleimpl(api_headers): def test_moduleimpls_sem(api_headers): """ - Route: /formations/moduleimpl/formsemestre//list + Route: /formation/moduleimpl/formsemestre//list """ fields = [ "id", @@ -160,7 +160,7 @@ def test_moduleimpls_sem(api_headers): "ens", ] r = requests.get( - API_URL + "/formations/moduleimpl/formsemestre/1/list", + API_URL + "/formation/moduleimpl/formsemestre/1/list", headers=api_headers, verify=CHECK_CERTIFICATE, ) @@ -175,10 +175,10 @@ def test_moduleimpls_sem(api_headers): def test_referentiel_competences(api_headers): """ - Route: "/formations//referentiel_competences", + Route: "/formation//referentiel_competences", """ r = requests.get( - API_URL + "/formations/1/referentiel_competences", + API_URL + "/formation/1/referentiel_competences", headers=api_headers, verify=CHECK_CERTIFICATE, ) From 8486512f8357c66f30cb259ad21996c9d08e3fb6 Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Thu, 5 May 2022 18:50:59 +0200 Subject: [PATCH 22/32] API: Departement: tests unitaires et corrections. --- app/api/departements.py | 54 +++++------- tests/api/test_api_departements.py | 137 ++++++++++++----------------- 2 files changed, 79 insertions(+), 112 deletions(-) diff --git a/app/api/departements.py b/app/api/departements.py index abea73b37..c3a4d2e7e 100644 --- a/app/api/departements.py +++ b/app/api/departements.py @@ -6,24 +6,35 @@ import app from app import models from app.api import bp from app.api.auth import token_auth, token_permission_required -from app.models import FormSemestre +from app.models import Departement, FormSemestre from app.scodoc.sco_permissions import Permission +def get_departement(dept_ident: str) -> Departement: + "Le departement, par id ou acronyme. Erreur 404 si pas trouvé." + try: + dept_id = int(dept_ident) + except ValueError: + dept_id = None + if dept_id is None: + return Departement.query.filter_by(acronym=dept_ident).first_or_404() + return Departement.query.get_or_404(dept_id) + + @bp.route("/departements_ids", methods=["GET"]) @token_auth.login_required @token_permission_required(Permission.APIView) def departements_ids(): """Liste des ids de départements""" - return jsonify([dept.id for dept in models.Departement.query]) + return jsonify([dept.id for dept in Departement.query]) -@bp.route("/departement/", methods=["GET"]) +@bp.route("/departement/", methods=["GET"]) @token_auth.login_required @token_permission_required(Permission.APIView) -def departement(dept_id: int): +def departement(dept_ident: str): """ - Info sur un département. + Info sur un département. Accès par id ou acronyme. Exemple de résultat : { @@ -34,7 +45,7 @@ def departement(dept_id: int): "date_creation": "Fri, 15 Apr 2022 12:19:28 GMT" } """ - dept = models.Departement.query.filter_by(dept_id=dept_id).first_or_404() + dept = get_departement(dept_ident) return jsonify(dept.to_dict()) @@ -43,13 +54,10 @@ def departement(dept_id: int): @token_permission_required(Permission.APIView) def departements(): """Liste les départements""" - return jsonify([dept.to_dict() for dept in models.Departement.query]) + return jsonify([dept.to_dict() for dept in Departement.query]) @bp.route("/departement//etudiants", methods=["GET"]) -# @bp.route( -# "/departement//etudiants/list/", methods=["GET"] -# ) @token_auth.login_required @token_permission_required(Permission.APIView) def list_etudiants(dept_ident: str): @@ -76,18 +84,9 @@ def list_etudiants(dept_ident: str): ] """ # Le département, spécifié par un id ou un acronyme - try: - dept_id = int(dept_ident) - except ValueError: - dept_id = None - if dept_id is None: - departement = models.Departement.query.filter_by( - acronym=dept_ident - ).first_or_404() - else: - departement = models.Departement.query.get_or_404(dept_id) + dept = get_departement(dept_ident) - return jsonify([etud.to_dict_short() for etud in departement.etudiants]) + return jsonify([etud.to_dict_short() for etud in dept.etudiants]) @bp.route("/departement//formsemestres_courants", methods=["GET"]) @@ -135,20 +134,11 @@ def liste_semestres_courant(dept_ident: str): ] """ # Le département, spécifié par un id ou un acronyme - try: - dept_id = int(dept_ident) - except ValueError: - dept_id = None - if dept_id is None: - departement = models.Departement.query.filter_by( - acronym=dept_ident - ).first_or_404() - else: - departement = models.Departement.query.get_or_404(dept_id) + dept = get_departement(dept_ident) # Les semestres en cours de ce département formsemestres = models.FormSemestre.query.filter( - dept_id == departement.id, + FormSemestre.dept_id == dept.id, FormSemestre.date_debut <= app.db.func.now(), FormSemestre.date_fin >= app.db.func.now(), ) diff --git a/tests/api/test_api_departements.py b/tests/api/test_api_departements.py index 91bac8257..cbb296fa9 100644 --- a/tests/api/test_api_departements.py +++ b/tests/api/test_api_departements.py @@ -22,11 +22,21 @@ import requests from tests.api.setup_test_api import API_URL, CHECK_CERTIFICATE, api_headers from tests.api.tools_test_api import verify_fields +DEPARTEMENT_FIELDS = [ + "id", + "acronym", + "description", + "visible", + "date_creation", +] -def test_departements_ids(api_headers): + +def test_departements(api_headers): """ " - Route: /departements_ids + Routes: /departements_ids, /departement + """ + # --- Liste des ids r = requests.get( API_URL + "/departements_ids", headers=api_headers, @@ -38,89 +48,38 @@ def test_departements_ids(api_headers): assert len(departements_ids) > 0 assert all(isinstance(x, int) for x in departements_ids) - -def test_departements(api_headers): - """ " - Route: /departements - """ - fields = [ - "id", - "acronym", - "description", - "visible", - "date_creation", - ] - + dept_id = departements_ids[0] + # --- Infos sur un département, accès par id r = requests.get( - API_URL + "/departements", + f"{API_URL}/departement/{dept_id}", headers=api_headers, verify=CHECK_CERTIFICATE, ) assert r.status_code == 200 - assert len(r.json()) == 1 - - dept = r.json()[0] - - fields_OK = verify_fields(dept, fields) - - assert fields_OK is True + dept_a = r.json() + assert verify_fields(dept_a, DEPARTEMENT_FIELDS) is True + # --- Infos sur un département, accès par acronyme4 + r = requests.get( + f"{API_URL}/departement/{dept_a['acronym']}", + headers=api_headers, + verify=CHECK_CERTIFICATE, + ) + assert r.status_code == 200 + dept_b = r.json() + assert dept_a == dept_b def test_list_etudiants(api_headers): - fields = { - "civilite", - "code_ine", - "code_nip", - "date_naissance", - "email", - "emailperso", - "etudid", - "nom", - "prenom", - "nomprenom", - "lieu_naissance", - "dept_naissance", - "nationalite", - "boursier", - "id", - "codepostaldomicile", - "paysdomicile", - "telephonemobile", - "typeadresse", - "domicile", - "villedomicile", - "telephone", - "fax", - "description", - } + fields = {"id", "nip", "ine", "nom", "nom_usuel", "prenom", "civilite"} r = requests.get( - API_URL + "/departements/TAPI/etudiants/list", + API_URL + "/departement/TAPI/etudiants", headers=api_headers, verify=CHECK_CERTIFICATE, ) - - etu = r.json()[0] - - fields_OK = verify_fields(etu, fields) - assert r.status_code == 200 - assert len(r.json()) == 16 - assert fields_OK is True - - r = requests.get( - API_URL + "/departements/TAPI/etudiants/list/1", - headers=api_headers, - verify=CHECK_CERTIFICATE, - ) - - etu = r.json()[0] - - fields_OK = verify_fields(etu, fields) - - assert r.status_code == 200 - assert len(r.json()) == 16 - assert fields_OK is True + etud = r.json()[0] + assert verify_fields(etud, fields) is True # liste_semestres_courant @@ -148,21 +107,39 @@ def test_semestres_courant(api_headers): "block_moyennes", "formsemestre_id", "titre_num", + "titre_formation", "date_debut_iso", "date_fin_iso", "responsables", ] - + dept_id = 1 r = requests.get( - API_URL + "/departements/TAPI/semestres_courants", + f"{API_URL}/departement/{dept_id}", headers=api_headers, verify=CHECK_CERTIFICATE, ) - - sem = r.json()[0] - - fields_OK = verify_fields(sem, fields) - assert r.status_code == 200 - assert len(r.json()) == 1 - assert fields_OK is True + dept = r.json() + assert dept["id"] == dept_id + # Accès via acronyme + r = requests.get( + f"{API_URL}/departement/{dept['acronym']}/formsemestres_courants", + headers=api_headers, + verify=CHECK_CERTIFICATE, + ) + assert r.status_code == 200 + result_a = r.json() + assert isinstance(result_a, list) # liste de formsemestres + assert len(result_a) > 0 + sem = result_a[0] + assert verify_fields(sem, fields) is True + + # accès via dept_id + r = requests.get( + f"{API_URL}/departement/{dept['id']}/formsemestres_courants", + headers=api_headers, + verify=CHECK_CERTIFICATE, + ) + assert r.status_code == 200 + result_b = r.json() + assert result_a == result_b From be51032b8afc7408eb3ba36cbc3385895df5e39c Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Thu, 5 May 2022 19:12:03 +0200 Subject: [PATCH 23/32] =?UTF-8?q?API=20tests:=20fix=20verify=5Ffields=20qu?= =?UTF-8?q?i=20testait=20=C3=A0=20l'envers?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/api/tools_test_api.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/tests/api/tools_test_api.py b/tests/api/tools_test_api.py index d0a224c50..7be43a891 100644 --- a/tests/api/tools_test_api.py +++ b/tests/api/tools_test_api.py @@ -2,16 +2,13 @@ """ -def verify_fields(json_response: dict, fields: set) -> bool: +def verify_fields(json_response: dict, expected_fields: set) -> bool: """ Vérifie si les champs attendu de la réponse json sont présents json_response : la réponse de la requête - fields : ensemble des champs à vérifier + expected_fields : ensemble des champs à vérifier Retourne True ou False """ - for field in json_response: - if field not in fields: - return False - return True + return all(field in json_response for field in expected_fields) From f8a3ef8bb57182c21eeb701217570fc74f179ff0 Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Thu, 5 May 2022 19:13:12 +0200 Subject: [PATCH 24/32] API Test: fix --- tests/api/test_api_etudiants.py | 179 ++++++++++++-------------------- 1 file changed, 65 insertions(+), 114 deletions(-) diff --git a/tests/api/test_api_etudiants.py b/tests/api/test_api_etudiants.py index ec6a23d18..99997da01 100644 --- a/tests/api/test_api_etudiants.py +++ b/tests/api/test_api_etudiants.py @@ -16,26 +16,72 @@ Utilisation : Lancer : pytest tests/api/test_api_etudiants.py """ -from random import randint import requests from tests.api.setup_test_api import API_URL, CHECK_CERTIFICATE, api_headers from tests.api.tools_test_api import verify_fields -# etudiants_courant +ETUD_FIELDS = { + "boursier", + "civilite", + "code_ine", + "code_nip", + "codepostaldomicile", + "date_naissance", + "dept_naissance", + "description", + "domicile", + "email", + "emailperso", + "etudid", + "id", + "lieu_naissance", + "nationalite", + "nom", + "nomprenom", + "paysdomicile", + "prenom", + "telephone", + "telephonemobile", + "typeadresse", + "villedomicile", +} + +FSEM_FIELDS = { + "block_moyennes", + "bul_bgcolor", + "bul_hide_xml", + "date_debut_iso", + "date_debut", + "date_fin_iso", + "date_fin", + "dept_id", + "elt_annee_apo", + "elt_sem_apo", + "ens_can_edit_eval", + "etat", + "formation_id", + "formsemestre_id", + "gestion_compensation", + "gestion_semestrielle", + "id", + "modalite", + "resp_can_change_ens", + "resp_can_edit", + "responsables", + "semestre_id", + "titre_formation", + "titre_num", + "titre", +} + + def test_etudiants_courant(api_headers): """ Route: /etudiants/courant """ - fields = [ - "id", - "nip", - "nom", - "nom_usuel", - "prenom", - "civilite", - ] + fields = {"id", "nip", "ine", "nom", "nom_usuel", "prenom", "civilite"} r = requests.get( API_URL + "/etudiants/courant", @@ -44,43 +90,12 @@ def test_etudiants_courant(api_headers): ) assert r.status_code == 200 etudiants = r.json() - assert len(etudiants) == 16 # XXX HARDCODED + assert len(etudiants) > 0 etud = etudiants[-1] - - fields_ok = verify_fields(etud, fields) - - assert fields_ok is True + assert verify_fields(etud, fields) is True ########## Version long ################ - - fields_long = [ - "civilite", - "code_ine", - "code_nip", - "date_naissance", - "email", - "emailperso", - "etudid", - "nom", - "prenom", - "nomprenom", - "lieu_naissance", - "dept_naissance", - "nationalite", - "boursier", - "id", - "codepostaldomicile", - "paysdomicile", - "telephonemobile", - "typeadresse", - "domicile", - "villedomicile", - "telephone", - "fax", - "description", - ] - r = requests.get( API_URL + "/etudiants/courant/long", headers=api_headers, @@ -91,41 +106,13 @@ def test_etudiants_courant(api_headers): assert len(etudiants) == 16 # HARDCODED etud = etudiants[-1] - fields_ok = verify_fields(etud, fields_long) - - assert fields_ok is True + assert verify_fields(etud, ETUD_FIELDS) is True def test_etudiant(api_headers): """ Route: """ - fields = [ - "civilite", - "code_ine", - "code_nip", - "date_naissance", - "email", - "emailperso", - "etudid", - "nom", - "prenom", - "nomprenom", - "lieu_naissance", - "dept_naissance", - "nationalite", - "boursier", - "id", - "domicile", - "villedomicile", - "telephone", - "fax", - "description", - "codepostaldomicile", - "paysdomicile", - "telephonemobile", - "typeadresse", - ] ######### Test etudid ######### r = requests.get( @@ -135,10 +122,8 @@ def test_etudiant(api_headers): ) assert r.status_code == 200 etud = r.json() - assert len(etud) == 24 # ? HARDCODED - fields_ok = verify_fields(etud, fields) - assert fields_ok is True + assert verify_fields(etud, ETUD_FIELDS) is True ######### Test code nip ######### @@ -149,8 +134,7 @@ def test_etudiant(api_headers): ) assert r.status_code == 200 etud = r.json() - assert len(etud) == 24 - fields_ok = verify_fields(etud, fields) + fields_ok = verify_fields(etud, ETUD_FIELDS) assert fields_ok is True ######### Test code ine ######### @@ -163,7 +147,7 @@ def test_etudiant(api_headers): assert r.status_code == 200 etud = r.json() assert len(etud) == 24 - fields_ok = verify_fields(etud, fields) + fields_ok = verify_fields(etud, ETUD_FIELDS) assert fields_ok is True @@ -171,33 +155,6 @@ def test_etudiant_formsemestres(api_headers): """ Route: /etudiant/etudid//formsemestres """ - fields = [ - "date_fin", - "resp_can_edit", - "dept_id", - "etat", - "resp_can_change_ens", - "id", - "modalite", - "ens_can_edit_eval", - "formation_id", - "gestion_compensation", - "elt_sem_apo", - "semestre_id", - "bul_hide_xml", - "elt_annee_apo", - "titre", - "block_moyennes", - "scodoc7_id", - "date_debut", - "gestion_semestrielle", - "bul_bgcolor", - "formsemestre_id", - "titre_num", - "date_debut_iso", - "date_fin_iso", - "responsables", - ] ######### Test etudid ######### @@ -211,9 +168,7 @@ def test_etudiant_formsemestres(api_headers): assert len(formsemestres) == 1 formsemestre = formsemestres[0] - - fields_ok = verify_fields(formsemestre, fields) - assert fields_ok is True + assert verify_fields(formsemestre, FSEM_FIELDS) is True ######### Test code nip ######### r = requests.get( @@ -226,9 +181,7 @@ def test_etudiant_formsemestres(api_headers): assert len(formsemestres) == 1 formsemestre = formsemestres[0] - - fields_ok = verify_fields(formsemestre, fields) - assert fields_ok is True + assert verify_fields(formsemestre, FSEM_FIELDS) is True ######### Test code ine ######### r = requests.get( @@ -241,9 +194,7 @@ def test_etudiant_formsemestres(api_headers): assert len(formsemestres) == 1 formsemestre = formsemestres[0] - - fields_ok = verify_fields(formsemestre, fields) - assert fields_ok is True + assert verify_fields(formsemestre, FSEM_FIELDS) is True def test_etudiant_bulletin_semestre(api_headers): From f2718635bb83ed8c99ec53d95f45602bd0c1a466 Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Thu, 5 May 2022 19:49:43 +0200 Subject: [PATCH 25/32] API: ajout /departement//formsemestres_ids --- app/api/departements.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/app/api/departements.py b/app/api/departements.py index c3a4d2e7e..fca5d6a82 100644 --- a/app/api/departements.py +++ b/app/api/departements.py @@ -89,6 +89,16 @@ def list_etudiants(dept_ident: str): return jsonify([etud.to_dict_short() for etud in dept.etudiants]) +@bp.route("/departement//formsemestres_ids", methods=["GET"]) +@token_auth.login_required +@token_permission_required(Permission.APIView) +def formsemestres_ids(dept_ident: str): + """liste des ids formsemestre du département""" + # Le département, spécifié par un id ou un acronyme + dept = get_departement(dept_ident) + return jsonify([formsemestre.id for formsemestre in dept.formsemestres]) + + @bp.route("/departement//formsemestres_courants", methods=["GET"]) @token_auth.login_required @token_permission_required(Permission.APIView) From 2d40932b5021d9ff07b1caca2687f11541636b09 Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Thu, 5 May 2022 19:50:09 +0200 Subject: [PATCH 26/32] API: tests: factorisation du code --- app/api/etudiants.py | 3 +- tests/api/test_api_departements.py | 15 +++++- tests/api/test_api_etudiants.py | 55 +------------------ tests/api/test_api_formations.py | 85 ++---------------------------- tests/api/test_api_formsemestre.py | 37 ++----------- tests/api/tools_test_api.py | 80 ++++++++++++++++++++++++++++ 6 files changed, 103 insertions(+), 172 deletions(-) diff --git a/app/api/etudiants.py b/app/api/etudiants.py index ab41fc319..6ad762c55 100644 --- a/app/api/etudiants.py +++ b/app/api/etudiants.py @@ -20,8 +20,7 @@ from app.scodoc.sco_permissions import Permission @token_permission_required(Permission.APIView) def etudiants_courant(long=False): """ - Retourne la liste des étudiants inscrits dans un - formsemestre actuellement en cours. + Liste des étudiants inscrits dans un formsemestre actuellement en cours. Exemple de résultat : [ diff --git a/tests/api/test_api_departements.py b/tests/api/test_api_departements.py index cbb296fa9..436cab2b5 100644 --- a/tests/api/test_api_departements.py +++ b/tests/api/test_api_departements.py @@ -33,7 +33,7 @@ DEPARTEMENT_FIELDS = [ def test_departements(api_headers): """ " - Routes: /departements_ids, /departement + Routes: /departements_ids, /departement, /departement//formsemestres_ids """ # --- Liste des ids @@ -68,6 +68,19 @@ def test_departements(api_headers): dept_b = r.json() assert dept_a == dept_b + # Liste des formsemestres + r = requests.get( + f"{API_URL}/departement/{dept_a['acronym']}/formsemestres_ids", + headers=api_headers, + verify=CHECK_CERTIFICATE, + ) + assert r.status_code == 200 + dept_ids = r.json() + assert isinstance(dept_ids, list) + assert all(isinstance(x, int) for x in dept_ids) + assert len(dept_ids) > 0 + assert dept_id in dept_ids + def test_list_etudiants(api_headers): fields = {"id", "nip", "ine", "nom", "nom_usuel", "prenom", "civilite"} diff --git a/tests/api/test_api_etudiants.py b/tests/api/test_api_etudiants.py index 99997da01..d2984f8e2 100644 --- a/tests/api/test_api_etudiants.py +++ b/tests/api/test_api_etudiants.py @@ -21,60 +21,7 @@ import requests from tests.api.setup_test_api import API_URL, CHECK_CERTIFICATE, api_headers from tests.api.tools_test_api import verify_fields - -ETUD_FIELDS = { - "boursier", - "civilite", - "code_ine", - "code_nip", - "codepostaldomicile", - "date_naissance", - "dept_naissance", - "description", - "domicile", - "email", - "emailperso", - "etudid", - "id", - "lieu_naissance", - "nationalite", - "nom", - "nomprenom", - "paysdomicile", - "prenom", - "telephone", - "telephonemobile", - "typeadresse", - "villedomicile", -} - -FSEM_FIELDS = { - "block_moyennes", - "bul_bgcolor", - "bul_hide_xml", - "date_debut_iso", - "date_debut", - "date_fin_iso", - "date_fin", - "dept_id", - "elt_annee_apo", - "elt_sem_apo", - "ens_can_edit_eval", - "etat", - "formation_id", - "formsemestre_id", - "gestion_compensation", - "gestion_semestrielle", - "id", - "modalite", - "resp_can_change_ens", - "resp_can_edit", - "responsables", - "semestre_id", - "titre_formation", - "titre_num", - "titre", -} +from tests.api.tools_test_api import ETUD_FIELDS, FSEM_FIELDS def test_etudiants_courant(api_headers): diff --git a/tests/api/test_api_formations.py b/tests/api/test_api_formations.py index e14fcb2c9..b61037da0 100644 --- a/tests/api/test_api_formations.py +++ b/tests/api/test_api_formations.py @@ -21,9 +21,9 @@ import requests from tests.api.setup_test_api import API_URL, CHECK_CERTIFICATE, api_headers from tests.api.tools_test_api import verify_fields +from tests.api.tools_test_api import FORMATION_FIELDS, MODIMPL_FIELDS -# formations def test_formations_ids(api_headers): """ Route: /formations_ids @@ -41,25 +41,10 @@ def test_formations_ids(api_headers): assert all(isinstance(x, int) for x in formations_ids) -# formations_by_id def test_formations_by_id(api_headers): """ Route: /formation/ """ - fields = [ - "id", - "acronyme", - "titre_officiel", - "formation_code", - "code_specialite", - "dept_id", - "titre", - "version", - "type_parcours", - "referentiel_competence_id", - "formation_id", - ] - r = requests.get( API_URL + "/formation/1", headers=api_headers, @@ -67,9 +52,7 @@ def test_formations_by_id(api_headers): ) assert r.status_code == 200 formation = r.json() - - fields_ok = verify_fields(formation, fields) - assert fields_ok is True + assert verify_fields(formation, FORMATION_FIELDS) is True # TODO tester le contenu de certains champs @@ -77,31 +60,14 @@ def test_formation_export(api_headers): """ Route: /formation/formation_export/ """ - fields = [ - "id", - "acronyme", - "titre_officiel", - "formation_code", - "code_specialite", - "dept_id", - "titre", - "version", - "type_parcours", - "referentiel_competence_id", - "formation_id", - "ue", - ] r = requests.get( API_URL + "/formation/formation_export/1", headers=api_headers, verify=CHECK_CERTIFICATE, ) assert r.status_code == 200 - export_formation = r.json() - - fields_ok = verify_fields(export_formation, fields) - assert fields_ok is True + assert verify_fields(export_formation, FORMATION_FIELDS) is True # TODO tester le contenu de certains champs @@ -119,17 +85,6 @@ def test_moduleimpl(api_headers): """ Route: /formation/moduleimpl/ """ - fields = [ - "id", - "formsemestre_id", - "computation_expr", - "module_id", - "responsable_id", - "moduleimpl_id", - "ens", - "module", - ] - r = requests.get( API_URL + "/formation/moduleimpl/1", headers=api_headers, @@ -137,42 +92,10 @@ def test_moduleimpl(api_headers): ) assert r.status_code == 200 moduleimpl = r.json() - - fields_ok = verify_fields(moduleimpl, fields) - assert fields_ok is True + assert verify_fields(moduleimpl, MODIMPL_FIELDS) is True # TODO tester le contenu de certains champs -def test_moduleimpls_sem(api_headers): - """ - Route: /formation/moduleimpl/formsemestre//list - """ - fields = [ - "id", - "formsemestre_id", - "computation_expr", - "module_id", - "responsable_id", - "moduleimpl_id", - "ens", - "module", - "moduleimpl_id", - "ens", - ] - r = requests.get( - API_URL + "/formation/moduleimpl/formsemestre/1/list", - headers=api_headers, - verify=CHECK_CERTIFICATE, - ) - assert r.status_code == 200 - moduleimpls = r.json() - moduleimpl = moduleimpls[0] - - fields_ok = verify_fields(moduleimpl, fields) - assert len(moduleimpls) == 21 # XXX HARDCODED ! - assert fields_ok is True - - def test_referentiel_competences(api_headers): """ Route: "/formation//referentiel_competences", diff --git a/tests/api/test_api_formsemestre.py b/tests/api/test_api_formsemestre.py index 724713a10..38f809335 100644 --- a/tests/api/test_api_formsemestre.py +++ b/tests/api/test_api_formsemestre.py @@ -21,11 +21,12 @@ import requests from tests.api.setup_test_api import API_URL, CHECK_CERTIFICATE, api_headers from tests.api.tools_test_api import verify_fields +from tests.api.tools_test_api import ETUD_FIELDS, FSEM_FIELDS def test_formsemestre(api_headers): """ - Route: + Route: /formsemestre/ """ r = requests.get( API_URL + "/formsemestre/1", @@ -33,40 +34,8 @@ def test_formsemestre(api_headers): verify=CHECK_CERTIFICATE, ) assert r.status_code == 200 - formsemestre = r.json() - - fields = [ - "date_fin", - "resp_can_edit", - "dept_id", - "etat", - "resp_can_change_ens", - "id", - "modalite", - "ens_can_edit_eval", - "formation_id", - "gestion_compensation", - "elt_sem_apo", - "semestre_id", - "bul_hide_xml", - "elt_annee_apo", - "titre", - "block_moyennes", - "scodoc7_id", - "date_debut", - "gestion_semestrielle", - "bul_bgcolor", - "formsemestre_id", - "titre_num", - "date_debut_iso", - "date_fin_iso", - "responsables", - ] - - fields_ok = verify_fields(formsemestre, fields) - - assert fields_ok is True + assert verify_fields(formsemestre, FSEM_FIELDS) def test_etudiant_bulletin(api_headers): diff --git a/tests/api/tools_test_api.py b/tests/api/tools_test_api.py index 7be43a891..5af41b9ed 100644 --- a/tests/api/tools_test_api.py +++ b/tests/api/tools_test_api.py @@ -12,3 +12,83 @@ def verify_fields(json_response: dict, expected_fields: set) -> bool: Retourne True ou False """ return all(field in json_response for field in expected_fields) + + +ETUD_FIELDS = { + "boursier", + "civilite", + "code_ine", + "code_nip", + "codepostaldomicile", + "date_naissance", + "dept_naissance", + "description", + "domicile", + "email", + "emailperso", + "etudid", + "id", + "lieu_naissance", + "nationalite", + "nom", + "nomprenom", + "paysdomicile", + "prenom", + "telephone", + "telephonemobile", + "typeadresse", + "villedomicile", +} + +FORMATION_FIELDS = { + "id", + "acronyme", + "titre_officiel", + "formation_code", + "code_specialite", + "dept_id", + "titre", + "version", + "type_parcours", + "referentiel_competence_id", + "formation_id", +} + +FSEM_FIELDS = { + "block_moyennes", + "bul_bgcolor", + "bul_hide_xml", + "date_debut_iso", + "date_debut", + "date_fin_iso", + "date_fin", + "dept_id", + "elt_annee_apo", + "elt_sem_apo", + "ens_can_edit_eval", + "etat", + "formation_id", + "formsemestre_id", + "gestion_compensation", + "gestion_semestrielle", + "id", + "modalite", + "resp_can_change_ens", + "resp_can_edit", + "responsables", + "semestre_id", + "titre_formation", + "titre_num", + "titre", +} + +MODIMPL_FIELDS = { + "id", + "formsemestre_id", + "computation_expr", + "module_id", + "responsable_id", + "moduleimpl_id", + "ens", + "module", +} From 4ab43f59c10a21652b4528be7851858aca5a6f8f Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Thu, 5 May 2022 20:06:49 +0200 Subject: [PATCH 27/32] API: test_api_formsemestre --- tests/api/test_api_formsemestre.py | 114 ++++++++++------------------- tests/api/tools_test_api.py | 37 ++++++++++ 2 files changed, 74 insertions(+), 77 deletions(-) diff --git a/tests/api/test_api_formsemestre.py b/tests/api/test_api_formsemestre.py index 38f809335..57d797721 100644 --- a/tests/api/test_api_formsemestre.py +++ b/tests/api/test_api_formsemestre.py @@ -18,10 +18,11 @@ Utilisation : """ import requests +from app.api.formsemestres import formsemestre from tests.api.setup_test_api import API_URL, CHECK_CERTIFICATE, api_headers -from tests.api.tools_test_api import verify_fields -from tests.api.tools_test_api import ETUD_FIELDS, FSEM_FIELDS +from tests.api.tools_test_api import MODIMPL_FIELDS, verify_fields +from tests.api.tools_test_api import FSEM_FIELDS, UE_FIELDS, MODULE_FIELDS def test_formsemestre(api_headers): @@ -42,26 +43,35 @@ def test_etudiant_bulletin(api_headers): """ Route: """ + formsemestre_id = 1 r = requests.get( - API_URL + "/formsemestre/1/etudiant/etudid/1/bulletin", + f"{API_URL}/etudiant/etudid/1/formsemestre/{formsemestre_id}/bulletin", headers=api_headers, verify=CHECK_CERTIFICATE, ) assert r.status_code == 200 + bull_a = r.json() r = requests.get( - API_URL + "/formsemestre/1/etudiant/nip/1/bulletin", + f"{API_URL}/etudiant/nip/1/formsemestre/{formsemestre_id}/bulletin", headers=api_headers, verify=CHECK_CERTIFICATE, ) assert r.status_code == 200 + bull_b = r.json() r = requests.get( - API_URL + "/formsemestre/1/etudiant/ine/1/bulletin", + f"{API_URL}/etudiant/ine/1/formsemestre/{formsemestre_id}/bulletin", headers=api_headers, verify=CHECK_CERTIFICATE, ) assert r.status_code == 200 + bull_c = r.json() + # elimine les dates de publication pour comparer les autres champs + del bull_a["date"] + del bull_b["date"] + del bull_c["date"] + assert bull_a == bull_b == bull_c def test_bulletins(api_headers): @@ -85,67 +95,11 @@ def test_bulletins(api_headers): # ) # assert r.status_code == 200 -# TODO A revoir -def test_semestre_index(api_headers): - """ - Route: TODO - """ - ue_fields = [ - "semestre_idx", - "type", - "formation_id", - "ue_code", - "id", - "ects", - "acronyme", - "is_external", - "numero", - "code_apogee", - "titre", - "coefficient", - "color", - "ue_id", - ] - ressource_fields = [ - "heures_tp", - "code_apogee", - "titre", - "coefficient", - "module_type", - "id", - "ects", - "abbrev", - "ue_id", - "code", - "formation_id", - "heures_cours", - "matiere_id", - "heures_td", - "semestre_id", - "numero", - "module_id", - ] - - sae_fields = [ - "heures_tp", - "code_apogee", - "titre", - "coefficient", - "module_type", - "id", - "ects", - "abbrev", - "ue_id", - "code", - "formation_id", - "heures_cours", - "matiere_id", - "heures_td", - "semestre_id", - "numero", - "module_id", - ] +def test_formsemestre_programme(api_headers): + """ + Route: /formsemestre/1/programme + """ r = requests.get( API_URL + "/formsemestre/1/programme", @@ -153,16 +107,22 @@ def test_semestre_index(api_headers): verify=CHECK_CERTIFICATE, ) assert r.status_code == 200 - assert len(r.json()) == 3 + prog = r.json() + assert isinstance(prog, dict) + assert "ues" in prog + assert "modules" in prog + assert "ressources" in prog + assert "saes" in prog + assert isinstance(prog["ues"], list) + assert isinstance(prog["modules"], list) + ue = prog["ues"][0] + modules = prog["modules"] + # Il y a toujours au moins une SAE et une ressources dans notre base de test + ressource = prog["ressources"][0] + sae = prog["saes"][0] - ue = r.json()["ues"][0] - ressource = r.json()["ressources"][0] - sae = r.json()["saes"][0] - - fields_ue_OK = verify_fields(ue, ue_fields) - fields_ressource_OK = verify_fields(ressource, ressource_fields) - fields_sae_OK = verify_fields(sae, sae_fields) - - assert fields_ue_OK is True - assert fields_ressource_OK is True - assert fields_sae_OK is True + assert verify_fields(ue, UE_FIELDS) + if len(modules) > 1: + assert verify_fields(modules[0], MODIMPL_FIELDS) + assert verify_fields(ressource, MODIMPL_FIELDS) + assert verify_fields(sae, MODIMPL_FIELDS) diff --git a/tests/api/tools_test_api.py b/tests/api/tools_test_api.py index 5af41b9ed..582b03fbc 100644 --- a/tests/api/tools_test_api.py +++ b/tests/api/tools_test_api.py @@ -92,3 +92,40 @@ MODIMPL_FIELDS = { "ens", "module", } + +MODULE_FIELDS = { + "heures_tp", + "code_apogee", + "titre", + "coefficient", + "module_type", + "id", + "ects", + "abbrev", + "ue_id", + "code", + "formation_id", + "heures_cours", + "matiere_id", + "heures_td", + "semestre_id", + "numero", + "module_id", +} + +UE_FIELDS = { + "semestre_idx", + "type", + "formation_id", + "ue_code", + "id", + "ects", + "acronyme", + "is_external", + "numero", + "code_apogee", + "titre", + "coefficient", + "color", + "ue_id", +} From 7594781001b77ed93db4684c9a7be01939b6944f Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Thu, 5 May 2022 22:40:44 +0200 Subject: [PATCH 28/32] API: fix test_api_permissions.py --- tests/api/test_api_permissions.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/api/test_api_permissions.py b/tests/api/test_api_permissions.py index aa80f36fb..eeebb8b4d 100644 --- a/tests/api/test_api_permissions.py +++ b/tests/api/test_api_permissions.py @@ -43,6 +43,8 @@ def test_permissions(api_headers): # "date_debut": # "date_fin": "dept": "TAPI", + "dept_ident": "TAPI", + "dept_id": 1, "etape_apo": "???", "etat": "I", "evaluation_id": 1, From f0744c594ad735752f6640da618104570e4346b4 Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Thu, 5 May 2022 22:44:46 +0200 Subject: [PATCH 29/32] Typo, fixes #375 --- app/scodoc/sco_dept.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app/scodoc/sco_dept.py b/app/scodoc/sco_dept.py index 14c476513..42f3db762 100644 --- a/app/scodoc/sco_dept.py +++ b/app/scodoc/sco_dept.py @@ -131,8 +131,10 @@ def index_html(showcodes=0, showsemtable=0): if not showsemtable: H.append( f"""
    -

    Voir tous les semestres ({len(othersems)} verrouillés) +

    Voir table des semestres (dont {len(othersems)} + verrouillé{'s' if len(othersems) else ''})

    """ ) From 912af8760340e25068acd8a1a1ae26cce923ff39 Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Fri, 6 May 2022 01:06:58 +0200 Subject: [PATCH 30/32] Fixes #377 --- app/scodoc/sco_abs_views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/scodoc/sco_abs_views.py b/app/scodoc/sco_abs_views.py index a4e1d3a72..8427777f2 100644 --- a/app/scodoc/sco_abs_views.py +++ b/app/scodoc/sco_abs_views.py @@ -965,7 +965,7 @@ def _tables_abs_etud( )[0] if format == "html": ex.append( - f"""{mod["module"]["code"] or "(module sans code)"}""" ) From 9809936d70f041401f03e29104c17add84650cb1 Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Fri, 6 May 2022 01:15:37 +0200 Subject: [PATCH 31/32] Saisie note eval sans dates: Fixes #378 --- app/models/evaluations.py | 5 +++-- app/scodoc/sco_saisie_notes.py | 37 +++++++++++++++++----------------- 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/app/models/evaluations.py b/app/models/evaluations.py index 46244a9a8..84383adc5 100644 --- a/app/models/evaluations.py +++ b/app/models/evaluations.py @@ -178,11 +178,12 @@ def evaluation_enrich_dict(e): else: e["descrheure"] = "" # matin, apresmidi: utile pour se referer aux absences: - if heure_debut_dt < datetime.time(12, 00): + + if e["jour"] and heure_debut_dt < datetime.time(12, 00): e["matin"] = 1 else: e["matin"] = 0 - if heure_fin_dt > datetime.time(12, 00): + if e["jour"] and heure_fin_dt > datetime.time(12, 00): e["apresmidi"] = 1 else: e["apresmidi"] = 0 diff --git a/app/scodoc/sco_saisie_notes.py b/app/scodoc/sco_saisie_notes.py index 9cda23725..733ef1730 100644 --- a/app/scodoc/sco_saisie_notes.py +++ b/app/scodoc/sco_saisie_notes.py @@ -799,22 +799,22 @@ def feuille_saisie_notes(evaluation_id, group_ids=[]): evals = sco_evaluation_db.do_evaluation_list({"evaluation_id": evaluation_id}) if not evals: raise ScoValueError("invalid evaluation_id") - E = evals[0] - M = sco_moduleimpl.moduleimpl_list(moduleimpl_id=E["moduleimpl_id"])[0] + eval_dict = evals[0] + M = sco_moduleimpl.moduleimpl_list(moduleimpl_id=eval_dict["moduleimpl_id"])[0] formsemestre_id = M["formsemestre_id"] Mod = sco_edit_module.module_list(args={"module_id": M["module_id"]})[0] sem = sco_formsemestre.get_formsemestre(M["formsemestre_id"]) mod_responsable = sco_users.user_info(M["responsable_id"]) - if E["jour"]: - indication_date = ndb.DateDMYtoISO(E["jour"]) + if eval_dict["jour"]: + indication_date = ndb.DateDMYtoISO(eval_dict["jour"]) else: - indication_date = scu.sanitize_filename(E["description"])[:12] - evalname = "%s-%s" % (Mod["code"], indication_date) + indication_date = scu.sanitize_filename(eval_dict["description"])[:12] + eval_name = "%s-%s" % (Mod["code"], indication_date) - if E["description"]: - evaltitre = "%s du %s" % (E["description"], E["jour"]) + if eval_dict["description"]: + evaltitre = "%s du %s" % (eval_dict["description"], eval_dict["jour"]) else: - evaltitre = "évaluation du %s" % E["jour"] + evaltitre = "évaluation du %s" % eval_dict["jour"] description = "%s en %s (%s) resp. %s" % ( evaltitre, Mod["abbrev"] or "", @@ -847,7 +847,7 @@ def feuille_saisie_notes(evaluation_id, group_ids=[]): # une liste de liste de chaines: lignes de la feuille de calcul L = [] - etuds = _get_sorted_etuds(E, etudids, formsemestre_id) + etuds = _get_sorted_etuds(eval_dict, etudids, formsemestre_id) for e in etuds: etudid = e["etudid"] groups = sco_groups.get_etud_groups(etudid, formsemestre_id) @@ -865,8 +865,10 @@ def feuille_saisie_notes(evaluation_id, group_ids=[]): ] ) - filename = "notes_%s_%s" % (evalname, gr_title_filename) - xls = sco_excel.excel_feuille_saisie(E, sem["titreannee"], description, lines=L) + filename = "notes_%s_%s" % (eval_name, gr_title_filename) + xls = sco_excel.excel_feuille_saisie( + eval_dict, sem["titreannee"], description, lines=L + ) return scu.send_file(xls, filename, scu.XLSX_SUFFIX, mime=scu.XLSX_MIMETYPE) # return sco_excel.send_excel_file(xls, filename) @@ -1008,10 +1010,9 @@ def saisie_notes(evaluation_id, group_ids=[]): return "\n".join(H) -def _get_sorted_etuds(E, etudids, formsemestre_id): - sem = sco_formsemestre.get_formsemestre(formsemestre_id) +def _get_sorted_etuds(eval_dict: dict, etudids: list, formsemestre_id: int): notes_db = sco_evaluation_db.do_evaluation_get_all_notes( - E["evaluation_id"] + eval_dict["evaluation_id"] ) # Notes existantes cnx = ndb.GetDBConnexion() etuds = [] @@ -1028,9 +1029,9 @@ def _get_sorted_etuds(E, etudids, formsemestre_id): e["groups"] = sco_groups.get_etud_groups(etudid, formsemestre_id) # Information sur absence (tenant compte de la demi-journée) - jour_iso = ndb.DateDMYtoISO(E["jour"]) + jour_iso = ndb.DateDMYtoISO(eval_dict["jour"]) warn_abs_lst = [] - if E["matin"]: + if eval_dict["matin"]: nbabs = sco_abs.count_abs(etudid, jour_iso, jour_iso, matin=1) nbabsjust = sco_abs.count_abs_just(etudid, jour_iso, jour_iso, matin=1) if nbabs: @@ -1038,7 +1039,7 @@ def _get_sorted_etuds(E, etudids, formsemestre_id): warn_abs_lst.append("absent justifié le matin !") else: warn_abs_lst.append("absent le matin !") - if E["apresmidi"]: + if eval_dict["apresmidi"]: nbabs = sco_abs.count_abs(etudid, jour_iso, jour_iso, matin=0) nbabsjust = sco_abs.count_abs_just(etudid, jour_iso, jour_iso, matin=0) if nbabs: From 1accb5025a5d55ceec74e39bf28f7765ca8a0e9f Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Fri, 6 May 2022 07:25:35 +0200 Subject: [PATCH 32/32] Fix bulletins BUT pdf UE bonus quand non inscrit (#376) --- app/api/formsemestres.py | 4 ++-- app/but/bulletin_but_pdf.py | 7 +++++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/app/api/formsemestres.py b/app/api/formsemestres.py index 8bd5a108f..577b2dbc4 100644 --- a/app/api/formsemestres.py +++ b/app/api/formsemestres.py @@ -16,9 +16,9 @@ from app.scodoc.sco_utils import ModuleType @token_permission_required(Permission.APIView) def formsemestre(formsemestre_id: int): """ - Retourne l'information sur le formsemestre correspondant au formsemestre_id + Information sur le formsemestre indiqué. - formsemestre_id : l'id d'un formsemestre + formsemestre_id : l'id du formsemestre Exemple de résultat : { diff --git a/app/but/bulletin_but_pdf.py b/app/but/bulletin_but_pdf.py index 36d11d1e4..e3e40c51f 100644 --- a/app/but/bulletin_but_pdf.py +++ b/app/but/bulletin_but_pdf.py @@ -127,6 +127,9 @@ class BulletinGeneratorStandardBUT(BulletinGeneratorStandard): def ue_rows(self, rows: list, ue_acronym: str, ue: dict, title_bg: tuple): "Décrit une UE dans la table synthèse: titre, sous-titre et liste modules" + if (ue["type"] == UE_SPORT) and len(ue.get("modules", [])) == 0: + # ne mentionne l'UE que s'il y a des modules + return # 1er ligne titre UE moy_ue = ue.get("moyenne") t = { @@ -206,7 +209,7 @@ class BulletinGeneratorStandardBUT(BulletinGeneratorStandard): for mod_code, mod in ue["modules"].items(): rows.append( { - "titre": f"{mod_code} {mod['titre']}", + "titre": f"{mod_code or ''} {mod['titre'] or ''}", } ) self.evaluations_rows(rows, mod["evaluations"]) @@ -313,7 +316,7 @@ class BulletinGeneratorStandardBUT(BulletinGeneratorStandard): "lignes des évaluations" for e in evaluations: t = { - "titre": f"{e['description']}", + "titre": f"{e['description'] or ''}", "moyenne": e["note"]["value"], "_moyenne_pdf": Paragraph( f"""{e["note"]["value"]}"""