From b740f403898c7adda536b085b3ac2af1c92df06c Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Thu, 5 May 2022 08:21:42 +0200 Subject: [PATCH 01/20] 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 24ec5eeb..655250af 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 02/20] =?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 9ccb6362..49445bc9 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 03/20] 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 fda72383..dee03510 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 04/20] =?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 917b0136..912136e6 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 05/20] 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 aaf19426..abea73b3 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 a6baa2d8..ab41fc31 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 3724c96a..f2443817 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 655250af..8bd5a108 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 872aa104..91bac825 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 b8f159ad..e14fcb2c 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 06/20] 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 abea73b3..c3a4d2e7 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 91bac825..cbb296fa 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 07/20] =?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 d0a224c5..7be43a89 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 08/20] 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 ec6a23d1..99997da0 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 09/20] 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 c3a4d2e7..fca5d6a8 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 10/20] 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 ab41fc31..6ad762c5 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 cbb296fa..436cab2b 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 99997da0..d2984f8e 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 e14fcb2c..b61037da 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 724713a1..38f80933 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 7be43a89..5af41b9e 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 11/20] 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 38f80933..57d79772 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 5af41b9e..582b03fb 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 12/20] 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 aa80f36f..eeebb8b4 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 13/20] 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 14c47651..42f3db76 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 14/20] 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 a4e1d3a7..8427777f 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 15/20] 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 46244a9a..84383adc 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 9cda2372..733ef173 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 16/20] 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 8bd5a108..577b2dbc 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 36d11d1e..e3e40c51 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"]}""" From bba79064938b1561b98dab93e0b7616c44b3ab28 Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Fri, 6 May 2022 09:38:30 +0200 Subject: [PATCH 17/20] =?UTF-8?q?API:=20traitement=20des=20param=C3=A8tres?= =?UTF-8?q?=20erron=C3=A9s=20(404)=20+=20test?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/api/absences.py | 24 +++++++++----------- app/api/etudiants.py | 40 +++++++++++++++++++-------------- app/api/evaluations.py | 2 +- app/api/formations.py | 6 +---- app/api/partitions.py | 12 ++-------- app/api/tools.py | 8 ++++--- tests/api/test_api_etudiants.py | 8 +++++++ 7 files changed, 50 insertions(+), 50 deletions(-) diff --git a/app/api/absences.py b/app/api/absences.py index b37a256a..1992454c 100644 --- a/app/api/absences.py +++ b/app/api/absences.py @@ -5,7 +5,7 @@ from flask import jsonify 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.api.tools import get_etud_from_etudid_or_nip_or_ine from app.scodoc import notesdb as ndb from app.scodoc import sco_abs @@ -50,12 +50,11 @@ def absences(etudid: int = None, nip: int = None, ine: int = None): """ if etudid is None: # Récupération de l'étudiant - etud = get_etu_from_etudid_or_nip_or_ine(etudid, nip, ine) + etud = get_etud_from_etudid_or_nip_or_ine(etudid, nip, ine) if etud is None: return error_response( - 409, - message="La requête ne peut être traitée en l’état actuel.\n " - "Veuillez vérifier que l'id de l'étudiant (etudid, nip, ine) est valide", + 404, + message="id de l'étudiant (etudid, nip, ine) inconnu", ) etudid = etud.etudid @@ -103,16 +102,13 @@ def absences_just(etudid: int = None, nip: int = None, ine: int = None): ] """ 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: + etud = get_etud_from_etudid_or_nip_or_ine(etudid, nip, ine) + if etud is None: return error_response( - 409, - message="La requête ne peut être traitée en l’état actuel.\n " - "Veuillez vérifier que l'id de l'étudiant (etudid, nip, ine) est valide", + 404, + message="id de l'étudiant (etudid, nip, ine) inconnu", ) + etudid = etud.etudid # Récupération des absences justifiées de l'étudiant abs_just = [ @@ -149,7 +145,7 @@ def absences_just(etudid: int = None, nip: int = None, ine: int = None): # 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" +# 404, message="La requête ne peut être traitée en l’état actuel" # ) # data = [] diff --git a/app/api/etudiants.py b/app/api/etudiants.py index 6ad762c5..afeb8aba 100644 --- a/app/api/etudiants.py +++ b/app/api/etudiants.py @@ -7,7 +7,7 @@ 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.api.tools import get_etud_from_etudid_or_nip_or_ine from app.models import FormSemestreInscription, FormSemestre, Identite from app.scodoc import sco_bulletins from app.scodoc import sco_groups @@ -96,8 +96,12 @@ def etudiant(etudid: int = None, nip: int = None, ine: int = None): } """ # Récupération de l'étudiant - etud = get_etu_from_etudid_or_nip_or_ine(etudid, nip, ine) - + etud = get_etud_from_etudid_or_nip_or_ine(etudid, nip, ine) + if etud is None: + return error_response( + 404, + message="id de l'étudiant (etudid, nip, ine) inconnu", + ) # Mise en forme des données data = etud.to_dict_bul(include_urls=False) @@ -150,7 +154,12 @@ def etudiant_formsemestres(etudid: int = None, nip: int = None, ine: int = None) ] """ # Récupération de l'étudiant - etud = get_etu_from_etudid_or_nip_or_ine(etudid, nip, ine) + etud = get_etud_from_etudid_or_nip_or_ine(etudid, nip, ine) + if etud is None: + return error_response( + 404, + message="id de l'étudiant (etudid, nip, ine) inconnu", + ) formsemestres = models.FormSemestre.query.filter( models.FormSemestreInscription.etudid == etud.id, @@ -383,17 +392,15 @@ def etudiant_bulletin_semestre( app.set_sco_dept(dept.acronym) - # Récupération de l'étudiant - try: - etu = get_etu_from_etudid_or_nip_or_ine(etudid, nip, ine) - except AttributeError: + etud = get_etud_from_etudid_or_nip_or_ine(etudid, nip, ine) + if etud is None: return error_response( - 409, - message="La requête ne peut être traitée en l’état actuel.\n " - "Veuillez vérifier que l'id de l'étudiant (etudid, nip, ine) est valide", + 404, + message="id de l'étudiant (etudid, nip, ine) inconnu", ) - - return sco_bulletins.get_formsemestre_bulletin_etud_json(formsemestre, etu, version) + return sco_bulletins.get_formsemestre_bulletin_etud_json( + formsemestre, etud, version + ) @bp.route( @@ -446,12 +453,11 @@ def etudiant_groups( ] """ if etudid is None: - etud = get_etu_from_etudid_or_nip_or_ine(etudid, nip, ine) + etud = get_etud_from_etudid_or_nip_or_ine(etudid, nip, ine) if etud is None: return error_response( - 409, - message="La requête ne peut être traitée en l’état actuel.\n " - "Veuillez vérifier que l'id de l'étudiant (etudid, nip, ine) est valide", + 404, + message="id de l'étudiant (etudid, nip, ine) inconnu", ) etudid = etud.etudid diff --git a/app/api/evaluations.py b/app/api/evaluations.py index 7fb1e6c5..cef58a42 100644 --- a/app/api/evaluations.py +++ b/app/api/evaluations.py @@ -99,7 +99,7 @@ def evaluation_notes(evaluation_id: int): data = do_evaluation_get_all_notes(evaluation_id) except AttributeError: # ??? return error_response( - 409, + 404, message="La requête ne peut être traitée en l’état actuel.", ) diff --git a/app/api/formations.py b/app/api/formations.py index f2443817..6ca6fde1 100644 --- a/app/api/formations.py +++ b/app/api/formations.py @@ -176,11 +176,7 @@ def formation_export_by_formation_id(formation_id: int, export_ids=False): # Utilisation de la fonction formation_export data = sco_formations.formation_export(formation_id, export_ids) except ValueError: - 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 error_response(500, message="Erreur inconnue") return jsonify(data) diff --git a/app/api/partitions.py b/app/api/partitions.py index ed75fd11..1ee74c46 100644 --- a/app/api/partitions.py +++ b/app/api/partitions.py @@ -113,11 +113,7 @@ def etud_in_group(group_id: int, etat=None): data = get_group_members(group_id, etat) if len(data) == 0: - return error_response( - 409, - message="La requête ne peut être traitée en l’état actuel. \n" - "Aucun groupe ne correspond au 'group_id' renseigné", - ) + return error_response(404, message="group_id inconnu") return jsonify(data) @@ -146,8 +142,4 @@ def set_groups( setGroups(partition_id, groups_lists, groups_to_create, groups_to_delete) return error_response(200, message="Groups set") except ValueError: - return error_response( - 409, - message="La requête ne peut être traitée en l’état actuel. \n" - "Veillez vérifier la conformité des éléments passé en paramètres", - ) + return error_response(404, message="Erreur") diff --git a/app/api/tools.py b/app/api/tools.py index 35b07cf2..e03d8334 100644 --- a/app/api/tools.py +++ b/app/api/tools.py @@ -1,15 +1,17 @@ from app import models -def get_etu_from_etudid_or_nip_or_ine(etudid, nip, ine): +def get_etud_from_etudid_or_nip_or_ine( + etudid=None, nip=None, ine=None +) -> models.Identite: """ - Fonction qui retourne un etudiant en fonction de l'etudid, code nip et code ine rentré en paramètres + etudiant en fonction de l'etudid, code nip et code ine rentré en paramètres etudid : None ou un int etudid nip : None ou un int code_nip ine : None ou un int code_ine - Exemple de résultat: + Return None si étudiant inexistant. """ if etudid is None: if nip is None: # si ine diff --git a/tests/api/test_api_etudiants.py b/tests/api/test_api_etudiants.py index d2984f8e..621f0859 100644 --- a/tests/api/test_api_etudiants.py +++ b/tests/api/test_api_etudiants.py @@ -180,6 +180,14 @@ def test_etudiant_bulletin_semestre(api_headers): bul = r.json() assert len(bul) == 13 # HARDCODED + ### --- Test étudiant inexistant + r = requests.get( + API_URL + "/etudiant/ine/189919919119191/formsemestre/1/bulletin", + headers=api_headers, + verify=CHECK_CERTIFICATE, + ) + assert r.status_code == 404 + def test_etudiant_groups(api_headers): """ From 34d64b3fd86d043664b8220aa11b18471b553418 Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Sat, 7 May 2022 08:23:30 +0200 Subject: [PATCH 18/20] =?UTF-8?q?API:=20Fix=20acc=C3=A8s=20par=20INE=20et?= =?UTF-8?q?=20NIP=20alphanum=C3=A9riques?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/api/absences.py | 4 +- app/api/etudiants.py | 22 ++++---- tests/api/test_api_etudiants.py | 45 +++++++++++++--- tests/api/test_api_jury.py | 52 +++++++++---------- .../fakedatabase/create_test_api_database.py | 15 +++--- 5 files changed, 88 insertions(+), 50 deletions(-) diff --git a/app/api/absences.py b/app/api/absences.py index 1992454c..44b1c350 100644 --- a/app/api/absences.py +++ b/app/api/absences.py @@ -14,8 +14,8 @@ 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"]) +@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): diff --git a/app/api/etudiants.py b/app/api/etudiants.py index afeb8aba..a41634f9 100644 --- a/app/api/etudiants.py +++ b/app/api/etudiants.py @@ -55,8 +55,8 @@ def etudiants_courant(long=False): @bp.route("/etudiant/etudid/", methods=["GET"]) -@bp.route("/etudiant/nip/", methods=["GET"]) -@bp.route("/etudiant/ine/", 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): @@ -109,8 +109,8 @@ 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") +@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): @@ -175,12 +175,12 @@ def etudiant_formsemestres(etudid: int = None, nip: int = None, ine: int = None) defaults={"version": "long"}, ) @bp.route( - "/etudiant/nip//formsemestre//bulletin", + "/etudiant/nip//formsemestre//bulletin", methods=["GET"], defaults={"version": "long"}, ) @bp.route( - "/etudiant/ine//formsemestre//bulletin", + "/etudiant/ine//formsemestre//bulletin", methods=["GET"], defaults={"version": "long"}, ) @@ -190,12 +190,12 @@ def etudiant_formsemestres(etudid: int = None, nip: int = None, ine: int = None) defaults={"version": "short"}, ) @bp.route( - "/etudiant/nip//formsemestre//bulletin/short", + "/etudiant/nip//formsemestre//bulletin/short", methods=["GET"], defaults={"version": "short"}, ) @bp.route( - "/etudiant/ine//formsemestre//bulletin/short", + "/etudiant/ine//formsemestre//bulletin/short", methods=["GET"], defaults={"version": "short"}, ) @@ -408,10 +408,12 @@ def etudiant_bulletin_semestre( methods=["GET"], ) @bp.route( - "/etudiant/nip//formsemestre//groups", methods=["GET"] + "/etudiant/nip//formsemestre//groups", + methods=["GET"], ) @bp.route( - "/etudiant/ine//formsemestre//groups", methods=["GET"] + "/etudiant/ine//formsemestre//groups", + methods=["GET"], ) @token_auth.login_required @token_permission_required(Permission.APIView) diff --git a/tests/api/test_api_etudiants.py b/tests/api/test_api_etudiants.py index 621f0859..c0ccfc9e 100644 --- a/tests/api/test_api_etudiants.py +++ b/tests/api/test_api_etudiants.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -"""Test Logos +"""Test API: accès aux étudiants Utilisation : créer les variables d'environnement: (indiquer les valeurs @@ -58,7 +58,7 @@ def test_etudiants_courant(api_headers): def test_etudiant(api_headers): """ - Route: + Routes: /etudiant/etudid, /etudiant/nip, /etudiant/ine """ ######### Test etudid ######### @@ -87,7 +87,7 @@ def test_etudiant(api_headers): ######### Test code ine ######### r = requests.get( - API_URL + "/etudiant/ine/1", + API_URL + "/etudiant/ine/INE1", headers=api_headers, verify=CHECK_CERTIFICATE, ) @@ -97,6 +97,39 @@ def test_etudiant(api_headers): fields_ok = verify_fields(etud, ETUD_FIELDS) assert fields_ok is True + # Vérifie le requetage des 3 1er étudiants + for etudid in (1, 2, 3): + r = requests.get( + f"{API_URL }/etudiant/etudid/{etudid}", + headers=api_headers, + verify=CHECK_CERTIFICATE, + ) + assert r.status_code == 200 + etud = r.json() + nip = etud["code_nip"] + ine = etud["code_ine"] + assert isinstance(etud["id"], int) + assert isinstance(nip, str) + assert isinstance(ine, str) + r = requests.get( + f"{API_URL }/etudiant/nip/{nip}", + headers=api_headers, + verify=CHECK_CERTIFICATE, + ) + assert r.status_code == 200 + etud_nip = r.json() + # On doit avoir obtenue le même étudiant + assert etud_nip == etud + r = requests.get( + f"{API_URL }/etudiant/ine/{ine}", + headers=api_headers, + verify=CHECK_CERTIFICATE, + ) + assert r.status_code == 200 + etud_ine = r.json() + # On doit avoir obtenue le même étudiant + assert etud_ine == etud + def test_etudiant_formsemestres(api_headers): """ @@ -132,7 +165,7 @@ def test_etudiant_formsemestres(api_headers): ######### Test code ine ######### r = requests.get( - API_URL + "/etudiant/ine/1/formsemestres", + API_URL + "/etudiant/ine/INE1/formsemestres", headers=api_headers, verify=CHECK_CERTIFICATE, ) @@ -172,7 +205,7 @@ def test_etudiant_bulletin_semestre(api_headers): ######### Test code ine ######### r = requests.get( - API_URL + "/etudiant/ine/1/formsemestre/1/bulletin", + API_URL + "/etudiant/ine/INE1/formsemestre/1/bulletin", headers=api_headers, verify=CHECK_CERTIFICATE, ) @@ -235,7 +268,7 @@ def test_etudiant_groups(api_headers): ######### Test code ine ######### r = requests.get( - API_URL + "/etudiant/ine/1/formsemestre/1/groups", + API_URL + "/etudiant/ine/INE1/formsemestre/1/groups", headers=api_headers, verify=CHECK_CERTIFICATE, ) diff --git a/tests/api/test_api_jury.py b/tests/api/test_api_jury.py index 13b0a0f1..23454d00 100644 --- a/tests/api/test_api_jury.py +++ b/tests/api/test_api_jury.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -"""Test Logos +"""Test API Jurys XXX TODO A ECRIRE Utilisation : créer les variables d'environnement: (indiquer les valeurs @@ -77,30 +77,30 @@ def test_set_decision_jury(api_headers): assert r.status_code == 200 -def test_annule_decision_jury(api_headers): - """ - Route: - """ - r = requests.get( - SCODOC_URL - + "/ScoDoc/api/jury/etudid//formsemestre//annule_decision", - headers=api_headers, - verify=CHECK_CERTIFICATE, - ) - assert r.status_code == 200 +# def test_annule_decision_jury(api_headers): +# """ +# Route: +# """ +# r = requests.get( +# SCODOC_URL +# + "/ScoDoc/api/jury/etudid//formsemestre//annule_decision", +# headers=api_headers, +# verify=CHECK_CERTIFICATE, +# ) +# assert r.status_code == 200 - r = requests.get( - SCODOC_URL - + "/ScoDoc/api/jury/nip//formsemestre//annule_decision", - headers=api_headers, - verify=CHECK_CERTIFICATE, - ) - assert r.status_code == 200 +# r = requests.get( +# SCODOC_URL +# + "/ScoDoc/api/jury/nip//formsemestre//annule_decision", +# headers=api_headers, +# verify=CHECK_CERTIFICATE, +# ) +# assert r.status_code == 200 - r = requests.get( - SCODOC_URL - + "/ScoDoc/api/jury/ine//formsemestre//annule_decision", - headers=api_headers, - verify=CHECK_CERTIFICATE, - ) - assert r.status_code == 200 +# r = requests.get( +# SCODOC_URL +# + "/ScoDoc/api/jury/ine//formsemestre//annule_decision", +# headers=api_headers, +# verify=CHECK_CERTIFICATE, +# ) +# assert r.status_code == 200 diff --git a/tools/fakedatabase/create_test_api_database.py b/tools/fakedatabase/create_test_api_database.py index ad1198f8..bfe4800e 100644 --- a/tools/fakedatabase/create_test_api_database.py +++ b/tools/fakedatabase/create_test_api_database.py @@ -28,7 +28,7 @@ import sys from app.auth.models import Role, User from app import models -from app.models import Departement, Formation, FormSemestre +from app.models import Departement, Formation, FormSemestre, Identite from app import db from app.scodoc import ( sco_cache, @@ -95,15 +95,18 @@ def create_users(dept: Departement) -> tuple: return user, other -def create_fake_etud(dept: Departement) -> models.Identite: - """Créé un faux étudiant et l'insère dans la base""" +def create_fake_etud(dept: Departement) -> Identite: + """Créé un faux étudiant et l'insère dans la base.""" civilite = random.choice(("M", "F", "X")) nom, prenom = nomprenom(civilite) - etud = models.Identite(civilite=civilite, nom=nom, prenom=prenom, dept_id=dept.id) + etud: Identite = Identite( + civilite=civilite, nom=nom, prenom=prenom, dept_id=dept.id + ) db.session.add(etud) db.session.commit() - etud.code_nip = etud.id - etud.code_ine = etud.id + # créé un étudiant sur deux avec un NIP et INE alphanumérique + etud.code_nip = f"{etud.id}" if (etud.id % 2) else f"NIP{etud.id}" + etud.code_ine = f"INE{etud.id}" if (etud.id % 2) else f"{etud.id}" db.session.add(etud) db.session.commit() adresse = models.Adresse( From 21d9655655fe1b007df09e4f01573cb7d77cd271 Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Mon, 9 May 2022 18:32:54 +0200 Subject: [PATCH 19/20] Export recap complet en excel brut --- app/scodoc/sco_recapcomplet.py | 10 +++++++--- sco_version.py | 2 +- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/app/scodoc/sco_recapcomplet.py b/app/scodoc/sco_recapcomplet.py index 179014cf..6a22c195 100644 --- a/app/scodoc/sco_recapcomplet.py +++ b/app/scodoc/sco_recapcomplet.py @@ -84,12 +84,15 @@ def formsemestre_recapcomplet( selected_etudid: etudid sélectionné (pour scroller au bon endroit) """ formsemestre = FormSemestre.query.get_or_404(formsemestre_id) - + file_formats = {"csv", "json", "xls", "xlsx", "xlsall", "xml"} + supported_formats = file_formats | {"html"} + if tabformat not in supported_formats: + raise ScoValueError(f"Format non supporté: {tabformat}") + is_file = tabformat in file_formats modejury = int(modejury) - xml_with_decisions = int(xml_with_decisions) force_publishing = int(force_publishing) - is_file = tabformat in {"csv", "json", "xls", "xlsx", "xlsall", "xml"} + data = _do_formsemestre_recapcomplet( formsemestre_id, format=tabformat, @@ -128,6 +131,7 @@ def formsemestre_recapcomplet( for (format, label) in ( ("html", "Tableau"), ("evals", "Avec toutes les évaluations"), + ("xlsx", "Excel non formatté"), ("xml", "Bulletins XML (obsolète)"), ("json", "Bulletins JSON"), ): diff --git a/sco_version.py b/sco_version.py index fe5771ed..8c3e5f8b 100644 --- a/sco_version.py +++ b/sco_version.py @@ -1,7 +1,7 @@ # -*- mode: python -*- # -*- coding: utf-8 -*- -SCOVERSION = "9.2.17" +SCOVERSION = "9.2.18" SCONAME = "ScoDoc" From 6e43ec6feb91f849fc0a206686b9127727833d51 Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Mon, 9 May 2022 19:56:40 +0200 Subject: [PATCH 20/20] =?UTF-8?q?Affichage=20groupes=20sur=20page=20=C3=A9?= =?UTF-8?q?tud.=20Closes=20#373?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/scodoc/sco_groups.py | 16 ++++++-- app/scodoc/sco_page_etud.py | 73 +++++++++++++++++++++++-------------- 2 files changed, 58 insertions(+), 31 deletions(-) diff --git a/app/scodoc/sco_groups.py b/app/scodoc/sco_groups.py index 8e834b23..80d8e02c 100644 --- a/app/scodoc/sco_groups.py +++ b/app/scodoc/sco_groups.py @@ -413,8 +413,15 @@ def formsemestre_get_etud_groupnames(formsemestre_id, attr="group_name"): return R -def etud_add_group_infos(etud, formsemestre_id, sep=" "): - """Add informations on partitions and group memberships to etud (a dict with an etudid)""" +def etud_add_group_infos(etud, formsemestre_id, sep=" ", only_to_show=False): + """Add informations on partitions and group memberships to etud + (a dict with an etudid) + If only_to_show, restrict to partions such that show_in_lists is True. + + etud['partitions'] = { partition_id : group + partition_name } + etud['groupes'] = "TDB, Gr2, TPB1" + etud['partitionsgroupes'] = "Groupes TD:TDB, Groupes TP:Gr2 (...)" + """ etud[ "partitions" ] = collections.OrderedDict() # partition_id : group + partition_name @@ -423,11 +430,14 @@ def etud_add_group_infos(etud, formsemestre_id, sep=" "): return etud infos = ndb.SimpleDictFetch( - """SELECT p.partition_name, g.*, g.id AS group_id + """SELECT p.partition_name, p.show_in_lists, g.*, g.id AS group_id FROM group_descr g, partition p, group_membership gm WHERE gm.etudid=%(etudid)s and gm.group_id = g.id and g.partition_id = p.id and p.formsemestre_id = %(formsemestre_id)s + """ + + (" and (p.show_in_lists is True) " if only_to_show else "") + + """ ORDER BY p.numero """, {"etudid": etud["etudid"], "formsemestre_id": formsemestre_id}, diff --git a/app/scodoc/sco_page_etud.py b/app/scodoc/sco_page_etud.py index 3a99d223..2631ef45 100644 --- a/app/scodoc/sco_page_etud.py +++ b/app/scodoc/sco_page_etud.py @@ -153,14 +153,14 @@ def ficheEtud(etudid=None): try: # pour les bookmarks avec d'anciens ids... etudid = int(etudid) except ValueError: - raise ScoValueError("id invalide !") + raise ScoValueError("id invalide !") from ValueError # la sidebar est differente s'il y a ou pas un etudid # voir html_sidebar.sidebar() g.etudid = etudid args = make_etud_args(etudid=etudid) etuds = sco_etud.etudident_list(cnx, args) if not etuds: - log("ficheEtud: etudid=%s request.args=%s" % (etudid, request.args)) + log(f"ficheEtud: etudid={etudid!r} request.args={request.args!r}") raise ScoValueError("Etudiant inexistant !") etud = etuds[0] etudid = etud["etudid"] @@ -173,7 +173,7 @@ def ficheEtud(etudid=None): if info["lieu_naissance"]: info["info_naissance"] += " à " + info["lieu_naissance"] if info["dept_naissance"]: - info["info_naissance"] += " (%s)" % info["dept_naissance"] + info["info_naissance"] += f" ({info['dept_naissance']})" info["etudfoto"] = sco_photos.etud_photo_html(etud) if ( (not info["domicile"]) @@ -205,7 +205,7 @@ def ficheEtud(etudid=None): ) else: info["emaillink"] = "(pas d'adresse e-mail)" - # champs dependant des permissions + # Champ dépendant des permissions: if authuser.has_permission(Permission.ScoEtudChangeAdr): info["modifadresse"] = ( 'modifier adresse' @@ -216,9 +216,10 @@ def ficheEtud(etudid=None): # Groupes: sco_groups.etud_add_group_infos( - info, info["cursem"]["formsemestre_id"] if info["cursem"] else None + info, + info["cursem"]["formsemestre_id"] if info["cursem"] else None, + only_to_show=True, ) - # Parcours de l'étudiant if info["sems"]: info["last_formsemestre_id"] = info["sems"][0]["formsemestre_id"] @@ -235,15 +236,28 @@ def ficheEtud(etudid=None): ) grlink = '%s' % descr["situation"] else: - group = sco_groups.get_etud_main_group(etudid, sem["formsemestre_id"]) - if group["partition_name"]: - gr_name = group["group_name"] - else: - gr_name = "tous" - grlink = ( - 'groupe %s' - % (group["group_id"], gr_name) + e = {"etudid": etudid} + sco_groups.etud_add_group_infos( + e, + sem["formsemestre_id"], + only_to_show=True, ) + + grlinks = [] + for partition in e["partitions"].values(): + if partition["partition_name"]: + gr_name = partition["group_name"] + else: + gr_name = "tous" + + grlinks.append( + f"""{gr_name} + """ + ) + grlink = ", ".join(grlinks) # infos ajoutées au semestre dans le parcours (groupe, menu) menu = _menuScolarite(authuser, sem, etudid) if menu: @@ -296,17 +310,18 @@ def ficheEtud(etudid=None): if not sco_permissions_check.can_suppress_annotation(a["id"]): a["dellink"] = "" else: - a[ - "dellink" - ] = '%s' % ( - etudid, - a["id"], - scu.icontag( - "delete_img", - border="0", - alt="suppress", - title="Supprimer cette annotation", - ), + a["dellink"] = ( + '%s' + % ( + etudid, + a["id"], + scu.icontag( + "delete_img", + border="0", + alt="suppress", + title="Supprimer cette annotation", + ), + ) ) author = sco_users.user_info(a["author"]) alist.append( @@ -422,9 +437,11 @@ def ficheEtud(etudid=None): # if info["groupes"].strip(): - info["groupes_row"] = ( - 'Groupe :%(groupes)s' % info - ) + info[ + "groupes_row" + ] = f""" + Groupes :{info['groupes']} + """ else: info["groupes_row"] = "" info["menus_etud"] = menus_etud(etudid)