diff --git a/app/api/assiduites.py b/app/api/assiduites.py index f7be3594..e31cb0f7 100644 --- a/app/api/assiduites.py +++ b/app/api/assiduites.py @@ -58,9 +58,9 @@ def assiduite(assiduite_id: int = None): "date_fin": "2022-10-31T10:00+01:00", "etat": "retard", "desc": "une description", - "user_id: 1 or null, - "user_name" : login scodoc or null - "user_nom_complet": "Marie Dupont" + "user_id": 1 or null, + "user_name" : login scodoc or null, + "user_nom_complet": "Marie Dupont", "est_just": False or True, } ``` @@ -132,42 +132,6 @@ def assiduites_count( """ Retourne le nombre d'assiduités d'un étudiant. - Un filtrage peut être donné avec une `query`. - - Les différents filtres : - - Type (type de comptage -> journee, demi, heure, nombre d'assiduite): - query?type=(journee, demi, heure) -> une seule valeur parmis les trois - ex: .../query?type=heure - Comportement par défaut : compte le nombre d'assiduité enregistrée - - - Etat (etat de l'étudiant -> absent, present ou retard): - `query?etat=[- liste des états séparé par une virgule -]` - ex: .../query?etat=present,retard - - Date debut - (date de début de l'assiduité, sont affichés les assiduités - dont la date de début est supérieur ou égale à la valeur donnée): - query?date_debut=[- date au format iso -] - ex: query?date_debut=2022-11-03T08:00+01:00 - Date fin - (date de fin de l'assiduité, sont affichés les assiduités - dont la date de fin est inférieure ou égale à la valeur donnée): - query?date_fin=[- date au format iso -] - ex: query?date_fin=2022-11-03T10:00+01:00 - Moduleimpl_id (l'id du module concerné par l'assiduité): - query?moduleimpl_id=[- int ou vide -] - ex: query?moduleimpl_id=1234 - query?moduleimpl_od= - Formsemstre_id (l'id du formsemestre concerné par l'assiduité) - query?formsemestre_id=[int] - ex query?formsemestre_id=3 - user_id (l'id de l'auteur de l'assiduité) - query?user_id=[int] - ex query?user_id=3 - est_just (si l'assiduité est justifié (fait aussi filtre par abs/retard)) - query?est_just=[bool] - query?est_just=f - query?est_just=t - QUERY ----- user_id: @@ -180,6 +144,18 @@ def assiduites_count( metric: split: + PARAMS + ----- + user_id:l'id de l'auteur de l'assiduité + est_just:si l'assiduité est justifiée (fait aussi filtre abs/retard) + moduleimpl_id:l'id du module concerné par l'assiduité + date_debut:date de début de l'assiduité (supérieur ou égal) + date_fin:date de fin de l'assiduité (inférieur ou égal) + etat:etat de l'étudiant → absent, present ou retard + formsemestre_id:l'identifiant du formsemestre concerné par l'assiduité + metric: la/les métriques de comptage (journee, demi, heure, compte) + split: divise le comptage par état + """ # Récupération de l'étudiant @@ -235,39 +211,6 @@ def assiduites_count( def assiduites(etudid: int = None, nip=None, ine=None, with_query: bool = False): """ Retourne toutes les assiduités d'un étudiant - chemin : /assiduites/ - - Un filtrage peut être donné avec une query - chemin : /assiduites//query? - - Les différents filtres : - Etat (etat de l'étudiant -> absent, present ou retard): - query?etat=[- liste des états séparé par une virgule -] - ex: .../query?etat=present,retard - Date debut - (date de début de l'assiduité, sont affichés les assiduités - dont la date de début est supérieur ou égale à la valeur donnée): - query?date_debut=[- date au format iso -] - ex: query?date_debut=2022-11-03T08:00+01:00 - Date fin - (date de fin de l'assiduité, sont affichés les assiduités - dont la date de fin est inférieure ou égale à la valeur donnée): - query?date_fin=[- date au format iso -] - ex: query?date_fin=2022-11-03T10:00+01:00 - Moduleimpl_id (l'id du module concerné par l'assiduité): - query?moduleimpl_id=[- int ou vide -] - ex: query?moduleimpl_id=1234 - query?moduleimpl_od= - Formsemstre_id (l'id du formsemestre concerné par l'assiduité) - query?formsemstre_id=[int] - ex query?formsemestre_id=3 - user_id (l'id de l'auteur de l'assiduité) - query?user_id=[int] - ex query?user_id=3 - est_just (si l'assiduité est justifié (fait aussi filtre par abs/retard)) - query?est_just=[bool] - query?est_just=f - query?est_just=t QUERY ----- @@ -279,6 +222,16 @@ def assiduites(etudid: int = None, nip=None, ine=None, with_query: bool = False) etat: formsemestre_id: + PARAMS + ----- + user_id:l'id de l'auteur de l'assiduité + est_just:si l'assiduité est justifiée (fait aussi filtre abs/retard) + moduleimpl_id:l'id du module concerné par l'assiduité + date_debut:date de début de l'assiduité (supérieur ou égal) + date_fin:date de fin de l'assiduité (inférieur ou égal) + etat:etat de l'étudiant → absent, present ou retard + formsemestre_id:l'identifiant du formsemestre concerné par l'assiduité + """ # Récupération de l'étudiant @@ -333,7 +286,9 @@ def assiduites_evaluations(etudid: int = None, nip=None, ine=None): Pour chaque évaluation, retourne la liste des objets assiduités sur la plage de l'évaluation - Présentation du retour : + Exemple de résultat: + + ```json [ { "evaluation_id": 1234, @@ -345,6 +300,7 @@ def assiduites_evaluations(etudid: int = None, nip=None, ine=None): ] } ] + ``` """ # Récupération de l'étudiant @@ -372,7 +328,10 @@ def assiduites_evaluations(etudid: int = None, nip=None, ine=None): def evaluation_assiduites(evaluation_id): """ Retourne les objets assiduités de chaque étudiant sur la plage de l'évaluation - Présentation du retour : + + Exemple de résultat: + + ```json { "" : [ { @@ -381,6 +340,11 @@ def evaluation_assiduites(evaluation_id): }, ] } + ``` + + CATEGORY + -------- + evaluations """ # Récupération de l'évaluation try: @@ -409,37 +373,6 @@ def assiduites_group(with_query: bool = False): Retourne toutes les assiduités d'un groupe d'étudiants chemin : /assiduites/group/query?etudids=1,2,3 - Un filtrage peut être donné avec une query - chemin : /assiduites/group/query?etudids=1,2,3 - - Les différents filtres : - Etat (etat de l'étudiant -> absent, present ou retard): - query?etat=[- liste des états séparé par une virgule -] - ex: .../query?etat=present,retard - Date debut - (date de début de l'assiduité, sont affichés les assiduités - dont la date de début est supérieur ou égale à la valeur donnée): - query?date_debut=[- date au format iso -] - ex: query?date_debut=2022-11-03T08:00+01:00 - Date fin - (date de fin de l'assiduité, sont affichés les assiduités - dont la date de fin est inférieure ou égale à la valeur donnée): - query?date_fin=[- date au format iso -] - ex: query?date_fin=2022-11-03T10:00+01:00 - Moduleimpl_id (l'id du module concerné par l'assiduité): - query?moduleimpl_id=[- int ou vide -] - ex: query?moduleimpl_id=1234 - query?moduleimpl_od= - Formsemstre_id (l'id du formsemestre concerné par l'assiduité) - query?formsemstre_id=[int] - ex query?formsemestre_id=3 - user_id (l'id de l'auteur de l'assiduité) - query?user_id=[int] - ex query?user_id=3 - est_just (si l'assiduité est justifié (fait aussi filtre par abs/retard)) - query?est_just=[bool] - query?est_just=f - query?est_just=t QUERY ----- @@ -449,9 +382,20 @@ def assiduites_group(with_query: bool = False): date_debut: date_fin: etat: - etudids: formsemestre_id: + PARAMS + ----- + user_id:l'id de l'auteur de l'assiduité + est_just:si l'assiduité est justifiée (fait aussi filtre abs/retard) + moduleimpl_id:l'id du module concerné par l'assiduité + date_debut:date de début de l'assiduité (supérieur ou égal) + date_fin:date de fin de l'assiduité (inférieur ou égal) + etat:etat de l'étudiant → absent, present ou retard + etudids:liste des ids des étudiants concernés par la recherche + formsemestre_id:l'identifiant du formsemestre concerné par l'assiduité + """ # Récupération des étudiants dans la requête @@ -511,6 +455,7 @@ def assiduites_group(with_query: bool = False): @permission_required(Permission.ScoView) def assiduites_formsemestre(formsemestre_id: int, with_query: bool = False): """Retourne toutes les assiduités du formsemestre + QUERY ----- user_id: @@ -519,6 +464,16 @@ def assiduites_formsemestre(formsemestre_id: int, with_query: bool = False): date_debut: date_fin: etat: + + PARAMS + ----- + user_id:l'id de l'auteur de l'assiduité + est_just:si l'assiduité est justifiée (fait aussi filtre abs/retard) + moduleimpl_id:l'id du module concerné par l'assiduité + date_debut:date de début de l'assiduité (supérieur ou égal) + date_fin:date de fin de l'assiduité (inférieur ou égal) + etat:etat de l'étudiant → absent, present ou retard + formsemestre_id:l'identifiant du formsemestre concerné par l'assiduité """ # Récupération du formsemestre à partir du formsemestre_id @@ -582,6 +537,18 @@ def assiduites_formsemestre_count( formsemestre_id: metric: split: + + PARAMS + ----- + user_id:l'id de l'auteur de l'assiduité + est_just:si l'assiduité est justifiée (fait aussi filtre abs/retard) + moduleimpl_id:l'id du module concerné par l'assiduité + date_debut:date de début de l'assiduité (supérieur ou égal) + date_fin:date de fin de l'assiduité (inférieur ou égal) + etat:etat de l'étudiant → absent, present ou retard + formsemestre_id:l'identifiant du formsemestre concerné par l'assiduité + metric: la/les métriques de comptage (journee, demi, heure, compte) + split: divise le comptage par état """ # Récupération du formsemestre à partir du formsemestre_id @@ -633,7 +600,10 @@ def assiduites_formsemestre_count( def assiduite_create(etudid: int = None, nip=None, ine=None): """ Enregistrement d'assiduités pour un étudiant (etudid) - La requête doit avoir un content type "application/json": + + DATA + ---- + ```json [ { "date_debut": str, @@ -649,6 +619,7 @@ def assiduite_create(etudid: int = None, nip=None, ine=None): } ... ] + ``` """ # Récupération de l'étudiant @@ -702,7 +673,10 @@ def assiduite_create(etudid: int = None, nip=None, ine=None): def assiduites_create(): """ Création d'une assiduité ou plusieurs assiduites - La requête doit avoir un content type "application/json": + + DATA + ---- + ```json [ { "date_debut": str, @@ -715,12 +689,12 @@ def assiduites_create(): "date_fin": str, "etat": str, "etudid":int, - "moduleimpl_id": int, "desc":str, } ... ] + ``` """ @@ -891,13 +865,14 @@ def assiduite_delete(): """ Suppression d'une assiduité à partir de son id - Forme des données envoyées : - + DATA + ---- + ```json [ , ... ] - + ``` """ # Récupération des ids envoyés dans la liste @@ -972,13 +947,17 @@ def _delete_one(assiduite_id: int) -> tuple[int, str]: def assiduite_edit(assiduite_id: int): """ Edition d'une assiduité à partir de son id - La requête doit avoir un content type "application/json": + + DATA + ---- + ```json { "etat"?: str, "moduleimpl_id"?: int "desc"?: str "est_just"?: bool } + ``` """ # Récupération de l'assiduité à modifier @@ -1020,7 +999,10 @@ def assiduite_edit(assiduite_id: int): def assiduites_edit(): """ Edition de plusieurs assiduités - La requête doit avoir un content type "application/json": + + DATA + ---- + ```json [ { "assiduite_id" : int, @@ -1030,6 +1012,7 @@ def assiduites_edit(): "est_just"?: bool } ] + ``` """ edit_list: list[object] = request.get_json(force=True) diff --git a/app/api/justificatifs.py b/app/api/justificatifs.py index eacd6778..09f467cc 100644 --- a/app/api/justificatifs.py +++ b/app/api/justificatifs.py @@ -92,38 +92,26 @@ def justificatif(justif_id: int = None): def justificatifs(etudid: int = None, nip=None, ine=None, with_query: bool = False): """ Retourne toutes les assiduités d'un étudiant - chemin : /justificatifs/ - Un filtrage peut être donné avec une query - chemin : /justificatifs//query? - - Les différents filtres : - Etat (etat du justificatif -> validé, non validé, modifé, en attente): - query?etat=[- liste des états séparé par une virgule -] - ex: .../query?etat=validé,modifié - Date debut - (date de début du justificatif, sont affichés les justificatifs - dont la date de début est supérieur ou égale à la valeur donnée): - query?date_debut=[- date au format iso -] - ex: query?date_debut=2022-11-03T08:00+01:00 - Date fin - (date de fin du justificatif, sont affichés les justificatifs - dont la date de fin est inférieure ou égale à la valeur donnée): - query?date_fin=[- date au format iso -] - ex: query?date_fin=2022-11-03T10:00+01:00 - user_id (l'id de l'auteur du justificatif) - query?user_id=[int] - ex query?user_id=3 QUERY ----- user_id: - est_just: date_debut: date_fin: etat: order: courant: group_id: + + PARAMS + ----- + user_id:l'id de l'auteur du justificatif + date_debut:date de début du justificatif (supérieur ou égal) + date_fin:date de fin du justificatif (inférieur ou égal) + etat:etat du justificatif → valide, non_valide, attente, modifie + order:retourne les justificatifs dans l'ordre décroissant (non vide = True) + courant:retourne les justificatifs de l'année courante (bool : v/t ou f) + group_id: """ # Récupération de l'étudiant etud: Identite = tools.get_etud(etudid, nip, ine) @@ -176,6 +164,16 @@ def justificatifs_dept(dept_id: int = None, with_query: bool = False): order: courant: group_id: + + PARAMS + ----- + user_id:l'id de l'auteur du justificatif + date_debut:date de début du justificatif (supérieur ou égal) + date_fin:date de fin du justificatif (inférieur ou égal) + etat:etat du justificatif → valide, non_valide, attente, modifie + order:retourne les justificatifs dans l'ordre décroissant (non vide = True) + courant:retourne les justificatifs de l'année courante (bool : v/t ou f) + group_id: """ # Récupération du département et des étudiants du département @@ -259,6 +257,16 @@ def justificatifs_formsemestre(formsemestre_id: int, with_query: bool = False): order: courant: group_id: + + PARAMS + ----- + user_id:l'id de l'auteur du justificatif + date_debut:date de début du justificatif (supérieur ou égal) + date_fin:date de fin du justificatif (inférieur ou égal) + etat:etat du justificatif → valide, non_valide, attente, modifie + order:retourne les justificatifs dans l'ordre décroissant (non vide = True) + courant:retourne les justificatifs de l'année courante (bool : v/t ou f) + group_id: """ # Récupération du formsemestre @@ -307,7 +315,10 @@ def justificatifs_formsemestre(formsemestre_id: int, with_query: bool = False): def justif_create(etudid: int = None, nip=None, ine=None): """ Création d'un justificatif pour l'étudiant (etudid) - La requête doit avoir un content type "application/json": + + DATA + ---- + ```json [ { "date_debut": str, @@ -322,6 +333,7 @@ def justif_create(etudid: int = None, nip=None, ine=None): } ... ] + ``` """ @@ -452,14 +464,17 @@ def _create_one( def justif_edit(justif_id: int): """ Edition d'un justificatif à partir de son id - La requête doit avoir un content type "application/json": + DATA + ---- + ```json { "etat"?: str, "raison"?: str "date_debut"?: str "date_fin"?: str } + ``` """ # Récupération du justificatif à modifier @@ -566,14 +581,14 @@ def justif_delete(): """ Suppression d'un justificatif à partir de son id - Forme des données envoyées : - + DATA + ---- + ```json [ , ... ] - - + ``` """ # Récupération des justif_ids @@ -648,6 +663,8 @@ def _delete_one(justif_id: int) -> tuple[int, str]: def justif_import(justif_id: int = None): """ Importation d'un fichier (création d'archive) + + > Procédure d'importation de fichier : [importer un justificatif](FichiersJustificatifs.md#importer-un-fichier) """ # On vérifie qu'un fichier a bien été envoyé @@ -699,6 +716,8 @@ def justif_export(justif_id: int | None = None, filename: str | None = None): """ Retourne un fichier d'une archive d'un justificatif. La permission est ScoView + (AbsJustifView ou être l'auteur du justifcatif) + + > Procédure de téléchargement de fichier : [télécharger un justificatif](FichiersJustificatifs.md#télécharger-un-fichier) """ # On récupère le justificatif concerné justificatif_unique = Justificatif.get_justificatif(justif_id) @@ -736,14 +755,20 @@ def justif_export(justif_id: int | None = None, filename: str | None = None): def justif_remove(justif_id: int = None): """ Supression d'un fichier ou d'une archive - { - "remove": <"all"/"list"> + > Procédure de suppression de fichier : [supprimer un justificatif](FichiersJustificatifs.md#supprimer-un-fichier) + + DATA + ---- + ```json + { + "remove": <"all"/"list">, "filenames"?: [ , ... ] } + ``` """ # On récupère le dictionnaire diff --git a/app/templates/apidoc.j2 b/app/templates/apidoc.j2 new file mode 100644 index 00000000..36d923c9 --- /dev/null +++ b/app/templates/apidoc.j2 @@ -0,0 +1,31 @@ +#### **`{{doc.nom}}`** + +{% if doc.routes %} +{% if doc.routes|length == 1 %} +* **Route:** `{{doc.routes[0]|safe}}` +{% else %} +* **Routes:** + {% for route in doc.routes %} + * `{{route|safe}}` + {% endfor %} +{% endif %} +{% endif %} +* **Méthode:** `{{doc.method}}` +* **Permission:** `{{doc.permission}}` +{% if doc.params %} +* **Paramètres:** + {% for param in doc.params %} + * `{{param.nom|safe}}` : {{param.description|safe}} + {% endfor %} +{% endif %} +{% if doc.description %} +* **Description:** {{doc.description|safe}} +{% endif %} +{% if doc.data %} +* **Data:** {{doc.data|safe}} +{% endif %} + +{% if doc.sample %} +* **Exemple de résultat:** [{{doc.sample.nom}}](./samples/sample_{{doc.sample.href}}) +{% else %} +{% endif %} \ No newline at end of file diff --git a/tools/create_api_map.py b/tools/create_api_map.py index 771f34f1..963cf394 100644 --- a/tools/create_api_map.py +++ b/tools/create_api_map.py @@ -4,10 +4,12 @@ Script permettant de générer une carte SVG de l'API de ScoDoc Écrit par Matthias HARTMANN """ +import sys import xml.etree.ElementTree as ET import re from app.auth.models import Permission +from flask import render_template class COLORS: @@ -507,6 +509,14 @@ def analyze_api_routes(app, endpoint_start: str) -> tuple: if child.query and not href.endswith("-query"): href += "-query" + # category + + category: str = func.__module__.replace("app.api.", "") + mod_doc: str = sys.modules[func.__module__].__doc__ or "" + mod_doc_dict: dict = _parse_doc_string(mod_doc) + if mod_doc_dict.get("CATEGORY"): + category = mod_doc_dict["CATEGORY"][0].strip() + permissions: str try: permissions: str = ", ".join( @@ -524,6 +534,7 @@ def analyze_api_routes(app, endpoint_start: str) -> tuple: "permission": permissions, "description": doc_dict.get("", ""), "params": doc_dict.get("PARAMS", ""), + "category": doc_dict.get("CATEGORY", [False])[0] or category, } # On met à jour le token courant pour le prochain segment @@ -543,8 +554,6 @@ def gen_api_map(app, endpoint_start="api."): puis génère un fichier SVG à partir de cet arbre """ - print("DEBUG", app.view_functions["apiweb.user_info"].scodoc_permission) - api_map, doctable_lines = analyze_api_routes(app, endpoint_start) # On génère le SVG à partir de l'arbre de Token @@ -870,88 +879,61 @@ def _write_gen_table(table: str, filename: str = "/tmp/api_table.md"): ) -if __name__ == "__main__": - # Exemple d'utilisation de la classe Token - # Exemple simple de création d'un arbre de Token - - # root = Token("api") - # child1 = Token("assiduites", leaf=True) - # child1.func_name = "assiduites_get" - # child2 = Token("count") - # child22 = Token("all") - # child23 = Token( - # "query", - # query={ - # "etat": "", - # "moduleimpl_id": "", - # "count": "", - # "formsemestre_id": "", - # }, - # ) - # child3 = Token("justificatifs", "POST") - # child3.func_name = "justificatifs_post" - - # root.add_child(child1) - # child1.add_child(child2) - # child2.add_child(child22) - # child2.add_child(child23) - # root.add_child(child3) - - # group_element = root.to_svg_group() - - # generate_svg(group_element, "/tmp/api_map.svg") - dt: dict = parse_doctable_doc(parse_doctable_doc.__doc__) - md: str = "POST" - hf: str = "assiduites-query" - - doc: dict = { - "doctable": dt, - "method": md, - "href": hf, - } - print(_gen_table([doc])) - - def doc_route(doctable: dict) -> str: """Generate markdown doc for a route""" - doc = f""" -#### **`{doctable['nom']}`** + jinja_obj: dict = {} + jinja_obj.update(doctable) + jinja_obj["nom"] = doctable["nom"].strip() # on retire les caractères blancs -""" - if doctable.get("routes"): - if len(doctable["routes"]) == 1: - doc += f"""* ** Route :** `{doctable["routes"][0]}`\n""" - else: - doc += "* ** Routes :**\n" - for route in doctable["routes"]: - doc += f""" * `{route}`\n""" - doc += f"""* **Méthode: {doctable['method']}** -* **Permission: `{doctable.get('permission', '')}`** -""" if doctable.get("params"): + jinja_obj["params"] = [] for param in doctable["params"]: frags = param.split(":", maxsplit=1) if len(frags) == 2: name, descr = frags + jinja_obj["params"].append( + {"nom": name.strip(), "description": descr.strip()} + ) else: print(f"Warning: {doctable['nom']} : invalid PARAMS {param}") - name, descr = param, "" - doc += f""" * `{name}`: {descr}\n""" - if doctable.get("data"): - doc += f"""* **Data:** {doctable['data']}\n""" if doctable.get("description"): descr = "\n".join(s for s in doctable["description"]) - doc += f"""* **Description:** {descr}\n""" - return doc + jinja_obj["description"] = descr.strip() + + jinja_obj["sample"] = { + "nom": f"{jinja_obj['nom']}.json", + "href": f"{jinja_obj['nom'].replace('_', '-')}.json.md", + } + + return render_template("apidoc.j2", doc=jinja_obj) def gen_api_doc(app, endpoint_start="api."): "commande gen-api-doc" _, doctable_lines = analyze_api_routes(app, endpoint_start) - mddoc = "\n".join( - doc_route(doctable) - for doctable in sorted(doctable_lines.values(), key=lambda x: x["nom"]) - ) + + mddoc: str = "" + + categories: dict = {} + for value in doctable_lines.values(): + category = value["category"] + if category not in categories: + categories[category] = [] + categories[category].append(value) + + # sort categories by name + categories: dict = dict(sorted(categories.items(), key=lambda x: x[0])) + + category: str + routes: list[dict] + for category, routes in categories.items(): + # sort routes by name + routes.sort(key=lambda x: x["nom"]) + + mddoc += f"### API {category.capitalize()}\n\n" + for route in routes: + mddoc += doc_route(route) + mddoc += "\n\n" fname = "/tmp/apidoc.md" with open(fname, "w", encoding="utf-8") as f: